From 8ba188dd7f7d31de9e1c556ed5ead8c6379a943d Mon Sep 17 00:00:00 2001 From: Vulae Date: Thu, 25 Apr 2024 20:55:24 -0500 Subject: [PATCH] new UI --- TODO | 3 +- src/components/Game.svelte | 133 ++-------------------- src/components/GithubCorner.svelte | 38 +++++++ src/components/InfoModal.svelte | 81 ++++++++++++++ src/components/InfoModalBiomes.svelte | 133 ++++++++++++++++++++++ src/components/Modal.svelte | 91 +++++++++------ src/components/Renderer.svelte | 142 ++++++++++++++++++++++++ src/lib/game/Chunk.ts | 10 +- src/lib/game/World.ts | 6 + src/routes/+layout.svelte | 3 + src/routes/+page.svelte | 81 ++++++-------- src/style.scss | 20 +++- static/biome_chocolate_screenshot.png | Bin 0 -> 4115 bytes static/biome_stroopwafel_screenshot.png | Bin 0 -> 8509 bytes static/biome_vanilla_screenshot.png | Bin 0 -> 4337 bytes static/biome_waffle_screenshot.png | Bin 0 -> 5040 bytes tailwind.config.js | 5 + 17 files changed, 537 insertions(+), 209 deletions(-) create mode 100644 src/components/GithubCorner.svelte create mode 100644 src/components/InfoModal.svelte create mode 100644 src/components/InfoModalBiomes.svelte create mode 100644 src/components/Renderer.svelte create mode 100644 static/biome_chocolate_screenshot.png create mode 100644 static/biome_stroopwafel_screenshot.png create mode 100644 static/biome_vanilla_screenshot.png create mode 100644 static/biome_waffle_screenshot.png diff --git a/TODO b/TODO index e2e3cee..4ffefb3 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,5 @@ -* [ ] Clean up /src/lib directory +* [X] Clean up /src/lib directory * [ ] Actual README.md @@ -18,6 +18,7 @@ * Control methods * [X] Mouse control method + * [ ] Remove middle click for moving, just use left click instead. * [ ] Full keyboard control method * [ ] Mobile control method diff --git a/src/components/Game.svelte b/src/components/Game.svelte index e6f6628..cdc69b4 100644 --- a/src/components/Game.svelte +++ b/src/components/Game.svelte @@ -1,147 +1,36 @@ { - keys.add(ev.key); - }} - on:keyup={ev => { - keys.delete(ev.key); - }} on:beforeunload={() => { save(saveSlot, world); }} /> - { - canvas.width = width; - canvas.height = height; - renderer.cameraScale(1); - if(firstCanvasResize) { - renderer.cameraTranslate(canvas.width / 2, canvas.height / 2); - firstCanvasResize = false; - } - needsRerender = true; - }} - on:mousedown={ev => { - if(document.pointerLockElement == canvas) return; - if(ev.button == 1) { - canvas.requestPointerLock(); - ev.preventDefault(); - } else if(ev.button == 0) { - ev.preventDefault(); - const pos = renderer.cameraPos(ev.offsetX, ev.offsetY); - world.reveal(pos.x, pos.y); - needsRerender = true; - } else if(ev.button == 2) { - ev.preventDefault(); - const pos = renderer.cameraPos(ev.offsetX, ev.offsetY); - world.flag(pos.x, pos.y); - needsRerender = true; +{#if world} + { + if(ev.detail.type == 'reveal') { + world.reveal(ev.detail.pos.x, ev.detail.pos.y); + } else if(ev.detail.type == 'flag') { + world.flag(ev.detail.pos.x, ev.detail.pos.y); + } else if(ev.detail.type == 'reset') { + world.reset(ev.detail.pos.x, ev.detail.pos.y); } - }} - on:mouseup={ev => { - if(document.pointerLockElement != canvas) return; - if(ev.button != 1) return; - document.exitPointerLock(); - }} - on:mousemove={ev => { - if(document.pointerLockElement != canvas) return; - renderer.cameraTranslate(ev.movementX, ev.movementY); - needsRerender = true; - }} - on:wheel|passive={ev => { - const scale = ev.deltaY > 0 ? 0.9 : 1.1; - if(renderer.cameraZoom != renderer.cameraScale(scale)) { - needsRerender = true; - } - }} - on:contextmenu={ev => { - ev.preventDefault(); - }} -/> + }} /> +{/if} diff --git a/src/components/GithubCorner.svelte b/src/components/GithubCorner.svelte new file mode 100644 index 0000000..c4544cf --- /dev/null +++ b/src/components/GithubCorner.svelte @@ -0,0 +1,38 @@ + + + +{#if animation} + +{/if} + + + + diff --git a/src/components/InfoModal.svelte b/src/components/InfoModal.svelte new file mode 100644 index 0000000..b533707 --- /dev/null +++ b/src/components/InfoModal.svelte @@ -0,0 +1,81 @@ + + + + +
+
+ {#each tabNames as tabName} + + {/each} +
+
+
+ {#if currentTab == 'Information'} +
+
+ Infinite Minesweeper +
+ An infinite twist on Minesweeper that adds biomes that change the rules of the game. +
+
+ + *Source code available on + GitHub + +
+
+ {:else if currentTab == 'Biomes'} +
+ +
+ {:else if currentTab == 'Controls'} +
+ + Left Click: Reveal tile +
+ Right Click: Flag tile +
+ Middle Click: Move view +
+ Scroll Wheel: Zoom view +
+
+ Arrow Keys: Move view +
+ Open Square Bracket '[': View zoom In +
+ Close Square Bracket ']': View zoom Out +
+
+
+ {/if} +
+
diff --git a/src/components/InfoModalBiomes.svelte b/src/components/InfoModalBiomes.svelte new file mode 100644 index 0000000..93129d1 --- /dev/null +++ b/src/components/InfoModalBiomes.svelte @@ -0,0 +1,133 @@ + + + + +
+
+ + {#if currentBiome == 'Vanilla'} +
+ Vanilla Biome Screenshot +
+

Vanilla

+
+ The standard Minesweeper rules. +
+
+
+ {:else if currentBiome == 'Chocolate'} +
+ Chocolate Biome Screenshot +
+

Chocolate

+
+ The standard Minesweeper rules. +
+ Much more mines than Vanilla biome. +
+
+
+ {:else if currentBiome == 'Waffle'} +
+ Waffle Biome Screenshot +
+

Waffle

+
+ 2x2 checkers of tiles.
+ Dark checkered sections have 3 mines.
+ Light checkered sections have 1 mine. +
+
+
+ {:else if currentBiome == 'Stroopwafel'} +
+ Stroopwafel Biome Screenshot +
+

Stroopwafel

+
+ 3x3 checkers of tiles.
+ Dark checkered sections have 8 mines.
+ Light checkered sections have 1 mine. +
+
+
+ {/if} +
+
+ + +
+
diff --git a/src/components/Modal.svelte b/src/components/Modal.svelte index d1b9819..be4fd37 100644 --- a/src/components/Modal.svelte +++ b/src/components/Modal.svelte @@ -1,44 +1,67 @@ - - + + + {#if visible} -
-
-
- {#if closable} -
-
-
- - {title} - - {#if closable} - - {/if} -
-
- -
+ diff --git a/src/components/Renderer.svelte b/src/components/Renderer.svelte new file mode 100644 index 0000000..8133e7d --- /dev/null +++ b/src/components/Renderer.svelte @@ -0,0 +1,142 @@ + + + { + keys.add(ev.key); + }} + on:keyup={ev => { + keys.delete(ev.key); + }} +/> + + { + canvas.width = width; + canvas.height = height; + renderer.cameraScale(1); + if(firstCanvasResize) { + renderer.cameraTranslate(canvas.width / 2, canvas.height / 2); + firstCanvasResize = false; + } + needsRerender = true; + }} + on:mousedown={ev => { + if(document.pointerLockElement == canvas) return; + if(ev.button == 1) { + canvas.requestPointerLock(); + ev.preventDefault(); + } else if(ev.button == 0) { + ev.preventDefault(); + const pos = renderer.cameraPos(ev.offsetX, ev.offsetY); + dispatcher('action', { type: 'reveal', pos }); + needsRerender = true; + } else if(ev.button == 2) { + ev.preventDefault(); + const pos = renderer.cameraPos(ev.offsetX, ev.offsetY); + dispatcher('action', { type: 'flag', pos }); + needsRerender = true; + } else if(ev.button == 3) { + // DEBUG: Reset tile + ev.preventDefault(); + const pos = renderer.cameraPos(ev.offsetX, ev.offsetY); + dispatcher('action', { type: 'reset', pos }); + needsRerender = true; + } + }} + on:mouseup={ev => { + if(document.pointerLockElement != canvas) return; + if(ev.button != 1) return; + document.exitPointerLock(); + }} + on:mousemove={ev => { + if(document.pointerLockElement != canvas) return; + renderer.cameraTranslate(ev.movementX, ev.movementY); + needsRerender = true; + }} + on:wheel|passive={ev => { + const scale = ev.deltaY > 0 ? 0.9 : 1.1; + if(renderer.cameraZoom != renderer.cameraScale(scale)) { + needsRerender = true; + } + }} + on:contextmenu={ev => { + ev.preventDefault(); + }} +/> diff --git a/src/lib/game/Chunk.ts b/src/lib/game/Chunk.ts index 9ac63bb..381b292 100644 --- a/src/lib/game/Chunk.ts +++ b/src/lib/game/Chunk.ts @@ -2,7 +2,7 @@ import Pako from "pako"; import { BitIO } from "../BitIO"; import { CHUNK_SIZE } from "./Constants"; -import { getTileType } from "./Generator"; +import { generateTile, getTileType } from "./Generator"; import type { World } from "./World"; import type { ValidTile } from "./tile/Tile"; @@ -65,6 +65,14 @@ export class GeneratedChunk extends Chunk { return this.tiles[chunkTileX + chunkTileY * CHUNK_SIZE]!; } + public resetTileAbsolute(tileX: number, tileY: number): void { + return this.resetTile(tileX - this.chunkX * CHUNK_SIZE, tileY - this.chunkY * CHUNK_SIZE); + } + + public resetTile(chunkTileX: number, chunkTileY: number): void { + this.tiles[chunkTileX + chunkTileY * CHUNK_SIZE] = generateTile(this.world, this.chunkX * CHUNK_SIZE + chunkTileX, this.chunkY * CHUNK_SIZE + chunkTileY); + } + public save(): ArrayBuffer { const io = new BitIO(2048); for(const tile of this.tiles) { diff --git a/src/lib/game/World.ts b/src/lib/game/World.ts index 1ce6734..04f3367 100644 --- a/src/lib/game/World.ts +++ b/src/lib/game/World.ts @@ -123,6 +123,12 @@ export class World { return false; } + public reset(x: number, y: number): void { + const chunk = this.getChunk(Math.floor(x / CHUNK_SIZE), Math.floor(y / CHUNK_SIZE)); + if(!chunk.isGenerated()) return; + chunk.resetTileAbsolute(x, y); + } + public closest0(offsetX: number, offsetY: number): { x: number, y: number } { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b47686b..c77a74d 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,4 +1,5 @@ @@ -10,3 +11,5 @@ + + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 742b361..de21921 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,10 +1,10 @@
{#if saveSlot} - world = ev.detail} on:rendererChange={ev => renderer = ev.detail} /> + {/if}
-
-
-
+
{ + layout = (width > height) ? 'vertical' : 'horizontal'; + layoutSide = (width > height) ? 'end' : 'start'; + }} + > +
+
-
-
-
- {#if world} - - Seed: {world.seed} -
- {/if} - - Frame {debugNumFrames} - {Math.round(debugFrameTime * 10) / 10}ms - -
-
-
- - -

- Infinite Minesweeper -

-

CONTROLS

- Left Click: Reveal tile -
- Right Click: Flag tile -
- Middle Click / Arrow Keys: Move view -
- Scroll Wheel: Zoom view -
- Open Bracket '[': Zoom In -
- Close Bracket ']': Zoom Out -
-
+ + + diff --git a/src/style.scss b/src/style.scss index c85d9ab..f2d1d40 100644 --- a/src/style.scss +++ b/src/style.scss @@ -5,6 +5,18 @@ +@layer utilities { + .force-overlap { + @apply grid grid-cols-1 grid-rows-1; + + & > * { + @apply w-full h-full col-start-1 col-end-1 row-start-1 row-end-1; + } + } +} + + + @font-face { font-family: 'Caveat'; src: url("https://fonts.gstatic.com/s/caveat/v18/Wnz6HAc5bAfYB2Q7ZjYY.woff2"); @@ -19,10 +31,14 @@ body { a { - @apply text-blue-500; + @apply text-blue-800; &:visited { - @apply text-purple-500; + @apply text-purple-800; } } + +*::selection { + @apply bg-purple-500 bg-opacity-70 text-white; +} diff --git a/static/biome_chocolate_screenshot.png b/static/biome_chocolate_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d41a787d2f0a0ec92d602115d0cbaa689aa90845 GIT binary patch literal 4115 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 BZe#!e literal 0 HcmV?d00001 diff --git a/static/biome_stroopwafel_screenshot.png b/static/biome_stroopwafel_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a8947817e114ff506b2a23087d660ce342429a6b GIT binary patch literal 8509 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 Q2JmnBGVi6Am+G5~2#W% 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 NjgDI#?lD-2{|DzG_pbl| literal 0 HcmV?d00001 diff --git a/static/biome_waffle_screenshot.png b/static/biome_waffle_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a824f0abdc9859dd97a67aa4c97b5176a4751288 GIT binary patch literal 5040 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 QKX-ur4yWw}+ZZSQ2lg)@bN~PV literal 0 HcmV?d00001 diff --git a/tailwind.config.js b/tailwind.config.js index 95118ec..944b2c7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,6 +6,11 @@ export default { fontFamily: { caveat: 'Caveat', segoe: [ 'Segoe UI', 'Tahoma', 'Geneva', 'Verdana', 'sans-serif' ] + }, + boxShadow: { + 'vignette-heavy': '0 0 min(100vw, 100vh) inset black', + 'vignette-medium': '0 0 min(100vw, 100vh) inset rgba(0, 0, 0, 0.7)', + 'vignette-light': '0 0 min(100vw, 100vh) inset rgba(0, 0, 0, 0.5)' } } },