1
Fork 0

Initial commit

This commit is contained in:
Tobias Berger 2023-06-27 18:52:22 +02:00
commit 192858094c
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
9 changed files with 420 additions and 0 deletions

169
.gitignore vendored Normal file
View file

@ -0,0 +1,169 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*

13
LICENSE Normal file
View file

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# minesweeper-nojs
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run server.ts
```
This project was created using `bun init` in bun v0.4.3. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

BIN
bun.lockb Executable file

Binary file not shown.

14
justfile Executable file
View file

@ -0,0 +1,14 @@
#!/usr/bin/just --justfile
# Lists all recipes
list:
{{ just_executable() }} --list
# Run a dev server anad recompile on file change
dev:
bun --port=55782 --watch ./server.ts
# Run a production server
run:
NODE_ENV="production" bun run --port=55782 ./server.ts

8
package.json Normal file
View file

@ -0,0 +1,8 @@
{
"name": "minesweeper-nojs",
"module": "server.ts",
"type": "module",
"devDependencies": {
"bun-types": "^0.4.0"
}
}

128
server.ts Normal file
View file

@ -0,0 +1,128 @@
const PORT = 55782;
const HEADERS_BOARD = {
headers: {
"Content-Type": "text/html",
"Cache-Control": "no-store",
},
};
const HEADERS_STYLESHEET = {
headers: {
"Content-Type": "text/css",
},
};
async function htmlDoc(body: string, head: string): Promise<string> {
return `<!DOCTYPE html><html><head><link rel=stylesheet href=style.css>${head}</head><body>${body}</body></html>`
}
async function createBoard(width: number, height: number, mines: number): Promise<string> {
console.debug("width", width, "height", height, "mines", mines);
if (width <= 0 || height <= 0 || mines <= 0) {
return await htmlDoc("NO 0 VALUES ALLOWED", "");
}
if (width > 50 || height > 50) {
return await htmlDoc("NO FIELDS LARGER THAN 50 IN EITHER DIMENSION", "");
}
if (mines >= width * height) {
return await htmlDoc("TOO MANY MINES", "");
}
const board = new Array(width * height).fill(false);
let safeIdx = board.map((_, idx) => idx);
for (let placedMines = 0; placedMines < mines; placedMines++) {
const mineIdx = safeIdx.splice(Math.floor(Math.random() * safeIdx.length), 1)[0];
board[mineIdx] = true;
}
function getNeighbors(idx: number): number {
let count = 0;
const leftEdge = (idx % width) === 0;
const topEdge = idx < width;
const bottomEdge = idx > (height * (width - 1));
const rightEdge = (idx % width) === (width - 1);
if (!leftEdge && board[idx - 1]) {
count++;
}
if (!leftEdge && !topEdge && board[idx - 1 - width]) {
count++;
}
if (!leftEdge && !bottomEdge && board[idx - 1 + width]) {
count++;
}
if (!rightEdge && board[idx + 1]) {
count++;
}
if (!rightEdge && !topEdge && board[idx + 1 - width]) {
count++;
}
if (!rightEdge && !bottomEdge && board[idx + 1 + width]) {
count++;
}
if (!topEdge && board[idx - width]) {
count++;
}
if (!bottomEdge && board[idx + width]) {
count++;
}
return count;
}
let table = "<table><tbody><tr>";
let inputs = "";
let style = "<style>";
for (const [idx, fieldIsMine] of board.entries()) {
table += `<td><label for=input_${idx}></label></td>`;
if ((idx % width) === (width - 1)) {
table += "</tr><tr>"
}
inputs += `<input id=input_${idx} type=checkbox ${fieldIsMine ? "data-mine" : "data-safe"}></input>`;
style += `#input_${idx}:checked ~ main label[for="input_${idx}"]::before { content: "${fieldIsMine ? "X" : getNeighbors(idx)}"; }\n`
+ `#input_${idx}:checked ~ main label[for="input_${idx}"] { pointer-events: none; }`;
}
table += "</tr></tbody></table>"
style += "</style>"
return await htmlDoc(`${inputs}<main>${table}</main><footer><span id=lost>HAHA you lost >:)</span><span id=won>YAY, you WON!!!</span></footer>`, style)
}
Bun.serve({
PORT,
async fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/favicon.ico") {
// TODO: Add favicon
return new Response("Not found", { status: 404, statusText: "Not found" });
}
if (url.pathname === "/style.css") {
return new Response(await Bun.file("style.css").text(), HEADERS_STYLESHEET);
}
if (url.pathname !== "/") {
return new Response("Not found", { status: 404, statusText: "Not found" });
}
let height = Number.parseInt(url.searchParams.get("height") ?? "NaN");
if (Number.isNaN(height)) {
height = 10;
}
let width = Number.parseInt(url.searchParams.get("width") ?? "NaN");
if (Number.isNaN(width)) {
width = 10;
}
let mines = Number.parseInt(url.searchParams.get("mines") ?? "NaN");
if (Number.isNaN(mines)) {
mines = 10;
}
return new Response(await createBoard(width, height, mines), HEADERS_BOARD);
}
})

53
style.css Normal file
View file

@ -0,0 +1,53 @@
html {
height: 100%;
background: black;
color: white;
font-size: 40px;
}
label, label::before {
display: block;
width: 50px;
height: 50px;
cursor: pointer;
}
label::before {
text-align: center;
}
body {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
main {
margin: auto;
color: white;
}
input { display:none; label: pointer-events: none; }
label { pointer-events: none; }
input ~ main label::before { content: "O"; }
#won { display: initial; user-select: none; }
input[data-safe]:not(:checked) ~ footer #won { display: none; }
input[data-safe]:not(:checked) ~ main label { pointer-events: initial; }
#lost { display: none; user-select: none ; }
input[data-mine]:checked ~ footer #lost { display: initial; }
input[data-mine]:checked ~ main label { pointer-events: none; }
footer {
display: flex;
justify-content: center;
}
#lost, #won {
position: absolute;
top: 10px;
}

20
tsconfig.json Normal file
View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"lib": [
"ESNext"
],
"module": "esnext",
"target": "esnext",
"moduleResolution": "nodenext",
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"types": [
"bun-types" // add Bun global
]
}
}