From 192858094cb7330a644f51b7423e1f58a0971e37 Mon Sep 17 00:00:00 2001 From: Tobias Berger Date: Tue, 27 Jun 2023 18:52:22 +0200 Subject: [PATCH] Initial commit --- .gitignore | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE | 13 ++++ README.md | 15 +++++ bun.lockb | Bin 0 -> 1195 bytes justfile | 14 +++++ package.json | 8 +++ server.ts | 128 ++++++++++++++++++++++++++++++++++++++ style.css | 53 ++++++++++++++++ tsconfig.json | 20 ++++++ 9 files changed, 420 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100755 bun.lockb create mode 100755 justfile create mode 100644 package.json create mode 100644 server.ts create mode 100644 style.css create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f81d56e --- /dev/null +++ b/.gitignore @@ -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.\* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ff9e935 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e73f87 --- /dev/null +++ b/README.md @@ -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. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..21575725803ac528175b05fc705db9cfb5c15e77 GIT binary patch literal 1195 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_lQ6^BZer z1J?8JfBxddbGEj3jYe88PB6}?Vf>QXC1!nuO_~L$2naxGL4X57F|b4F1}H57Qvl`* zFfcR#F#{)%E-0~+jxSg%U;QTiY=_WmB}S+YQmGB4{15AanwS{n=7DHQAhrUU52P5N zjs!7@0w6aay91T~kN+RCBvB?3R>a5vF&Eir7R{ya_}Dt+vR`sbMRJC+`|g(4Y)SW6 z5PL3+hvmlcxvPJNXkUJ@s%5{X>}9`?_U9IH%dEBjcj&v)d-wd^s?t7d2wMwRkpPXM zIZ&F*rno3sFEg*WBrzvP50*UiLW)vT?G%g*6pAyeQuEVv6igHnb28KO^3%BBMlt+{ z0+0yEKQKOshN%KsgX~w3_z9@pXW(uqH8!#XT7ktfklr6qb$^IdZ@>()g@M5fN^{vn z8=1wHS^zy{2Dify)kCm!3QM0HjBy5fCVB=83{#+{z-ZJED^ATVOD)oKttd$?%1g`% zE-A{)OSe-nL|9}2x99-WBw=Vm<+1^}$q=iPax?Q%i_23}3sQ@8^YXKbL7BItvLLlM xqokyu*h*i&C^bE^xTL63FRvgst5`3;C|w_}Mqd}f)&+Y*uOz(+5-5X@1OR%l(-{B& literal 0 HcmV?d00001 diff --git a/justfile b/justfile new file mode 100755 index 0000000..649a2ae --- /dev/null +++ b/justfile @@ -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 + diff --git a/package.json b/package.json new file mode 100644 index 0000000..2ed941b --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "minesweeper-nojs", + "module": "server.ts", + "type": "module", + "devDependencies": { + "bun-types": "^0.4.0" + } +} \ No newline at end of file diff --git a/server.ts b/server.ts new file mode 100644 index 0000000..6d9410a --- /dev/null +++ b/server.ts @@ -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 { + return `${head}${body}` +} + +async function createBoard(width: number, height: number, mines: number): Promise { + 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 = ""; + let inputs = ""; + let style = "" + + return await htmlDoc(`${inputs}
${table}
HAHA you lost >:)YAY, you WON!!!
`, 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); + } +}) \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..83f8e5c --- /dev/null +++ b/style.css @@ -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; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..30522c1 --- /dev/null +++ b/tsconfig.json @@ -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 + ] + } +} \ No newline at end of file