diff --git a/.gitignore b/.gitignore index 497553e..4caea98 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Logs +.env logs *.log npm-debug.log* @@ -24,3 +25,6 @@ temp_dbs *.njsproj *.sln *.sw? + +deploy.sh +sqlite.db diff --git a/README.md b/README.md index 22fc07b..4bff9bd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,32 @@ -# Minesweeper +# 💣 Business Minesweeper -A simple version of minesweeper built with react in about 1h. +This is a version of minesweeper with a expanding board after each stage. This also includes a account system with match history, spectating live matches and collectables. -![image](https://github.com/user-attachments/assets/25012972-ebe8-4610-bd28-c181ce8c4e2d) +## 🚀 Local Development -## Ideas +For local development you are required to have [bun](https://bun.sh/) installed. + +```bash +# Create a .env file for token signing +echo "SECRET=SOME_RANDOM_STRING" > .env +bun install +bun run drizzle:migrate +bun dev +``` + +## 📦 Used Libraries + +- [Pixi.js](https://github.com/pixijs/pixi-react) +- [PixiViewport](https://github.com/davidfig/pixi-viewport) +- [Tanstack Query](https://github.com/TanStack/query) +- [Zod](https://github.com/colinhacks/zod) +- [Drizzle ORM](https://github.com/drizzle-team/drizzle-orm) +- [Tailwind CSS v4](https://github.com/tailwindlabs/tailwindcss) +- [React](https://github.com/facebook/react) + +## 📋 Ideas - Add global big board - Questinmark after flag - Earn points for wins +- Powerups diff --git a/backend/controller/controller.ts b/backend/controller/controller.ts index d1a70e2..1e7bc93 100644 --- a/backend/controller/controller.ts +++ b/backend/controller/controller.ts @@ -1,27 +1,30 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ServerWebSocket } from "bun"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; -import type { z } from "zod"; +import type { z, ZodType } from "zod"; interface RequestContext { user?: string; db: BunSQLiteDatabase; + ws: ServerWebSocket; } -export type Endpoint = { - validate: z.ZodType; - handler: (input: TInput, context: RequestContext) => Promise; +export type Endpoint = { + validate: TInputSchema; + handler: ( + input: z.infer, + context: RequestContext, + ) => Promise; }; -export type Request> = { - method: "POST"; - url: string; - body: z.infer; -}; - -export const createEndpoint = ( - validate: z.ZodType, +export const createEndpoint = < + TInputSchema extends ZodType, + TResponse, + TInput = z.infer, +>( + validate: TInputSchema, handler: (input: TInput, context: RequestContext) => Promise, -): Endpoint => { +): Endpoint => { return { validate, handler }; }; diff --git a/backend/controller/gameController.ts b/backend/controller/gameController.ts index 484a560..8e073e3 100644 --- a/backend/controller/gameController.ts +++ b/backend/controller/gameController.ts @@ -1,33 +1,42 @@ import { z } from "zod"; import { createController, createEndpoint } from "./controller"; -import { getGame, upsertGameState } from "../repositories/gameRepository"; import { - serverGame, - serverToClientGame, - type ServerGame, -} from "../../shared/game"; + getCurrentGame, + getGame, + getGames, + getTotalGamesPlayed, + parseGameState, + upsertGameState, +} from "../repositories/gameRepository"; +import { serverToClientGame, type ServerGame } from "../../shared/game"; import crypto from "crypto"; import { game } from "../entities/game"; import { UnauthorizedError } from "../errors/UnauthorizedError"; -import { emit } from "../events"; +import { emit, emitToWS } from "../events"; +import { serverGame } from "../../shared/gameType"; +import { pickRandom } from "../../shared/utils"; +import { addGems } from "../repositories/gemsRepository"; +import { getCollection } from "../repositories/collectionRepository"; export const gameController = createController({ getGameState: createEndpoint(z.string(), async (uuid, ctx) => { const game = await getGame(ctx.db, uuid); - const parsed = JSON.parse(game.gameState); + const parsed = parseGameState(game.gameState); const gameState = await serverGame.parseAsync(parsed); if (game.finished) return gameState; return serverToClientGame(gameState); }), - createGame: createEndpoint(z.undefined(), async (_, { user, db }) => { + createGame: createEndpoint(z.null(), async (_, { user, db }) => { if (!user) throw new UnauthorizedError("Unauthorized"); const uuid = crypto.randomUUID() as string; + const collection = await getCollection(db, user); const newGame: ServerGame = game.createGame({ uuid, user: user, mines: 2, width: 4, height: 4, + theme: pickRandom(collection.entries.filter((e) => e.selected)).id, }); upsertGameState(db, newGame); emit({ @@ -42,4 +51,145 @@ export const gameController = createController({ }); return newGame; }), + reveal: createEndpoint( + z.object({ x: z.number(), y: z.number() }), + async ({ x, y }, { db, user, ws }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const dbGame = await getCurrentGame(db, user); + const serverGame = parseGameState(dbGame.gameState); + const ts = serverGame.finished; + game.reveal(serverGame, x, y, true); + await upsertGameState(db, serverGame); + emit({ + type: "updateGame", + game: dbGame.uuid, + }); + if (ts === 0 && serverGame.finished !== 0) { + emit({ + type: "loss", + stage: serverGame.stage, + user, + time: serverGame.finished - serverGame.started, + }); + const reward = game.getRewards(serverGame); + emitToWS( + { + type: "gemsRewarded", + stage: serverGame.stage, + gems: reward, + }, + ws, + ); + await addGems(db, user, reward); + } + }, + ), + placeFlag: createEndpoint( + z.object({ x: z.number(), y: z.number() }), + async ({ x, y }, { db, user, ws }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const dbGame = await getCurrentGame(db, user); + const serverGame = parseGameState(dbGame.gameState); + const ts = serverGame.finished; + game.placeFlag(serverGame, x, y); + await upsertGameState(db, serverGame); + emit({ + type: "updateGame", + game: dbGame.uuid, + }); + if (ts === 0 && serverGame.finished !== 0) { + emit({ + type: "loss", + stage: serverGame.stage, + user, + time: serverGame.finished - serverGame.started, + }); + const reward = game.getRewards(serverGame); + emitToWS( + { + type: "gemsRewarded", + stage: serverGame.stage, + gems: reward, + }, + ws, + ); + await addGems(db, user, reward); + } + }, + ), + placeQuestionMark: createEndpoint( + z.object({ x: z.number(), y: z.number() }), + async ({ x, y }, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const dbGame = await getCurrentGame(db, user); + const serverGame = parseGameState(dbGame.gameState); + game.placeQuestionMark(serverGame, x, y); + await upsertGameState(db, serverGame); + emit({ + type: "updateGame", + game: dbGame.uuid, + }); + }, + ), + clearTile: createEndpoint( + z.object({ x: z.number(), y: z.number() }), + async ({ x, y }, { db, user, ws }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const dbGame = await getCurrentGame(db, user); + const serverGame = parseGameState(dbGame.gameState); + const ts = serverGame.finished; + game.clearTile(serverGame, x, y); + upsertGameState(db, serverGame); + emit({ + type: "updateGame", + game: dbGame.uuid, + }); + if (ts === 0 && serverGame.finished !== 0) { + emit({ + type: "loss", + stage: serverGame.stage, + user, + time: serverGame.finished - serverGame.started, + }); + const reward = game.getRewards(serverGame); + emitToWS( + { + type: "gemsRewarded", + stage: serverGame.stage, + gems: reward, + }, + ws, + ); + await addGems(db, user, reward); + } + }, + ), + getGames: createEndpoint( + z.object({ + page: z.number().default(0), + user: z.string(), + }), + async ({ page, user }, { db }) => { + const perPage = 20; + const offset = page * perPage; + const games = await getGames(db, user); + const parsedGames = games + .slice(offset, offset + perPage) + .map((game) => parseGameState(game.gameState)); + const isLastPage = games.length <= offset + perPage; + return { + data: parsedGames, + nextPage: isLastPage ? undefined : page + 1, + }; + }, + ), + getTotalGamesPlayed: createEndpoint( + z.object({ + user: z.string().optional(), + }), + async ({ user }, { db }) => { + const total = await getTotalGamesPlayed(db, user); + return total; + }, + ), }); diff --git a/backend/controller/scoreboardController.ts b/backend/controller/scoreboardController.ts new file mode 100644 index 0000000..58550ec --- /dev/null +++ b/backend/controller/scoreboardController.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; +import { createController, createEndpoint } from "./controller"; +import { getScoreBoard } from "../repositories/scoreRepository"; + +export const scoreboardController = createController({ + getScoreBoard: createEndpoint(z.number(), async (limit, { db }) => { + return (await getScoreBoard(db)).slice(0, limit); + }), +}); diff --git a/backend/controller/userController.ts b/backend/controller/userController.ts new file mode 100644 index 0000000..5414034 --- /dev/null +++ b/backend/controller/userController.ts @@ -0,0 +1,170 @@ +import { z } from "zod"; +import { createController, createEndpoint } from "./controller"; +import { + getUserCount, + getUserSettings, + loginUser, + registerUser, + upsertUserSettings, +} from "../repositories/userRepository"; +import crypto from "crypto"; +import { resetSessionUser, setSessionUser } from "../router"; +import { userSettings } from "../../shared/user-settings"; +import { UnauthorizedError } from "../errors/UnauthorizedError"; +import { getGems, removeGems } from "../repositories/gemsRepository"; +import { + getCollection, + upsertCollection, +} from "../repositories/collectionRepository"; +import { getWeight, lootboxes } from "../../shared/lootboxes"; +import { weightedPickRandom } from "../../shared/utils"; +import { emit } from "../events"; + +const secret = process.env.SECRET!; + +const signString = (payload: string) => { + return crypto.createHmac("sha256", secret).update(payload).digest("hex"); +}; + +export const userController = createController({ + getSelf: createEndpoint(z.null(), async (_, { user }) => { + return user || null; + }), + login: createEndpoint( + z.object({ username: z.string(), password: z.string() }), + async (input, { db, ws }) => { + const { name: user } = await loginUser( + db, + input.username, + input.password, + ); + const session = { user, expires: Date.now() + 1000 * 60 * 60 * 24 * 14 }; + const sig = signString(JSON.stringify(session)); + setSessionUser(ws, user); + return { token: JSON.stringify({ session, sig }) }; + }, + ), + loginWithToken: createEndpoint( + z.object({ token: z.string() }), + async (input, { ws }) => { + const { session, sig } = JSON.parse(input.token); + const { user } = session; + if (sig !== signString(JSON.stringify(session))) { + return { success: false }; + } + if (Date.now() > session.expires) { + return { success: false }; + } + setSessionUser(ws, user); + return { success: true }; + }, + ), + logout: createEndpoint(z.null(), async (_, { ws }) => { + resetSessionUser(ws); + }), + register: createEndpoint( + z.object({ + username: z + .string() + .min(3, "Username must be at least 3 characters") + .max(15, "Username cannot be longer than 15 characters"), + password: z.string().min(6, "Password must be at least 6 characters"), + }), + async (input, { db, ws }) => { + await registerUser(db, input.username, input.password); + const user = input.username; + const session = { user, expires: Date.now() + 1000 * 60 * 60 * 24 * 14 }; + const sig = signString(JSON.stringify(session)); + setSessionUser(ws, user); + return { token: JSON.stringify({ session, sig }) }; + }, + ), + getSettings: createEndpoint(z.null(), async (_, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const settings = await getUserSettings(db, user); + return settings; + }), + updateSettings: createEndpoint(userSettings, async (input, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const settings = await getUserSettings(db, user); + const newSettings = { ...settings, ...input }; + await upsertUserSettings(db, user, input); + return newSettings; + }), + getUserCount: createEndpoint(z.null(), async (_, { db }) => { + const count = await getUserCount(db); + return count; + }), + getOwnGems: createEndpoint(z.null(), async (_, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const gems = await getGems(db, user); + return gems; + }), + getOwnCollection: createEndpoint(z.null(), async (_, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const collection = await getCollection(db, user); + return collection; + }), + selectCollectionEntry: createEndpoint( + z.object({ + id: z.string(), + }), + async ({ id }, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const collection = await getCollection(db, user); + if (!collection.entries.some((e) => e.id === id)) { + throw new Error("Entry not found"); + } + for (const entry of collection.entries) { + entry.selected = entry.id === id; + } + await upsertCollection(db, user, collection); + }, + ), + addCollectionEntryToShuffle: createEndpoint( + z.object({ + id: z.string(), + }), + async ({ id }, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const collection = await getCollection(db, user); + const entry = collection.entries.find((e) => e.id === id); + if (!entry) { + throw new Error("Entry not found"); + } + entry.selected = true; + await upsertCollection(db, user, collection); + }, + ), + openLootbox: createEndpoint( + z.object({ + id: z.string(), + }), + async ({ id }, { db, user }) => { + if (!user) throw new UnauthorizedError("Unauthorized"); + const collection = await getCollection(db, user); + const lootbox = lootboxes.find((l) => l.id === id); + if (!lootbox) { + throw new Error("Lootbox not found"); + } + await removeGems(db, user, lootbox.price); + const result = weightedPickRandom(lootbox.items, (i) => + getWeight(i.rarity), + ); + console.log(result); + collection.entries.push({ + id: result.id, + aquired: Date.now(), + selected: false, + }); + await upsertCollection(db, user, collection); + emit({ + type: "lootboxPurchased", + user, + lootbox: lootbox.id, + reward: result.id, + rarity: result.rarity, + }); + }, + ), +}); diff --git a/backend/drizzle/0000_gigantic_wasp.sql b/backend/drizzle/0000_gigantic_wasp.sql new file mode 100644 index 0000000..f389c82 --- /dev/null +++ b/backend/drizzle/0000_gigantic_wasp.sql @@ -0,0 +1,24 @@ +CREATE TABLE `games` ( + `uuid` text PRIMARY KEY NOT NULL, + `user` text NOT NULL, + `gameState` blob NOT NULL, + `stage` integer NOT NULL, + `finished` integer DEFAULT 0 NOT NULL, + `timestamp` integer NOT NULL, + FOREIGN KEY (`user`) REFERENCES `users`(`name`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `users` ( + `name` text PRIMARY KEY NOT NULL, + `password` text NOT NULL +); +--> statement-breakpoint +CREATE TABLE `userSettings` ( + `user` text PRIMARY KEY NOT NULL, + `settings` text NOT NULL +); +--> statement-breakpoint +CREATE INDEX `user_idx` ON `games` (`user`);--> statement-breakpoint +CREATE INDEX `started_idx` ON `games` (`timestamp`);--> statement-breakpoint +CREATE INDEX `user_started_idx` ON `games` (`user`,`timestamp`);--> statement-breakpoint +CREATE INDEX `full_idx` ON `games` (`user`,`timestamp`,`uuid`); \ No newline at end of file diff --git a/backend/drizzle/0000_nostalgic_next_avengers.sql b/backend/drizzle/0000_nostalgic_next_avengers.sql deleted file mode 100644 index 4db83b8..0000000 --- a/backend/drizzle/0000_nostalgic_next_avengers.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE `games` ( - `uuid` text PRIMARY KEY NOT NULL, - `user` text NOT NULL, - `gameState` text NOT NULL, - `stage` integer NOT NULL, - `finished` integer DEFAULT 0 NOT NULL, - `timestamp` integer NOT NULL, - FOREIGN KEY (`user`) REFERENCES `users`(`name`) ON UPDATE no action ON DELETE no action -); ---> statement-breakpoint -CREATE TABLE `users` ( - `name` text PRIMARY KEY NOT NULL, - `password` text NOT NULL -); diff --git a/backend/drizzle/0001_breezy_martin_li.sql b/backend/drizzle/0001_breezy_martin_li.sql new file mode 100644 index 0000000..300e988 --- /dev/null +++ b/backend/drizzle/0001_breezy_martin_li.sql @@ -0,0 +1,10 @@ +CREATE TABLE `collection` ( + `user` text PRIMARY KEY NOT NULL, + `collection` blob NOT NULL +); +--> statement-breakpoint +CREATE TABLE `gems` ( + `user` text PRIMARY KEY NOT NULL, + `count` integer NOT NULL, + `totalCount` integer NOT NULL +); diff --git a/backend/drizzle/meta/0000_snapshot.json b/backend/drizzle/meta/0000_snapshot.json index 9bfef61..eb7b93b 100644 --- a/backend/drizzle/meta/0000_snapshot.json +++ b/backend/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "af6e3102-34d0-4247-84ae-14f2d3d8fa4c", + "id": "2c470a78-d3d6-49b7-910c-eb8156e58a2c", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "games": { @@ -23,7 +23,7 @@ }, "gameState": { "name": "gameState", - "type": "text", + "type": "blob", "primaryKey": false, "notNull": true, "autoincrement": false @@ -51,7 +51,39 @@ "autoincrement": false } }, - "indexes": {}, + "indexes": { + "user_idx": { + "name": "user_idx", + "columns": [ + "user" + ], + "isUnique": false + }, + "started_idx": { + "name": "started_idx", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "user_started_idx": { + "name": "user_started_idx", + "columns": [ + "user", + "timestamp" + ], + "isUnique": false + }, + "full_idx": { + "name": "full_idx", + "columns": [ + "user", + "timestamp", + "uuid" + ], + "isUnique": false + } + }, "foreignKeys": { "games_user_users_name_fk": { "name": "games_user_users_name_fk", @@ -92,6 +124,29 @@ "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {} + }, + "userSettings": { + "name": "userSettings", + "columns": { + "user": { + "name": "user", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} } }, "enums": {}, diff --git a/backend/drizzle/meta/0001_snapshot.json b/backend/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..aff49f1 --- /dev/null +++ b/backend/drizzle/meta/0001_snapshot.json @@ -0,0 +1,214 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "7347c405-254d-4a1f-9196-47b2935f1733", + "prevId": "2c470a78-d3d6-49b7-910c-eb8156e58a2c", + "tables": { + "collection": { + "name": "collection", + "columns": { + "user": { + "name": "user", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "collection": { + "name": "collection", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "games": { + "name": "games", + "columns": { + "uuid": { + "name": "uuid", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user": { + "name": "user", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "gameState": { + "name": "gameState", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "stage": { + "name": "stage", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "finished": { + "name": "finished", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_idx": { + "name": "user_idx", + "columns": [ + "user" + ], + "isUnique": false + }, + "started_idx": { + "name": "started_idx", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "user_started_idx": { + "name": "user_started_idx", + "columns": [ + "user", + "timestamp" + ], + "isUnique": false + }, + "full_idx": { + "name": "full_idx", + "columns": [ + "user", + "timestamp", + "uuid" + ], + "isUnique": false + } + }, + "foreignKeys": { + "games_user_users_name_fk": { + "name": "games_user_users_name_fk", + "tableFrom": "games", + "tableTo": "users", + "columnsFrom": [ + "user" + ], + "columnsTo": [ + "name" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "gems": { + "name": "gems", + "columns": { + "user": { + "name": "user", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "totalCount": { + "name": "totalCount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "name": { + "name": "name", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "userSettings": { + "name": "userSettings", + "columns": { + "user": { + "name": "user", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "settings": { + "name": "settings", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/backend/drizzle/meta/_journal.json b/backend/drizzle/meta/_journal.json index 0976922..a2ce872 100644 --- a/backend/drizzle/meta/_journal.json +++ b/backend/drizzle/meta/_journal.json @@ -5,8 +5,15 @@ { "idx": 0, "version": "6", - "when": 1726774158116, - "tag": "0000_nostalgic_next_avengers", + "when": 1727551167145, + "tag": "0000_gigantic_wasp", + "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1728834181598, + "tag": "0001_breezy_martin_li", "breakpoints": true } ] diff --git a/backend/entities/game.ts b/backend/entities/game.ts index 783cfb7..56a5630 100644 --- a/backend/entities/game.ts +++ b/backend/entities/game.ts @@ -1,3 +1,4 @@ +import { getValue } from "../../shared/game"; import type { ServerGame } from "../../shared/game"; interface CreateGameOptions { @@ -6,8 +7,172 @@ interface CreateGameOptions { width: number; height: number; mines: number; + theme: string; } +const isValid = (game: ServerGame, x: number, y: number) => { + const { width, height } = game; + return x >= 0 && x < width && y >= 0 && y < height; +}; + +const getNeighborFlagCount = (game: ServerGame, x: number, y: number) => { + const { isFlagged } = game; + const neighbors = [ + isFlagged[x - 1]?.[y - 1], + isFlagged[x]?.[y - 1], + isFlagged[x + 1]?.[y - 1], + isFlagged[x - 1]?.[y], + isFlagged[x + 1]?.[y], + isFlagged[x - 1]?.[y + 1], + isFlagged[x]?.[y + 1], + isFlagged[x + 1]?.[y + 1], + ]; + return neighbors.filter((n) => n).length; +}; + +const hasWon = (serverGame: ServerGame) => { + const { mines, isRevealed, isFlagged, finished, width, height } = serverGame; + if (finished) return false; + + for (let i = 0; i < width; i++) { + for (let j = 0; j < height; j++) { + if (!isRevealed[i][j] && !isFlagged[i][j]) return false; + if (mines[i][j] && !isFlagged[i][j]) return false; + if (isFlagged[i][j] && !mines[i][j]) return false; + } + } + + return true; +}; + +const expandBoard = (serverGame: ServerGame) => { + const { width, height, stage, mines, isFlagged, isRevealed, isQuestionMark } = + serverGame; + let dir = stage % 2 === 0 ? "down" : "right"; + if (stage > 13) { + dir = "down"; + } + // Expand the board by the current board size 8x8 -> 16x8 + if (dir === "down") { + const newHeight = Math.floor(Math.min(height + 7, height * 1.5)); + const newWidth = width; + const newMinesCount = Math.floor( + width * height * 0.5 * (0.2 + 0.0015 * stage), + ); + // expand mines array + const newMines = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + const newIsRevealed = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + const newIsFlagged = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + const newIsQuestionMark = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + for (let i = 0; i < newWidth; i++) { + for (let j = 0; j < newHeight; j++) { + const x = i; + const y = j; + if (mines[x]?.[y]) { + newMines[i][j] = true; + } + if (isRevealed[x]?.[y]) { + newIsRevealed[i][j] = true; + } + if (isFlagged[x]?.[y]) { + newIsFlagged[i][j] = true; + } + if (isQuestionMark[x]?.[y]) { + newIsQuestionMark[i][j] = true; + } + } + } + // generate new mines + let remainingMines = newMinesCount; + while (remainingMines > 0) { + const x = Math.floor(Math.random() * width); + const y = height + Math.floor(Math.random() * (newHeight - height)); + if (!newMines[x][y]) { + newMines[x][y] = true; + remainingMines--; + } + } + Object.assign(serverGame, { + width: newWidth, + height: newHeight, + mines: newMines, + minesCount: newMinesCount, + stage: stage + 1, + isRevealed: newIsRevealed, + isFlagged: newIsFlagged, + isQuestionMark: newIsQuestionMark, + }); + } + if (dir === "right") { + const newWidth = Math.floor(Math.min(width + 7, width * 1.5)); + const newHeight = height; + const newMinesCount = Math.floor( + width * height * 0.5 * (0.2 + 0.0015 * stage), + ); + // expand mines array + const newMines = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + const newIsRevealed = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + const newIsFlagged = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + const newIsQuestionMark = Array.from({ length: newWidth }, () => + new Array(newHeight).fill(false), + ); + for (let i = 0; i < newWidth; i++) { + for (let j = 0; j < newHeight; j++) { + const x = i; + const y = j; + if (mines[x]?.[y]) { + newMines[i][j] = true; + } + if (isRevealed[x]?.[y]) { + newIsRevealed[i][j] = true; + } + if (isFlagged[x]?.[y]) { + newIsFlagged[i][j] = true; + } + if (isQuestionMark[x]?.[y]) { + newIsQuestionMark[i][j] = true; + } + } + } + // generate new mines + let remainingMines = newMinesCount; + while (remainingMines > 0) { + const x = width + Math.floor(Math.random() * (newWidth - width)); + const y = Math.floor(Math.random() * height); + if (!newMines[x][y]) { + newMines[x][y] = true; + remainingMines--; + } + } + Object.assign(serverGame, { + width: newWidth, + height: newHeight, + mines: newMines, + minesCount: newMinesCount, + stage: stage + 1, + isRevealed: newIsRevealed, + isFlagged: newIsFlagged, + isQuestionMark: newIsQuestionMark, + }); + } + const newMinesCount = serverGame.mines.flat().filter((m) => m).length; + Object.assign(serverGame, { minesCount: newMinesCount }); +}; + export const game = { createGame: (options: CreateGameOptions): ServerGame => { const { uuid, user, width, height, mines } = options; @@ -24,6 +189,9 @@ export const game = { const isFlaggedArray = Array.from({ length: width }, () => new Array(height).fill(false), ); + const isQuestionMarkArray = Array.from({ length: width }, () => + new Array(height).fill(false), + ); let remainingMines = mines; while (remainingMines > 0) { @@ -45,9 +213,104 @@ export const game = { mines: minesArray, isRevealed: isRevealedArray, isFlagged: isFlaggedArray, + isQuestionMark: isQuestionMarkArray, stage: 1, lastClick: [-1, -1], minesCount: mines, + theme: options.theme, }; }, + reveal: (serverGame: ServerGame, x: number, y: number, initial = false) => { + const aux = ( + serverGame: ServerGame, + x: number, + y: number, + initial: boolean = false, + ) => { + const { mines, isRevealed, isFlagged, isQuestionMark, finished } = + serverGame; + if (finished) return; + if (!isValid(serverGame, x, y)) return; + if (isQuestionMark[x][y]) return; + if (isFlagged[x][y]) return; + serverGame.lastClick = [x, y]; + + if (mines[x][y]) { + serverGame.finished = Date.now(); + return; + } + + const value = getValue(serverGame.mines, x, y); + const neighborFlagCount = getNeighborFlagCount(serverGame, x, y); + + if (isRevealed[x][y] && value === neighborFlagCount && initial) { + if (!isFlagged[x - 1]?.[y]) aux(serverGame, x - 1, y); + if (!isFlagged[x - 1]?.[y - 1]) aux(serverGame, x - 1, y - 1); + if (!isFlagged[x - 1]?.[y + 1]) aux(serverGame, x - 1, y + 1); + if (!isFlagged[x]?.[y - 1]) aux(serverGame, x, y - 1); + if (!isFlagged[x]?.[y + 1]) aux(serverGame, x, y + 1); + if (!isFlagged[x + 1]?.[y - 1]) aux(serverGame, x + 1, y - 1); + if (!isFlagged[x + 1]?.[y]) aux(serverGame, x + 1, y); + if (!isFlagged[x + 1]?.[y + 1]) aux(serverGame, x + 1, y + 1); + } + + serverGame.isRevealed[x][y] = true; + + if (value === 0 && neighborFlagCount === 0) { + const revealNeighbors = (nx: number, ny: number) => { + if (isValid(serverGame, nx, ny) && !isRevealed[nx]?.[ny]) { + aux(serverGame, nx, ny); + } + }; + + revealNeighbors(x - 1, y - 1); + revealNeighbors(x, y - 1); + revealNeighbors(x + 1, y - 1); + revealNeighbors(x - 1, y); + revealNeighbors(x + 1, y); + revealNeighbors(x - 1, y + 1); + revealNeighbors(x, y + 1); + revealNeighbors(x + 1, y + 1); + } + }; + aux(serverGame, x, y, initial); + if (hasWon(serverGame)) { + expandBoard(serverGame); + } + }, + placeFlag: (serverGame: ServerGame, x: number, y: number) => { + const { isRevealed, finished } = serverGame; + if (finished) return; + if (!isValid(serverGame, x, y)) return; + if (isRevealed[x][y]) return; + serverGame.isFlagged[x][y] = true; + if (hasWon(serverGame)) { + expandBoard(serverGame); + } + }, + placeQuestionMark: (serverGame: ServerGame, x: number, y: number) => { + const { isRevealed, finished } = serverGame; + if (finished) return; + if (!isValid(serverGame, x, y)) return; + if (isRevealed[x][y]) return; + serverGame.isFlagged[x][y] = false; + serverGame.isQuestionMark[x][y] = true; + }, + clearTile: (serverGame: ServerGame, x: number, y: number) => { + const { isRevealed, finished } = serverGame; + if (finished) return; + if (!isValid(serverGame, x, y)) return; + if (isRevealed[x][y]) return; + serverGame.isFlagged[x][y] = false; + serverGame.isQuestionMark[x][y] = false; + if (hasWon(serverGame)) { + expandBoard(serverGame); + } + }, + getRewards: (serverGame: ServerGame) => { + const { finished, stage } = serverGame; + if (finished == 0) return 0; + if (stage < 2) return 0; + return Math.floor(Math.pow(2, stage * 0.93) + stage * 4 + 5); + }, }; diff --git a/backend/events.ts b/backend/events.ts index a64480d..cba963e 100644 --- a/backend/events.ts +++ b/backend/events.ts @@ -1,28 +1,5 @@ -import type { ClientGame } from "../shared/game"; - -export type EventType = "new" | "finished" | "updateGame" | "updateStage"; - -type Events = - | { - type: "new"; - user: string; - } - | { - type: "loss"; - user: string; - stage: number; - } - | { - type: "updateGame"; - game: string; - data: ClientGame; - } - | { - type: "updateStage"; - game: string; - stage: number; - started: number; - }; +import type { ServerWebSocket } from "bun"; +import type { Events } from "../shared/events"; const listeners = new Set<(event: Events) => void>(); @@ -37,3 +14,7 @@ export const off = (listener: (event: Events) => void) => { export const emit = (event: Events) => { listeners.forEach((listener) => listener(event)); }; + +export const emitToWS = (event: Events, ws: ServerWebSocket) => { + ws.send(JSON.stringify(event)); +}; diff --git a/backend/index.ts b/backend/index.ts index f5872d6..8514b17 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -1,4 +1,5 @@ -import type { ServerWebSocket } from "bun"; +import { on } from "./events"; +import { handleRequest } from "./router"; const allowCors = { "Access-Control-Allow-Origin": "*", @@ -6,7 +7,6 @@ const allowCors = { "Access-Control-Allow-Headers": "Content-Type", }; -const userName = new WeakMap, string>(); const server = Bun.serve({ async fetch(request: Request) { if (request.method === "OPTIONS") { @@ -22,10 +22,10 @@ const server = Bun.serve({ if (typeof message !== "string") { return; } - const user = userName.get(ws); try { const msg = JSON.parse(message); - console.log(msg); + console.log("Received message", msg); + handleRequest(msg, ws); } catch (e) { console.error("Faulty request", message, e); return; @@ -35,5 +35,10 @@ const server = Bun.serve({ ws.subscribe("minesweeper-global"); }, }, - port: 8076, + port: 8072, }); +on((event) => { + server.publish("minesweeper-global", JSON.stringify(event)); +}); + +console.log("Listening on port 8072"); diff --git a/backend/repositories/collectionRepository.ts b/backend/repositories/collectionRepository.ts new file mode 100644 index 0000000..bc3bd5c --- /dev/null +++ b/backend/repositories/collectionRepository.ts @@ -0,0 +1,52 @@ +import { eq } from "drizzle-orm"; +import { Collection, type CollectionType } from "../schema"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; +import { decode, encode } from "@msgpack/msgpack"; +import type { UserCollection } from "../../shared/gameType"; + +export const getCollection = async ( + db: BunSQLiteDatabase, + user: string, +): Promise => { + const res = ( + await db.select().from(Collection).where(eq(Collection.user, user)) + )[0]; + if (res) return parseCollection(res); + return { + entries: [ + { + id: "default", + aquired: Date.now(), + selected: true, + }, + ], + }; +}; + +export const upsertCollection = async ( + db: BunSQLiteDatabase, + user: string, + collection: UserCollection, +) => { + const dbCollection = await db + .select() + .from(Collection) + .where(eq(Collection.user, user)); + if (dbCollection.length > 0) { + await db + .update(Collection) + .set({ + collection: Buffer.from(encode(collection)), + }) + .where(eq(Collection.user, user)); + } else { + await db.insert(Collection).values({ + user, + collection: Buffer.from(encode(collection)), + }); + } +}; + +export const parseCollection = (collection: CollectionType) => { + return decode(collection.collection) as UserCollection; +}; diff --git a/backend/repositories/gameRepository.test.ts b/backend/repositories/gameRepository.test.ts index 76a9525..ceccfc2 100644 --- a/backend/repositories/gameRepository.test.ts +++ b/backend/repositories/gameRepository.test.ts @@ -16,7 +16,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -25,7 +25,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -44,7 +44,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -52,7 +52,7 @@ describe("GameRepository", () => { uuid: "TestUuid2", user: "TestUser", stage: 2, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started: started + 1, }); @@ -61,7 +61,7 @@ describe("GameRepository", () => { uuid: "TestUuid2", user: "TestUser", stage: 2, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started: started + 1, }); @@ -76,7 +76,7 @@ describe("GameRepository", () => { uuid, user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -92,7 +92,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -101,7 +101,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -114,7 +114,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started, }); @@ -122,7 +122,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 2, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started: started + 1, }); @@ -131,7 +131,7 @@ describe("GameRepository", () => { uuid: "TestUuid", user: "TestUser", stage: 2, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started: started + 1, }); diff --git a/backend/repositories/gameRepository.ts b/backend/repositories/gameRepository.ts index 23cf456..f626410 100644 --- a/backend/repositories/gameRepository.ts +++ b/backend/repositories/gameRepository.ts @@ -2,6 +2,7 @@ import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; import { Game, type GameType } from "../schema"; import { eq, sql, desc, and, not } from "drizzle-orm"; import type { ServerGame } from "../../shared/game"; +import { decode, encode } from "@msgpack/msgpack"; export const getGame = async (db: BunSQLiteDatabase, uuid: string) => { return (await db.select().from(Game).where(eq(Game.uuid, uuid)))[0]; @@ -12,7 +13,7 @@ export const getGames = async (db: BunSQLiteDatabase, user: string) => { .select() .from(Game) .where(and(eq(Game.user, user), not(eq(Game.finished, 0)))) - .orderBy(Game.started, sql`desc`); + .orderBy(desc(Game.started)); }; export const getCurrentGame = async (db: BunSQLiteDatabase, user: string) => { @@ -69,8 +70,31 @@ export const upsertGameState = async ( uuid, user, stage, - gameState: JSON.stringify(game), + gameState: Buffer.from(encode(game)), finished, started, }); }; + +export const getTotalGamesPlayed = async ( + db: BunSQLiteDatabase, + user?: string, +) => { + if (user) + return ( + await db + .select({ count: sql`count(*)` }) + .from(Game) + .where(and(eq(Game.user, user), not(eq(Game.finished, 0)))) + )[0].count; + return ( + await db + .select({ count: sql`count(*)` }) + .from(Game) + .where(not(eq(Game.finished, 0))) + )[0].count; +}; + +export const parseGameState = (gameState: Buffer) => { + return decode(gameState) as ServerGame; +}; diff --git a/backend/repositories/gemsRepository.ts b/backend/repositories/gemsRepository.ts new file mode 100644 index 0000000..9d94fb2 --- /dev/null +++ b/backend/repositories/gemsRepository.ts @@ -0,0 +1,41 @@ +import { eq } from "drizzle-orm"; +import { Gems } from "../schema"; +import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; + +export const getGems = async (db: BunSQLiteDatabase, user: string) => { + const res = (await db.select().from(Gems).where(eq(Gems.user, user)))[0]; + const count = res?.count ?? 0; + const totalCount = res?.totalCount ?? 0; + return { count, totalCount }; +}; + +export const addGems = async ( + db: BunSQLiteDatabase, + user: string, + gems: number, +) => { + const { count, totalCount } = await getGems(db, user); + if ((await db.select().from(Gems).where(eq(Gems.user, user))).length === 0) { + await db + .insert(Gems) + .values({ user, count: count + gems, totalCount: totalCount + gems }); + return; + } + await db + .update(Gems) + .set({ count: count + gems, totalCount: totalCount + gems }) + .where(eq(Gems.user, user)); +}; + +export const removeGems = async ( + db: BunSQLiteDatabase, + user: string, + gems: number, +) => { + const { count, totalCount } = await getGems(db, user); + if (count - gems < 0) throw new Error("Not enough gems"); + await db + .update(Gems) + .set({ count: count - gems, totalCount: totalCount - gems }) + .where(eq(Gems.user, user)); +}; diff --git a/backend/repositories/scoreRepository.test.ts b/backend/repositories/scoreRepository.test.ts index c48391d..3b1ac03 100644 --- a/backend/repositories/scoreRepository.test.ts +++ b/backend/repositories/scoreRepository.test.ts @@ -14,7 +14,7 @@ describe("ScoreRepository", () => { user: "TestUser", uuid: crypto.randomUUID(), stage: 1, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started: Date.now(), }); @@ -22,7 +22,7 @@ describe("ScoreRepository", () => { user: "TestUser", uuid: crypto.randomUUID(), stage: 10, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 1, started: Date.now(), }); @@ -30,7 +30,7 @@ describe("ScoreRepository", () => { user: "TestUser", uuid: crypto.randomUUID(), stage: 20, - gameState: "ANY", + gameState: Buffer.from("ANY"), finished: 0, started: Date.now(), }); diff --git a/backend/repositories/scoreRepository.ts b/backend/repositories/scoreRepository.ts index 64a9dfb..04d4961 100644 --- a/backend/repositories/scoreRepository.ts +++ b/backend/repositories/scoreRepository.ts @@ -3,9 +3,11 @@ import { Game } from "../schema"; import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; export const getScoreBoard = async (db: BunSQLiteDatabase) => { - return await db - .select({ stage: sql`max(${Game.stage})`, user: Game.user }) - .from(Game) - .where(not(eq(Game.finished, 0))) - .groupBy(Game.user); + return ( + await db + .select({ stage: sql`max(${Game.stage})`, user: Game.user }) + .from(Game) + .where(not(eq(Game.finished, 0))) + .groupBy(Game.user) + ).sort((a, b) => b.stage - a.stage); }; diff --git a/backend/repositories/userRepository.ts b/backend/repositories/userRepository.ts index 821480d..8269ceb 100644 --- a/backend/repositories/userRepository.ts +++ b/backend/repositories/userRepository.ts @@ -1,6 +1,10 @@ import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite"; -import { User, type UserType } from "../schema"; +import { User, UserSettings, type UserType } from "../schema"; import { eq, sql } from "drizzle-orm"; +import { + userSettings as userSettingsSchema, + type UserSettings as UserSettingsType, +} from "../../shared/user-settings"; export const registerUser = async ( db: BunSQLiteDatabase, @@ -46,3 +50,44 @@ export const getUser = async ( .where(eq(sql`lower(${User.name})`, name.toLowerCase())); return { ...user[0], password: undefined }; }; + +export const getUserSettings = async ( + db: BunSQLiteDatabase, + user: string, +): Promise => { + const userSettings = await db + .select() + .from(UserSettings) + .where(eq(UserSettings.user, user)); + const settings = userSettings[0]?.settings || "{}"; + return userSettingsSchema.parse(JSON.parse(settings)); +}; + +export const upsertUserSettings = async ( + db: BunSQLiteDatabase, + user: string, + settings: UserSettingsType, +) => { + const dbSettings = await db + .select() + .from(UserSettings) + .where(eq(UserSettings.user, user)); + if (dbSettings.length > 0) { + await db + .update(UserSettings) + .set({ + settings: JSON.stringify(settings), + }) + .where(eq(UserSettings.user, user)); + } else { + await db.insert(UserSettings).values({ + user, + settings: JSON.stringify(settings), + }); + } +}; + +export const getUserCount = async (db: BunSQLiteDatabase) => { + return (await db.select({ count: sql`count(*)` }).from(User))[0] + .count; +}; diff --git a/backend/router.ts b/backend/router.ts index 8f6dff3..04ed1ef 100644 --- a/backend/router.ts +++ b/backend/router.ts @@ -1,37 +1,71 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { ServerWebSocket } from "bun"; import type { Controller, Endpoint } from "./controller/controller"; import { gameController } from "./controller/gameController"; import { db } from "./database/db"; -import { BadRequestError } from "./errors/BadRequestError"; +import { userController } from "./controller/userController"; +import { ZodError } from "zod"; +import { scoreboardController } from "./controller/scoreboardController"; const controllers = { game: gameController, + user: userController, + scoreboard: scoreboardController, } satisfies Record>; -export const handleRequest = (message: unknown, sessionUser?: string) => { +const userName = new WeakMap, string>(); + +export const setSessionUser = (ws: ServerWebSocket, user: string) => { + userName.set(ws, user); +}; + +export const resetSessionUser = (ws: ServerWebSocket) => { + userName.delete(ws); +}; + +export const handleRequest = async ( + message: unknown, + ws: ServerWebSocket, +) => { + const sessionUser = userName.get(ws) || undefined; const ctx = { user: sessionUser, db, + ws, }; if ( !message || !(typeof message === "object") || !("type" in message) || - !("payload" in message) + !("payload" in message) || + !("id" in message) ) return; - const { type, payload } = message; + const { type, payload, id } = message; if (!(typeof type === "string")) return; const [controllerName, action] = type.split("."); if (!(controllerName in controllers)) return; - // @ts-expect-error controllers[controllerName] is a Controller - const endpoint = controllers[controllerName][action] as Endpoint; - const input = endpoint.validate.safeParse(payload); - if (input.success) { - const result = endpoint.handler(input.data, ctx); - return result; + try { + // @ts-expect-error controllers[controllerName] is a Controller + const endpoint = controllers[controllerName][action] as Endpoint; + const input = endpoint.validate.parse(payload); + console.time(action); + const result = await endpoint.handler(input, ctx); + ws.send(JSON.stringify({ id, payload: result })); + console.timeEnd(action); + return; + } catch (e) { + if (e instanceof ZodError) { + ws.send( + JSON.stringify({ id, error: e.issues[0].message, type: message.type }), + ); + } else if (e instanceof Error) { + ws.send(JSON.stringify({ id, error: e.message, type: message.type })); + } else { + ws.send(JSON.stringify({ id, error: "Bad Request", type: message.type })); + } + console.error(e); } - throw new BadRequestError(input.error.message); }; export type Routes = typeof controllers; diff --git a/backend/schema.ts b/backend/schema.ts index 77bcd14..ee89562 100644 --- a/backend/schema.ts +++ b/backend/schema.ts @@ -1,22 +1,58 @@ -import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; +import { + sqliteTable, + text, + integer, + index, + blob, +} from "drizzle-orm/sqlite-core"; export const User = sqliteTable("users", { name: text("name").primaryKey().notNull(), password: text("password").notNull(), }); -export const Game = sqliteTable("games", { - uuid: text("uuid").primaryKey().notNull(), - user: text("user") - .notNull() - .references(() => User.name), - gameState: text("gameState").notNull(), - stage: integer("stage").notNull(), - finished: integer("finished").notNull().default(0), - started: integer("timestamp").notNull(), +export const Game = sqliteTable( + "games", + { + uuid: text("uuid").primaryKey().notNull(), + user: text("user") + .notNull() + .references(() => User.name), + gameState: blob("gameState", { mode: "buffer" }).notNull(), + stage: integer("stage").notNull(), + finished: integer("finished").notNull().default(0), + started: integer("timestamp").notNull(), + }, + (table) => { + return { + userIdx: index("user_idx").on(table.user), + startedIdx: index("started_idx").on(table.started), + userStartedIdx: index("user_started_idx").on(table.user, table.started), + fullIdx: index("full_idx").on(table.user, table.started, table.uuid), + }; + }, +); + +export const UserSettings = sqliteTable("userSettings", { + user: text("user").primaryKey().notNull(), + settings: text("settings").notNull(), +}); + +export const Gems = sqliteTable("gems", { + user: text("user").primaryKey().notNull(), + count: integer("count").notNull(), + totalCount: integer("totalCount").notNull(), +}); + +export const Collection = sqliteTable("collection", { + user: text("user").primaryKey().notNull(), + collection: blob("collection", { mode: "buffer" }).notNull(), }); export type UserType = Omit & { password?: undefined; }; export type GameType = typeof Game.$inferSelect; +export type UserSettingsType = typeof UserSettings.$inferSelect; +export type GemsType = typeof Gems.$inferSelect; +export type CollectionType = typeof Collection.$inferSelect; diff --git a/bad b/bad new file mode 100644 index 0000000..1b6af5b --- /dev/null +++ b/bad @@ -0,0 +1 @@ +f7360d42-dcc1-4e1e-a90b-ef2295298872 diff --git a/bun.lockb b/bun.lockb index 7a664db..d0091f2 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/dev.ts b/dev.ts new file mode 100644 index 0000000..afda337 --- /dev/null +++ b/dev.ts @@ -0,0 +1,3 @@ +import { $ } from "bun"; + +await Promise.all([$`bun run dev:backend`, $`bun run dev:client`]); diff --git a/drizzle.config.js b/drizzle.config.js new file mode 100644 index 0000000..ed626f7 --- /dev/null +++ b/drizzle.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from "drizzle-kit"; +export default defineConfig({ + dialect: "sqlite", + schema: "./backend/schema.ts", + out: "./backend/drizzle", + dbCredentials: { + url: "file:./sqlite.db", + }, +}); diff --git a/eslint.config.js b/eslint.config.js index 092408a..0d9d41f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,28 +1,39 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; +import pluginReact from "eslint-plugin-react"; +import reactHooks from "eslint-plugin-react-hooks"; -export default tseslint.config( - { ignores: ['dist'] }, +export default [ { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + ignores: ["dist/", "node_modules/"], + }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + { + ...pluginReact.configs.flat.recommended, + settings: { + react: { + version: "detect", + }, }, }, -) + { + plugins: { "react-hooks": reactHooks }, + rules: reactHooks.configs.recommended.rules, + }, + { + files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + rules: { + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "react/display-name": "off", + }, + }, + { + languageOptions: { + ...pluginReact.configs.flat.recommended.languageOptions, + globals: { ...globals.browser, ...globals.node }, + }, + }, +]; diff --git a/index.html b/index.html index 944032c..cb1b53f 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,10 @@ - + + + Minesweeper diff --git a/index.ts b/index.ts deleted file mode 100644 index f67b2c6..0000000 --- a/index.ts +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello via Bun!"); \ No newline at end of file diff --git a/package.json b/package.json index c461e0b..fd22354 100644 --- a/package.json +++ b/package.json @@ -4,39 +4,66 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "bun run dev.ts", + "dev:backend": "bun run backend/index.ts --watch --hot", + "dev:client": "vite", "build": "tsc -b && vite build", - "lint": "eslint .", + "lint": "eslint", "preview": "vite preview", - "drizzle:schema": "drizzle-kit generate --dialect sqlite --schema ./backend/schema.ts --out ./backend/drizzle", - "drizzle:migrate": "bun run backend/migrate.ts" + "drizzle:schema": "drizzle-kit generate", + "drizzle:migrate": "bun run backend/migrate.ts", + "nukedb": "rm sqlite.db && bun run backend/migrate.ts" }, "dependencies": { + "@msgpack/msgpack": "^3.0.0-beta2", + "@pixi/canvas-display": "^7.4.2", + "@pixi/canvas-renderer": "^7.4.2", + "@pixi/react": "^7.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-switch": "^1.1.1", + "@tanstack/react-query": "^5.59.11", + "@tanstack/react-query-devtools": "^5.59.11", + "@tsparticles/engine": "^3.5.0", + "@tsparticles/preset-sea-anemone": "^3.1.0", + "@tsparticles/react": "^3.0.0", + "@tsparticles/slim": "^3.5.0", "@uidotdev/usehooks": "^2.4.1", - "drizzle-orm": "^0.33.0", - "lucide-react": "^0.441.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "drizzle-orm": "0.33.0", + "framer-motion": "^11.11.8", + "jotai": "^2.10.0", + "lucide-react": "^0.452.0", + "pixi-viewport": "^5.0.3", + "pixi.js": "^7.0.0", + "pixi.js-legacy": "^7.4.2", "react": "^18.3.1", "react-confetti-boom": "^1.0.0", "react-dom": "^18.3.1", - "react-hot-toast": "^2.4.1", + "tailwind-merge": "^2.5.3", "use-sound": "^4.0.3", - "zod": "^3.23.8", - "zustand": "^4.5.5" + "wouter": "^3.3.5", + "zod": "^3.23.8" }, "devDependencies": { - "@eslint/js": "^9.10.0", + "vite-imagetools": "^7.0.4", + "tailwindcss": "^4.0.0-alpha.26", + "@eslint/compat": "^1.2.0", + "@eslint/js": "^9.12.0", "@types/bun": "latest", - "@types/react": "^18.3.8", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react-swc": "^3.7.0", - "drizzle-kit": "^0.24.2", - "eslint": "^9.10.0", - "eslint-plugin-react-hooks": "^5.1.0-rc.0", - "eslint-plugin-react-refresh": "^0.4.12", - "globals": "^15.9.0", - "typescript": "^5.6.2", - "typescript-eslint": "^8.6.0", - "vite": "^5.4.6" - }, - "module": "index.ts" + "@types/react": "^18.3.11", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react-swc": "^3.7.1", + "drizzle-kit": "0.24.2", + "eslint": "^9.12.0", + "eslint-plugin-react": "^7.37.1", + "eslint-plugin-react-hooks": "5.0.0", + "globals": "^15.11.0", + "typescript": "^5.6.3", + "typescript-eslint": "^8.8.1", + "vite": "^5.4.8", + "@tailwindcss/vite": "next" + } } diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/shared/events.ts b/shared/events.ts new file mode 100644 index 0000000..68361ad --- /dev/null +++ b/shared/events.ts @@ -0,0 +1,36 @@ +import type { Rarity } from "../shared/lootboxes"; +export type EventType = "new" | "finished" | "updateGame" | "updateStage"; + +export type Events = + | { + type: "new"; + user: string; + } + | { + type: "loss"; + user: string; + stage: number; + time: number; + } + | { + type: "updateGame"; + game: string; + } + | { + type: "updateStage"; + game: string; + stage: number; + started: number; + } + | { + type: "gemsRewarded"; + stage: number; + gems: number; + } + | { + type: "lootboxPurchased"; + lootbox: string; + reward: string; + user: string; + rarity: Rarity; + }; diff --git a/shared/game.test.ts b/shared/game.test.ts index 5609c8f..105ec4b 100644 --- a/shared/game.test.ts +++ b/shared/game.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test"; -import { getValue, serverToClientGame } from "./game"; +import { getValue, ServerGame, serverToClientGame } from "./game"; describe("Game", () => { it("should get value", () => { @@ -16,7 +16,8 @@ describe("Game", () => { }); it("should convert server to client game", () => { - const serverGame = { + const serverGame: ServerGame = { + theme: "default", mines: [ [false, false, true, true, true], [true, false, true, false, true], @@ -36,15 +37,23 @@ describe("Game", () => { [false, false, true, false, true], [true, false, false, false, false], ], - isGameOver: false, started: 1679599200000, finished: 0, lastClick: [0, 0] satisfies [number, number], uuid: "C270D7CD-AF97-42CE-A6C9-CB765102CA17", width: 5, height: 4, + user: "TestUser", + stage: 1, + isQuestionMark: [ + [false, false, true, false, true], + [true, false, true, false, true], + [false, false, true, false, true], + [false, false, false, false, false], + ], }; expect(serverToClientGame(serverGame)).toEqual({ + theme: "default", minesCount: 4, isRevealed: [ [false, false, true, false, true], @@ -69,6 +78,14 @@ describe("Game", () => { uuid: "C270D7CD-AF97-42CE-A6C9-CB765102CA17", width: 5, height: 4, + user: "TestUser", + stage: 1, + isQuestionMark: [ + [false, false, true, false, true], + [true, false, true, false, true], + [false, false, true, false, true], + [false, false, false, false, false], + ], }); }); }); diff --git a/shared/game.ts b/shared/game.ts index 335aa7c..dec4b1b 100644 --- a/shared/game.ts +++ b/shared/game.ts @@ -1,36 +1,10 @@ -import { z } from "zod"; +import type { ServerGame, ClientGame } from "./gameType"; +export type { ServerGame, ClientGame } from "./gameType"; -export const clientGame = z.object({ - user: z.string(), - uuid: z.string(), - width: z.number(), - height: z.number(), - isRevealed: z.array(z.array(z.boolean())), - isFlagged: z.array(z.array(z.boolean())), - values: z.array(z.array(z.number())), - minesCount: z.number(), - lastClick: z.tuple([z.number(), z.number()]), - started: z.number(), - stage: z.number(), -}); - -export const serverGame = z.object({ - user: z.string(), - uuid: z.string(), - width: z.number(), - height: z.number(), - isRevealed: z.array(z.array(z.boolean())), - isFlagged: z.array(z.array(z.boolean())), - mines: z.array(z.array(z.boolean())), - minesCount: z.number(), - lastClick: z.tuple([z.number(), z.number()]), - started: z.number(), - finished: z.number().default(0), - stage: z.number(), -}); - -export type ClientGame = z.infer; -export type ServerGame = z.infer; +export const isServerGame = (game: ServerGame | ClientGame) => "mines" in game; +export const isClientGame = ( + game: ServerGame | ClientGame, +): game is ClientGame => !("mines" in game); export const getValue = (mines: boolean[][], x: number, y: number) => { const neighbors = [ @@ -54,6 +28,7 @@ export const serverToClientGame = (game: ServerGame): ClientGame => { height: game.height, isRevealed: game.isRevealed, isFlagged: game.isFlagged, + isQuestionMark: game.isQuestionMark, minesCount: game.minesCount, values: game.mines.map((_, i) => game.mines[0].map((_, j) => { @@ -64,5 +39,6 @@ export const serverToClientGame = (game: ServerGame): ClientGame => { lastClick: game.lastClick, started: game.started, stage: game.stage, + theme: game.theme, }; }; diff --git a/shared/gameType.ts b/shared/gameType.ts new file mode 100644 index 0000000..b7266ba --- /dev/null +++ b/shared/gameType.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; + +export const clientGame = z.object({ + user: z.string(), + uuid: z.string(), + width: z.number(), + height: z.number(), + isRevealed: z.array(z.array(z.boolean())), + isFlagged: z.array(z.array(z.boolean())), + isQuestionMark: z.array(z.array(z.boolean())), + values: z.array(z.array(z.number())), + minesCount: z.number(), + lastClick: z.tuple([z.number(), z.number()]), + started: z.number(), + stage: z.number(), + theme: z.string().default("default"), +}); + +export const serverGame = z.object({ + user: z.string(), + uuid: z.string(), + width: z.number(), + height: z.number(), + isRevealed: z.array(z.array(z.boolean())), + isFlagged: z.array(z.array(z.boolean())), + isQuestionMark: z.array(z.array(z.boolean())), + mines: z.array(z.array(z.boolean())), + minesCount: z.number(), + lastClick: z.tuple([z.number(), z.number()]), + started: z.number(), + finished: z.number().default(0), + stage: z.number(), + theme: z.string().default("default"), +}); + +export type ClientGame = z.infer; +export type ServerGame = z.infer; + +export interface UserCollectionEntry { + id: string; + aquired: number; + selected: boolean; +} + +export interface UserCollection { + entries: UserCollectionEntry[]; +} diff --git a/shared/lootboxes.ts b/shared/lootboxes.ts new file mode 100644 index 0000000..f24278f --- /dev/null +++ b/shared/lootboxes.ts @@ -0,0 +1,211 @@ +import type { themes } from "../src/themes"; +import lootbox1 from "../src/assets/illustrations/lootbox1.png?w=360&inline"; + +export const rarities = [ + { + name: "Common", + id: "common", + weight: 1, + }, + { + name: "Uncommon", + id: "uncommon", + weight: 0.5, + }, + { + name: "Rare", + id: "rare", + weight: 0.25, + }, + { + name: "Legendary", + id: "legendary", + weight: 0.1, + }, +] as const; + +export const getWeight = (rarity: Rarity) => + rarities.find((r) => r.id === rarity)?.weight ?? 0; + +export type Rarity = (typeof rarities)[number]["id"]; +type ThemeId = (typeof themes)[number]["id"]; + +interface Lootbox { + name: string; + id: string; + price: number; + priceText: string; + image: string; + items: { + id: ThemeId; + rarity: Rarity; + }[]; +} + +export const series1: Lootbox = { + name: "Series 1", + id: "series1", + price: 5000, + priceText: "5.000", + image: lootbox1, + items: [ + { + id: "basic", + rarity: "common", + }, + { + id: "black-and-white", + rarity: "common", + }, + { + id: "blue", + rarity: "common", + }, + { + id: "green", + rarity: "common", + }, + { + id: "orange", + rarity: "common", + }, + { + id: "pink", + rarity: "common", + }, + { + id: "purple", + rarity: "common", + }, + { + id: "red", + rarity: "common", + }, + { + id: "turquoise", + rarity: "common", + }, + { + id: "yellow", + rarity: "common", + }, + { + id: "nautical", + rarity: "uncommon", + }, + { + id: "up-in-smoke", + rarity: "uncommon", + }, + { + id: "shadow-warrior", + rarity: "uncommon", + }, + { + id: "crimson", + rarity: "uncommon", + }, + { + id: "romance", + rarity: "uncommon", + }, + { + id: "flowers", + rarity: "rare", + }, + { + id: "dinos", + rarity: "rare", + }, + { + id: "cats", + rarity: "rare", + }, + { + id: "mine-dogs", + rarity: "rare", + }, + { + id: "tron-blue", + rarity: "rare", + }, + { + id: "tron-orange", + rarity: "rare", + }, + { + id: "circuit", + rarity: "rare", + }, + { + id: "circuit-binary", + rarity: "rare", + }, + { + id: "farm", + rarity: "rare", + }, + { + id: "halli-galli", + rarity: "rare", + }, + { + id: "insects", + rarity: "rare", + }, + { + id: "poop", + rarity: "rare", + }, + { + id: "underwater", + rarity: "rare", + }, + { + id: "retro-wave", + rarity: "legendary", + }, + { + id: "elden-ring", + rarity: "legendary", + }, + { + id: "janitor-tresh", + rarity: "legendary", + }, + { + id: "teemo", + rarity: "legendary", + }, + { + id: "ziggs", + rarity: "legendary", + }, + { + id: "minecraft-nether", + rarity: "legendary", + }, + { + id: "minecraft-overworld", + rarity: "legendary", + }, + { + id: "techies-dire", + rarity: "legendary", + }, + { + id: "techies-radiant", + rarity: "legendary", + }, + { + id: "isaac", + rarity: "legendary", + }, + { + id: "mlg", + rarity: "legendary", + }, + ], +}; + +export const lootboxes = [series1]; diff --git a/shared/testBoard.ts b/shared/testBoard.ts new file mode 100644 index 0000000..8f49804 --- /dev/null +++ b/shared/testBoard.ts @@ -0,0 +1,42 @@ +import { ServerGame } from "./gameType"; + +const rotate = (arr: boolean[][]) => { + return arr[0].map((_, colIndex) => arr.map((row) => row[colIndex])); +}; + +export const testBoard: (theme: string) => ServerGame = (theme: string) => ({ + user: "TestUser", + uuid: "C270D7CD-AF97-42CE-A6C9-CB765102CA17", + width: 11, + height: 4, + isRevealed: rotate([ + [false, false, false, false, false, ...Array(6).fill(true)], + [...Array(11).fill(true)], + [...Array(11).fill(true)], + [...Array(6).fill(true), ...Array(5).fill(false)], + ]), + isFlagged: rotate([ + [true, ...Array(10).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + ]), + finished: 1, + started: 1, + stage: 420, + lastClick: [2, 2], + mines: rotate([ + [false, false, false, false, false, ...Array(6).fill(true)], + [...Array(8).fill(false), true, false, true], + [false, false, ...Array(9).fill(true)], + [...Array(11).fill(false)], + ]), + minesCount: 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8, + isQuestionMark: rotate([ + [false, true, ...Array(9).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + [...Array(11).fill(false)], + ]), + theme, +}); diff --git a/shared/time.ts b/shared/time.ts new file mode 100644 index 0000000..220b64b --- /dev/null +++ b/shared/time.ts @@ -0,0 +1,60 @@ +export const formatTimeSpan = (timespan: number) => { + const days = Math.floor(timespan / (1000 * 60 * 60 * 24)); + const hours = Math.floor( + (timespan % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), + ); + const minutes = Math.floor((timespan % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((timespan % (1000 * 60)) / 1000); + + const result = []; + + if (days > 0) { + result.push(`${days}d`); + } + + if (hours > 0) { + result.push(`${hours}h`); + } + + if (minutes > 0) { + result.push(`${minutes}m`); + } + + if (seconds > 0) { + result.push(`${seconds}s`); + } + + if (result.length === 0) { + return timespan + "ms"; + } + + return result.join(" "); +}; + +const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); + +export const formatRelativeTime = (date: number) => { + const now = Date.now(); + const diff = date - now; + const days = Math.ceil(diff / (1000 * 60 * 60 * 24)); + const hours = Math.ceil((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.ceil((diff % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((diff % (1000 * 60)) / 1000); + + if (days <= -1) { + return rtf.format(days, "day"); + } + + if (hours <= -1) { + return rtf.format(hours, "hour"); + } + + if (minutes <= -1) { + return rtf.format(minutes, "minute"); + } + + if (seconds <= -1) { + return rtf.format(seconds, "second"); + } + return "just now"; +}; diff --git a/shared/user-settings.ts b/shared/user-settings.ts new file mode 100644 index 0000000..6f114e9 --- /dev/null +++ b/shared/user-settings.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const userSettings = z.object({ + placeQuestionMark: z.boolean().default(false), + longPressOnDesktop: z.boolean().default(false), +}); + +export type UserSettings = z.infer; +export type UserSettingsInput = z.input; diff --git a/shared/utils.ts b/shared/utils.ts new file mode 100644 index 0000000..ac48d73 --- /dev/null +++ b/shared/utils.ts @@ -0,0 +1,29 @@ +export const pickRandom = (arr: T[]) => { + const index = Math.floor(Math.random() * arr.length); + return arr[index]; +}; + +export const hashStr = (str: string) => { + return [...str].reduce( + (hash, c) => (Math.imul(31, hash) + c.charCodeAt(0)) | 0, + 0, + ); +}; + +export const weightedPickRandom = ( + arr: T[], + getWeight: (item: T) => number = () => 1, + getRandom: (tw: number) => number = (totalWeight) => + Math.random() * totalWeight, +): T => { + const totalWeight = arr.reduce((acc, cur) => acc + getWeight(cur), 0); + const random = getRandom(totalWeight); + let currentWeight = 0; + for (const entry of arr) { + currentWeight += getWeight(entry); + if (random < currentWeight) { + return entry; + } + } + return arr[arr.length - 1]; +}; diff --git a/sqlite.db b/sqlite.db deleted file mode 100644 index e548cfa..0000000 Binary files a/sqlite.db and /dev/null differ diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 805047a..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { Button } from "./Button"; -import Timer from "./Timer"; -import explosion from "./sound/explosion.mp3"; -import useGameStore from "./GameState"; -import { useEffect, useState } from "react"; -import useSound from "use-sound"; -import { loseGame } from "./ws"; -import toast, { useToasterStore } from "react-hot-toast"; - -interface Score { - user: string; - stage: number; -} - -function useMaxToasts(max: number) { - const { toasts } = useToasterStore(); - - useEffect(() => { - toasts - .filter((t) => t.visible) // Only consider visible toasts - .filter((_, i) => i >= max) // Is toast index over limit? - .forEach((t) => toast.dismiss(t.id)); // Dismiss – Use toast.remove(t.id) for no exit animation - }, [toasts, max]); -} - -function App() { - const game = useGameStore(); - const [scores, setScores] = useState([]); - const [playSound] = useSound(explosion, { - volume: 0.5, - }); - - useEffect(() => { - if (game.isGameOver) { - playSound(); - loseGame(game.name, game.stage); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [game.isGameOver]); - useEffect(() => { - game.resetGame(4, 4, 2); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - fetch("https://mb.gordon.business") - .then((res) => res.json()) - .then((data) => { - setScores(data); - }); - const i = setInterval(() => { - fetch("https://mb.gordon.business") - .then((res) => res.json()) - .then((data) => { - setScores(data); - }); - }, 2000); - return () => clearInterval(i); - }, []); - - useMaxToasts(5); - - return ( -
- {import.meta.env.DEV && ( - - )} -
-
-

- Minesweeper Endless{" "} - -

-

- Name:{" "} - game.setName(e.target.value)} - /> -

-

- Feed:{" "} - -

-
-
- {scores.slice(0, 10).map((score) => ( -

- {score.user} - {score.stage} -

- ))} -
-
-
-
- -
- {game.mines[0].map((_, y) => - game.mines.map((_, x) => ( -
-
-
-
-
Version: 1.1.6
-
-          Made by MasterGordon -{" "}
-          
-            Source Code
-          
-        
-
-
- ); -} - -export default App; diff --git a/src/Button.tsx b/src/Button.tsx deleted file mode 100644 index 0c73c6f..0000000 --- a/src/Button.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { ReactNode, useRef } from "react"; -import { Bomb, Flag } from "lucide-react"; -import useGameStore from "./GameState"; -import { useLongPress } from "@uidotdev/usehooks"; - -interface ButtonProps { - x: number; - y: number; -} - -// eslint-disable-next-line react-refresh/only-export-components -export const colorMap: Record = { - "1": "#049494", - "2": "#8c9440", - "3": "#cc6666", - "4": "#b294bb", - "5": "#f7c530", - "6": "#81a2be", - "7": "#707880", - "8": "#b5bd68", -}; - -export const Button = ({ x, y }: ButtonProps) => { - const { - isRevealed, - isFlagged, - isMine, - getValue, - reveal, - flag, - getNeighborFlags, - isGameOver, - getHasWon, - } = useGameStore(); - - let content: ReactNode = ""; - - if (isRevealed[x][y]) { - content = isMine(x, y) ? : getValue(x, y).toString(); - } - - const attrs = useLongPress( - () => { - if (isRevealed[x][y]) return; - flag(x, y); - }, - { - threshold: 400, - }, - ); - - if (isFlagged[x][y]) { - content = ; - } - if (content === "0") content = ""; - if ( - import.meta.env.DEV && - window.location.href.includes("xray") && - isMine(x, y) && - !isFlagged[x][y] - ) - content = ; - - const touchStart = useRef(0); - - return ( -
0 ? "1.75rem" : undefined, - cursor: isRevealed[x][y] ? "default" : "pointer", - }} - onMouseDown={() => { - touchStart.current = Date.now(); - }} - onMouseUp={(e) => { - if (Date.now() - touchStart.current > 400 && !isRevealed[x][y]) { - flag(x, y); - return; - } - if (getHasWon() || isGameOver) { - return; - } - if (e.button === 0) { - // Left click - if (isFlagged[x][y]) return; - if (!isRevealed[x][y]) { - reveal(x, y); - } else { - const neighborFlagCount = getNeighborFlags(x, y).filter( - (n) => n, - ).length; - const value = getValue(x, y); - if (neighborFlagCount === value) { - if (!isFlagged[x - 1]?.[y]) if (reveal(x - 1, y)) return; - if (!isFlagged[x - 1]?.[y - 1]) if (reveal(x - 1, y - 1)) return; - if (!isFlagged[x - 1]?.[y + 1]) if (reveal(x - 1, y + 1)) return; - if (!isFlagged[x]?.[y - 1]) if (reveal(x, y - 1)) return; - if (!isFlagged[x]?.[y + 1]) if (reveal(x, y + 1)) return; - if (!isFlagged[x + 1]?.[y - 1]) if (reveal(x + 1, y - 1)) return; - if (!isFlagged[x + 1]?.[y]) if (reveal(x + 1, y)) return; - if (!isFlagged[x + 1]?.[y + 1]) if (reveal(x + 1, y + 1)) return; - } - } - } else if (e.button === 2 && !isRevealed[x][y]) { - flag(x, y); - } - e.preventDefault(); - }} - > - {content} -
- ); -}; diff --git a/src/Game.ts b/src/Game.ts deleted file mode 100644 index 32043d1..0000000 --- a/src/Game.ts +++ /dev/null @@ -1,144 +0,0 @@ -export class Game { - mines: boolean[][] = []; - minesCount: number = 0; - isRevealed: boolean[][] = []; - isFlagged: boolean[][] = []; - isGameOver: boolean = false; - startTime: number = Date.now(); - - constructor(width: number, height: number, mines: number) { - if (mines > width * height) { - throw new Error("Too many mines"); - } - this.minesCount = mines; - for (let i = 0; i < width; i++) { - this.mines.push(new Array(height).fill(false)); - this.isRevealed.push(new Array(height).fill(false)); - this.isFlagged.push(new Array(height).fill(false)); - } - while (mines > 0) { - const x = Math.floor(Math.random() * width); - const y = Math.floor(Math.random() * height); - if (!this.mines[x][y]) { - this.mines[x][y] = true; - mines--; - } - } - } - - getWidth() { - return this.mines.length; - } - - getHeight() { - return this.mines[0].length; - } - - isMine(x: number, y: number) { - return this.mines[x][y]; - } - - flag(x: number, y: number) { - this.isFlagged[x][y] = !this.isFlagged[x][y]; - } - - isValid(x: number, y: number) { - return x >= 0 && x < this.getWidth() && y >= 0 && y < this.getHeight(); - } - - reveal(x: number, y: number) { - if (!this.isValid(x, y)) return; - this.isRevealed[x][y] = true; - if (this.isMine(x, y)) { - this.isGameOver = true; - return; - } - const value = this.getValue(x, y); - if (value === 0) { - if (this.isValid(x - 1, y - 1) && !this.isRevealed[x - 1]?.[y - 1]) - this.reveal(x - 1, y - 1); - if (this.isValid(x, y - 1) && !this.isRevealed[x]?.[y - 1]) - this.reveal(x, y - 1); - if (this.isValid(x + 1, y - 1) && !this.isRevealed[x + 1]?.[y - 1]) - this.reveal(x + 1, y - 1); - if (this.isValid(x - 1, y) && !this.isRevealed[x - 1]?.[y]) - this.reveal(x - 1, y); - if (this.isValid(x + 1, y) && !this.isRevealed[x + 1]?.[y]) - this.reveal(x + 1, y); - if (this.isValid(x - 1, y + 1) && !this.isRevealed[x - 1]?.[y + 1]) - this.reveal(x - 1, y + 1); - if (this.isValid(x, y + 1) && !this.isRevealed[x]?.[y + 1]) - this.reveal(x, y + 1); - if (this.isValid(x + 1, y + 1) && !this.isRevealed[x + 1]?.[y + 1]) - this.reveal(x + 1, y + 1); - } - } - - getHasWon() { - if (this.isGameOver) { - return false; - } - for (let i = 0; i < this.getWidth(); i++) { - for (let j = 0; j < this.getHeight(); j++) { - if (!this.isRevealed[i][j] && !this.isFlagged[i][j]) { - return false; - } - if (this.isMine(i, j) && !this.isFlagged[i][j]) { - return false; - } - } - } - return true; - } - - getMinesLeft() { - return this.minesCount - this.isFlagged.flat().filter((m) => m).length; - } - - getNeighborFlags(x: number, y: number) { - const neighbors = [ - this.isFlagged[x - 1]?.[y - 1], - this.isFlagged[x]?.[y - 1], - this.isFlagged[x + 1]?.[y - 1], - this.isFlagged[x - 1]?.[y], - this.isFlagged[x + 1]?.[y], - this.isFlagged[x - 1]?.[y + 1], - this.isFlagged[x]?.[y + 1], - this.isFlagged[x + 1]?.[y + 1], - ]; - return neighbors; - } - - getNeighborMines(x: number, y: number) { - const neighbors = [ - this.mines[x - 1]?.[y - 1], - this.mines[x]?.[y - 1], - this.mines[x + 1]?.[y - 1], - this.mines[x - 1]?.[y], - this.mines[x + 1]?.[y], - this.mines[x - 1]?.[y + 1], - this.mines[x]?.[y + 1], - this.mines[x + 1]?.[y + 1], - ]; - return neighbors; - } - - getValue(x: number, y: number) { - const neighbors = this.getNeighborMines(x, y); - const mines = neighbors.filter((n) => n).length; - return mines; - } - - quickStart() { - for (let i = 0; i < this.getWidth(); i++) { - for (let j = 0; j < this.getHeight(); j++) { - const value = this.getValue(i, j); - const isMine = this.isMine(i, j); - if (value === 0 && !isMine) { - this.reveal(i, j); - return; - } - } - } - } -} diff --git a/src/GameState.ts b/src/GameState.ts deleted file mode 100644 index 6a6d132..0000000 --- a/src/GameState.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { create } from "zustand"; -import { newGame } from "./ws"; - -interface GameState { - showFeed: boolean; - mines: boolean[][]; - minesCount: number; - isRevealed: boolean[][]; - isFlagged: boolean[][]; - isGameOver: boolean; - startTime: number; - width: number; - height: number; - stage: number; - name: string; - - flag: (x: number, y: number) => void; - reveal: (x: number, y: number) => boolean; - getValue: (x: number, y: number) => number; - getHasWon: () => boolean; - getMinesLeft: () => number; - quickStart: () => void; - isValid: (x: number, y: number) => boolean; - resetGame: (width: number, height: number, mines: number) => void; - isMine: (x: number, y: number) => boolean; - getNeighborMines: (x: number, y: number) => boolean[]; - getNeighborFlags: (x: number, y: number) => boolean[]; - getWidth: () => number; - getHeight: () => number; - isTouched: () => boolean; - triggerPostGame: () => boolean; - expandBoard: () => void; - setName: (name: string) => void; - setShowFeed: (showFeed: boolean) => void; -} - -const useGameStore = create((set, get) => ({ - mines: [[]], - minesCount: 0, - isRevealed: [[]], - isFlagged: [[]], - isGameOver: false, - startTime: Date.now(), - width: 0, - height: 0, - stage: 1, - name: localStorage.getItem("name") || "No Name", - showFeed: !localStorage.getItem("showFeed") - ? true - : localStorage.getItem("showFeed") === "true", - - flag: (x, y) => { - set((state) => { - const isFlagged = [...state.isFlagged]; - isFlagged[x][y] = !isFlagged[x][y]; - return { isFlagged }; - }); - const { triggerPostGame } = get(); - triggerPostGame(); - }, - - reveal: (x, y) => { - const { mines, isRevealed, isGameOver, getValue, triggerPostGame } = get(); - if (isGameOver || !get().isValid(x, y) || isRevealed[x][y]) return false; - - const newRevealed = [...isRevealed]; - newRevealed[x][y] = true; - - if (mines[x][y]) { - set({ isGameOver: true, isRevealed: newRevealed }); - return true; - } else { - set({ isRevealed: newRevealed }); - const value = getValue(x, y); - const neighborFlagCount = get() - .getNeighborFlags(x, y) - .filter((n) => n).length; - if (value === 0 && neighborFlagCount === 0) { - const revealNeighbors = (nx: number, ny: number) => { - if (get().isValid(nx, ny) && !isRevealed[nx]?.[ny]) { - get().reveal(nx, ny); - } - }; - - revealNeighbors(x - 1, y - 1); - revealNeighbors(x, y - 1); - revealNeighbors(x + 1, y - 1); - revealNeighbors(x - 1, y); - revealNeighbors(x + 1, y); - revealNeighbors(x - 1, y + 1); - revealNeighbors(x, y + 1); - revealNeighbors(x + 1, y + 1); - } - } - return triggerPostGame(); - }, - - getValue: (x, y) => { - const { mines } = get(); - const neighbors = [ - mines[x - 1]?.[y - 1], - mines[x]?.[y - 1], - mines[x + 1]?.[y - 1], - mines[x - 1]?.[y], - mines[x + 1]?.[y], - mines[x - 1]?.[y + 1], - mines[x]?.[y + 1], - mines[x + 1]?.[y + 1], - ]; - return neighbors.filter((n) => n).length; - }, - - getHasWon: () => { - const { mines, isRevealed, isFlagged, isGameOver, width, height } = get(); - if (isGameOver) return false; - - for (let i = 0; i < width; i++) { - for (let j = 0; j < height; j++) { - if (!isRevealed[i][j] && !isFlagged[i][j]) return false; - if (mines[i][j] && !isFlagged[i][j]) return false; - if (isFlagged[i][j] && !mines[i][j]) return false; - } - } - - return true; - }, - - getMinesLeft: () => { - const { minesCount, isFlagged } = get(); - return minesCount - isFlagged.flat().filter((flag) => flag).length; - }, - - quickStart: () => { - const { width, height, mines, getValue, reveal } = get(); - for (let i = 0; i < width; i++) { - for (let j = 0; j < height; j++) { - const value = getValue(i, j); - if (value === 0 && !mines[i][j]) { - reveal(i, j); - return; - } - } - } - }, - isValid: (x: number, y: number) => { - const { width, height } = get(); - return x >= 0 && x < width && y >= 0 && y < height; - }, - resetGame: (width: number, height: number, mines: number) => { - const { name } = get(); - newGame(name); - if (mines > width * height) { - throw new Error("Too many mines"); - } - - const minesArray = Array.from({ length: width }, () => - new Array(height).fill(false), - ); - const isRevealedArray = Array.from({ length: width }, () => - new Array(height).fill(false), - ); - const isFlaggedArray = Array.from({ length: width }, () => - new Array(height).fill(false), - ); - - let remainingMines = mines; - while (remainingMines > 0) { - const x = Math.floor(Math.random() * width); - const y = Math.floor(Math.random() * height); - if (!minesArray[x][y]) { - minesArray[x][y] = true; - remainingMines--; - } - } - - set({ - width, - height, - mines: minesArray, - isRevealed: isRevealedArray, - isFlagged: isFlaggedArray, - minesCount: mines, - isGameOver: false, - startTime: Date.now(), - stage: 1, - }); - }, - isMine: (x: number, y: number) => { - const { mines } = get(); - return mines[x][y]; - }, - getNeighborMines: (x: number, y: number) => { - const { mines } = get(); - const neighbors = [ - mines[x - 1]?.[y - 1], - mines[x]?.[y - 1], - mines[x + 1]?.[y - 1], - mines[x - 1]?.[y], - mines[x + 1]?.[y], - mines[x - 1]?.[y + 1], - mines[x]?.[y + 1], - mines[x + 1]?.[y + 1], - ]; - return neighbors; - }, - getNeighborFlags: (x: number, y: number) => { - const { isFlagged } = get(); - const neighbors = [ - isFlagged[x - 1]?.[y - 1], - isFlagged[x]?.[y - 1], - isFlagged[x + 1]?.[y - 1], - isFlagged[x - 1]?.[y], - isFlagged[x + 1]?.[y], - isFlagged[x - 1]?.[y + 1], - isFlagged[x]?.[y + 1], - isFlagged[x + 1]?.[y + 1], - ]; - return neighbors; - }, - getWidth: () => { - const { width } = get(); - return width; - }, - getHeight: () => { - const { height } = get(); - return height; - }, - isTouched: () => { - const { isRevealed, isFlagged } = get(); - return ( - isRevealed.flat().filter((flag) => flag).length > 0 || - isFlagged.flat().filter((flag) => flag).length > 0 - ); - }, - triggerPostGame: () => { - const { getHasWon, expandBoard } = get(); - if (getHasWon()) { - expandBoard(); - return true; - } - return false; - }, - expandBoard: () => { - const { width, height, stage, mines, isFlagged, isRevealed } = get(); - let dir = stage % 2 === 0 ? "down" : "right"; - if (stage > 11) { - dir = "down"; - } - // Expand the board by the current board size 8x8 -> 16x8 - if (dir === "down") { - const newHeight = Math.floor(height * 1.5); - const newWidth = width; - const newMinesCount = Math.floor( - width * height * 0.5 * (0.2 + 0.003 * stage), - ); - // expand mines array - const newMines = Array.from({ length: newWidth }, () => - new Array(newHeight).fill(false), - ); - const newIsRevealed = Array.from({ length: newWidth }, () => - new Array(newHeight).fill(false), - ); - const newIsFlagged = Array.from({ length: newWidth }, () => - new Array(newHeight).fill(false), - ); - for (let i = 0; i < newWidth; i++) { - for (let j = 0; j < newHeight; j++) { - const x = i; - const y = j; - if (mines[x]?.[y]) { - newMines[i][j] = true; - } - if (isRevealed[x]?.[y]) { - newIsRevealed[i][j] = true; - } - if (isFlagged[x]?.[y]) { - newIsFlagged[i][j] = true; - } - } - } - // generate new mines - let remainingMines = newMinesCount; - while (remainingMines > 0) { - const x = Math.floor(Math.random() * width); - const y = height + Math.floor(Math.random() * (newHeight - height)); - if (!newMines[x][y]) { - newMines[x][y] = true; - remainingMines--; - } - } - set({ - width: newWidth, - height: newHeight, - mines: newMines, - minesCount: newMinesCount, - stage: stage + 1, - isRevealed: newIsRevealed, - isFlagged: newIsFlagged, - }); - } - if (dir === "right") { - const newWidth = Math.floor(width * 1.5); - const newHeight = height; - const newMinesCount = Math.floor( - width * height * 0.5 * (0.2 + 0.003 * stage), - ); - // expand mines array - const newMines = Array.from({ length: newWidth }, () => - new Array(newHeight).fill(false), - ); - const newIsRevealed = Array.from({ length: newWidth }, () => - new Array(newHeight).fill(false), - ); - const newIsFlagged = Array.from({ length: newWidth }, () => - new Array(newHeight).fill(false), - ); - for (let i = 0; i < newWidth; i++) { - for (let j = 0; j < newHeight; j++) { - const x = i; - const y = j; - if (mines[x]?.[y]) { - newMines[i][j] = true; - } - if (isRevealed[x]?.[y]) { - newIsRevealed[i][j] = true; - } - if (isFlagged[x]?.[y]) { - newIsFlagged[i][j] = true; - } - } - } - // generate new mines - let remainingMines = newMinesCount; - while (remainingMines > 0) { - const x = width + Math.floor(Math.random() * (newWidth - width)); - const y = Math.floor(Math.random() * height); - if (!newMines[x][y]) { - newMines[x][y] = true; - remainingMines--; - } - } - set({ - width: newWidth, - height: newHeight, - mines: newMines, - minesCount: newMinesCount, - stage: stage + 1, - isRevealed: newIsRevealed, - isFlagged: newIsFlagged, - }); - } - const newMinesCount = get() - .mines.flat() - .filter((m) => m).length; - set({ minesCount: newMinesCount }); - }, - setName: (name) => { - localStorage.setItem("name", name); - set({ name }); - }, - setShowFeed: (showFeed) => { - localStorage.setItem("showFeed", showFeed.toString()); - set({ showFeed }); - }, -})); - -export default useGameStore; diff --git a/src/Options.tsx b/src/Options.tsx deleted file mode 100644 index ccfc56e..0000000 --- a/src/Options.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { useEffect, useState } from "react"; -import useGameStore from "./GameState"; - -const presets = { - Easy: { width: 10, height: 10, mines: 20 }, - Medium: { width: 16, height: 16, mines: 32 }, - Expert: { width: 30, height: 16, mines: 99 }, - "Max Mode": { width: 40, height: 40, mines: 350 }, -} as const; - -function Options() { - const game = useGameStore(); - const [width, setWidth] = useState(16); - const [height, setHeight] = useState(16); - const [mines, setMines] = useState(32); - const [showOptions, setShowOptions] = useState(false); - - useEffect(() => { - const fixWidth = Math.min(40, width); - const fixHeight = Math.min(40, height); - setWidth(fixWidth); - setHeight(fixHeight); - }, [width, height]); - - useEffect(() => { - if (!game.isTouched()) { - if (width <= 0 || height <= 0 || mines <= 0) { - return; - } - game.resetGame(width, height, mines); - } - }, [width, height, mines, game]); - - return ( -
- - {showOptions && ( - <> -

- Presets:{" "} - {(Object.keys(presets) as Array).map( - (key) => ( - - ), - )} -

-

- Width:{" "} - setWidth(Number(e.target.value))} - /> -

-

- Height:{" "} - setHeight(Number(e.target.value))} - /> -

-

- Mines:{" "} - setMines(Number(e.target.value))} - /> -

- - )} - -
- ); -} - -export default Options; diff --git a/src/Shell.tsx b/src/Shell.tsx new file mode 100644 index 0000000..dead12d --- /dev/null +++ b/src/Shell.tsx @@ -0,0 +1,129 @@ +import { PropsWithChildren, useEffect, useRef, useState } from "react"; +import { Button } from "./components/Button"; +import { motion } from "framer-motion"; +import { + GitBranch, + History, + Home, + Library, + Menu, + Play, + Settings, + Store, +} from "lucide-react"; +import Hr from "./components/Hr"; +import NavLink from "./components/NavLink"; +import { useMediaQuery } from "@uidotdev/usehooks"; +import Header from "./components/Header"; +import { Tag } from "./components/Tag"; +import Feed from "./components/Feed/Feed"; + +const drawerWidth = 256; +const drawerWidthWithPadding = drawerWidth; + +const Shell: React.FC = ({ children }) => { + const [isOpen, setIsOpen] = useState(false); + const drawerRef = useRef(null); + + const x = isOpen ? 0 : -drawerWidthWithPadding; + const width = isOpen ? drawerWidthWithPadding : 0; + const isMobile = useMediaQuery("(max-width: 768px)"); + useEffect(() => { + setIsOpen(!isMobile); + }, [isMobile]); + useEffect(() => { + const onOutsideClick = (e: MouseEvent) => { + if ( + drawerRef.current && + !drawerRef.current.contains(e.target as Node) && + isMobile + ) { + setIsOpen(false); + e.stopPropagation(); + e.preventDefault(); + } + }; + document.addEventListener("click", onOutsideClick); + return () => { + document.removeEventListener("click", onOutsideClick); + }; + }); + + return ( +
+ +
+

+ Business +
+ Minesweeper +

+
+ + + Home + + + + Play + + + + History + + + + Store + + + + Collection NEW + + + + Settings + +
+ + {/*
*/} +
+ + + Source + +
+
+ +
+ + + + +
+
+ {children} +
+
+
+
+ ); +}; + +export default Shell; diff --git a/src/Timer.tsx b/src/Timer.tsx deleted file mode 100644 index 8359db3..0000000 --- a/src/Timer.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useEffect, useState } from "react"; -import Confetti from "react-confetti-boom"; -import useGameStore from "./GameState"; - -const emoteByStage = [ - "😐", - "😐", - "🙂", - "🤔", - "👀", - "😎", - "💀", - "🤯", - "🐐", - "⚡", - "🦸", - "🔥", - "💥", - "🐶", - "🦉", - "🚀", - "👾", -]; - -const Timer = () => { - const game = useGameStore(); - const [currentTime, setCurrentTime] = useState(Date.now()); - - useEffect(() => { - if (game.isGameOver || game.getHasWon()) { - if (game.stage === 1) return; - const name = game.name; - if (name) { - fetch("https://mb.gordon.business/submit", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user: name, - stage: game.stage, - }), - }); - } - return; - } - const interval = setInterval(() => { - setCurrentTime(Date.now()); - }, 1000); - - return () => clearInterval(interval); - }, [game, game.isGameOver]); - - return ( - <> -
-

- Stage: {game.stage} ({game.getWidth()}x{game.getHeight()}) -

-
-
-

{game.getMinesLeft()}

-

- {game.getHasWon() - ? "😎" - : game.isGameOver - ? "😢" - : emoteByStage[game.stage] || "😐"} - {game.stage > 1 && ( - - )} -

-

- {Math.max( - 0, - Math.floor((currentTime - (game.startTime || 0)) / 1000), - )} -

-
- - ); -}; - -export default Timer; diff --git a/src/assets/gem.png b/src/assets/gem.png new file mode 100644 index 0000000..659e7de Binary files /dev/null and b/src/assets/gem.png differ diff --git a/src/assets/illustrations/defusing.png b/src/assets/illustrations/defusing.png new file mode 100644 index 0000000..af1e28b Binary files /dev/null and b/src/assets/illustrations/defusing.png differ diff --git a/src/assets/illustrations/lootbox1.png b/src/assets/illustrations/lootbox1.png new file mode 100644 index 0000000..cb84a6e Binary files /dev/null and b/src/assets/illustrations/lootbox1.png differ diff --git a/src/assets/illustrations/mine.png b/src/assets/illustrations/mine.png new file mode 100644 index 0000000..12b27a9 Binary files /dev/null and b/src/assets/illustrations/mine.png differ diff --git a/src/assets/themes/MLG/1.png b/src/assets/themes/MLG/1.png new file mode 100644 index 0000000..aea4a6d Binary files /dev/null and b/src/assets/themes/MLG/1.png differ diff --git a/src/assets/themes/MLG/2.png b/src/assets/themes/MLG/2.png new file mode 100644 index 0000000..8625218 Binary files /dev/null and b/src/assets/themes/MLG/2.png differ diff --git a/src/assets/themes/MLG/3.png b/src/assets/themes/MLG/3.png new file mode 100644 index 0000000..5def64f Binary files /dev/null and b/src/assets/themes/MLG/3.png differ diff --git a/src/assets/themes/MLG/4.png b/src/assets/themes/MLG/4.png new file mode 100644 index 0000000..26a6f3d Binary files /dev/null and b/src/assets/themes/MLG/4.png differ diff --git a/src/assets/themes/MLG/5.png b/src/assets/themes/MLG/5.png new file mode 100644 index 0000000..84f42ea Binary files /dev/null and b/src/assets/themes/MLG/5.png differ diff --git a/src/assets/themes/MLG/6.png b/src/assets/themes/MLG/6.png new file mode 100644 index 0000000..bb9e560 Binary files /dev/null and b/src/assets/themes/MLG/6.png differ diff --git a/src/assets/themes/MLG/7.png b/src/assets/themes/MLG/7.png new file mode 100644 index 0000000..ccc600b Binary files /dev/null and b/src/assets/themes/MLG/7.png differ diff --git a/src/assets/themes/MLG/8.png b/src/assets/themes/MLG/8.png new file mode 100644 index 0000000..fe0197b Binary files /dev/null and b/src/assets/themes/MLG/8.png differ diff --git a/src/assets/themes/MLG/Layer 1.png b/src/assets/themes/MLG/Layer 1.png new file mode 100644 index 0000000..77a85a8 Binary files /dev/null and b/src/assets/themes/MLG/Layer 1.png differ diff --git a/src/assets/themes/MLG/MLG.aseprite b/src/assets/themes/MLG/MLG.aseprite new file mode 100644 index 0000000..92e0ca7 Binary files /dev/null and b/src/assets/themes/MLG/MLG.aseprite differ diff --git a/src/assets/themes/MLG/flag-1.png b/src/assets/themes/MLG/flag-1.png new file mode 100644 index 0000000..ae0dce1 Binary files /dev/null and b/src/assets/themes/MLG/flag-1.png differ diff --git a/src/assets/themes/MLG/flag-2.png b/src/assets/themes/MLG/flag-2.png new file mode 100644 index 0000000..045fc52 Binary files /dev/null and b/src/assets/themes/MLG/flag-2.png differ diff --git a/src/assets/themes/MLG/last-pos.png b/src/assets/themes/MLG/last-pos.png new file mode 100644 index 0000000..5e28c2d Binary files /dev/null and b/src/assets/themes/MLG/last-pos.png differ diff --git a/src/assets/themes/MLG/mine.png b/src/assets/themes/MLG/mine.png new file mode 100644 index 0000000..390110f Binary files /dev/null and b/src/assets/themes/MLG/mine.png differ diff --git a/src/assets/themes/MLG/question-mark.png b/src/assets/themes/MLG/question-mark.png new file mode 100644 index 0000000..0e888ef Binary files /dev/null and b/src/assets/themes/MLG/question-mark.png differ diff --git a/src/assets/themes/MLG/revealed.png b/src/assets/themes/MLG/revealed.png new file mode 100644 index 0000000..2cdf0f7 Binary files /dev/null and b/src/assets/themes/MLG/revealed.png differ diff --git a/src/assets/themes/MLG/tile.png b/src/assets/themes/MLG/tile.png new file mode 100644 index 0000000..1ded545 Binary files /dev/null and b/src/assets/themes/MLG/tile.png differ diff --git a/src/assets/themes/basic/1.png b/src/assets/themes/basic/1.png new file mode 100644 index 0000000..d172a97 Binary files /dev/null and b/src/assets/themes/basic/1.png differ diff --git a/src/assets/themes/basic/2.png b/src/assets/themes/basic/2.png new file mode 100644 index 0000000..1a5d9dc Binary files /dev/null and b/src/assets/themes/basic/2.png differ diff --git a/src/assets/themes/basic/3.png b/src/assets/themes/basic/3.png new file mode 100644 index 0000000..fcd3102 Binary files /dev/null and b/src/assets/themes/basic/3.png differ diff --git a/src/assets/themes/basic/4.png b/src/assets/themes/basic/4.png new file mode 100644 index 0000000..1322dfc Binary files /dev/null and b/src/assets/themes/basic/4.png differ diff --git a/src/assets/themes/basic/5.png b/src/assets/themes/basic/5.png new file mode 100644 index 0000000..423db44 Binary files /dev/null and b/src/assets/themes/basic/5.png differ diff --git a/src/assets/themes/basic/6.png b/src/assets/themes/basic/6.png new file mode 100644 index 0000000..adcffbc Binary files /dev/null and b/src/assets/themes/basic/6.png differ diff --git a/src/assets/themes/basic/7.png b/src/assets/themes/basic/7.png new file mode 100644 index 0000000..c6a0244 Binary files /dev/null and b/src/assets/themes/basic/7.png differ diff --git a/src/assets/themes/basic/8.png b/src/assets/themes/basic/8.png new file mode 100644 index 0000000..cf900d0 Binary files /dev/null and b/src/assets/themes/basic/8.png differ diff --git a/src/assets/themes/basic/basic.aseprite b/src/assets/themes/basic/basic.aseprite new file mode 100644 index 0000000..bd887cb Binary files /dev/null and b/src/assets/themes/basic/basic.aseprite differ diff --git a/src/assets/themes/basic/flag.png b/src/assets/themes/basic/flag.png new file mode 100644 index 0000000..46b9c6c Binary files /dev/null and b/src/assets/themes/basic/flag.png differ diff --git a/src/assets/themes/basic/last-pos.png b/src/assets/themes/basic/last-pos.png new file mode 100644 index 0000000..3f2f82c Binary files /dev/null and b/src/assets/themes/basic/last-pos.png differ diff --git a/src/assets/themes/basic/mine.png b/src/assets/themes/basic/mine.png new file mode 100644 index 0000000..d4f3909 Binary files /dev/null and b/src/assets/themes/basic/mine.png differ diff --git a/src/assets/themes/basic/question-mark.png b/src/assets/themes/basic/question-mark.png new file mode 100644 index 0000000..bd778ec Binary files /dev/null and b/src/assets/themes/basic/question-mark.png differ diff --git a/src/assets/themes/basic/revealed.png b/src/assets/themes/basic/revealed.png new file mode 100644 index 0000000..5cb54c9 Binary files /dev/null and b/src/assets/themes/basic/revealed.png differ diff --git a/src/assets/themes/basic/tile.png b/src/assets/themes/basic/tile.png new file mode 100644 index 0000000..20f450b Binary files /dev/null and b/src/assets/themes/basic/tile.png differ diff --git a/src/assets/themes/black-and-white/1.png b/src/assets/themes/black-and-white/1.png new file mode 100644 index 0000000..84191bd Binary files /dev/null and b/src/assets/themes/black-and-white/1.png differ diff --git a/src/assets/themes/black-and-white/2.png b/src/assets/themes/black-and-white/2.png new file mode 100644 index 0000000..76f573d Binary files /dev/null and b/src/assets/themes/black-and-white/2.png differ diff --git a/src/assets/themes/black-and-white/3.png b/src/assets/themes/black-and-white/3.png new file mode 100644 index 0000000..9d053b1 Binary files /dev/null and b/src/assets/themes/black-and-white/3.png differ diff --git a/src/assets/themes/black-and-white/4.png b/src/assets/themes/black-and-white/4.png new file mode 100644 index 0000000..d1f0bc8 Binary files /dev/null and b/src/assets/themes/black-and-white/4.png differ diff --git a/src/assets/themes/black-and-white/5.png b/src/assets/themes/black-and-white/5.png new file mode 100644 index 0000000..37515ec Binary files /dev/null and b/src/assets/themes/black-and-white/5.png differ diff --git a/src/assets/themes/black-and-white/6.png b/src/assets/themes/black-and-white/6.png new file mode 100644 index 0000000..4779d1a Binary files /dev/null and b/src/assets/themes/black-and-white/6.png differ diff --git a/src/assets/themes/black-and-white/7.png b/src/assets/themes/black-and-white/7.png new file mode 100644 index 0000000..43b57e9 Binary files /dev/null and b/src/assets/themes/black-and-white/7.png differ diff --git a/src/assets/themes/black-and-white/8.png b/src/assets/themes/black-and-white/8.png new file mode 100644 index 0000000..e1a20c8 Binary files /dev/null and b/src/assets/themes/black-and-white/8.png differ diff --git a/src/assets/themes/black-and-white/black-and-white.aseprite b/src/assets/themes/black-and-white/black-and-white.aseprite new file mode 100644 index 0000000..e26ef5e Binary files /dev/null and b/src/assets/themes/black-and-white/black-and-white.aseprite differ diff --git a/src/assets/themes/black-and-white/flag.png b/src/assets/themes/black-and-white/flag.png new file mode 100644 index 0000000..7d45d8f Binary files /dev/null and b/src/assets/themes/black-and-white/flag.png differ diff --git a/src/assets/themes/black-and-white/last-pos.png b/src/assets/themes/black-and-white/last-pos.png new file mode 100644 index 0000000..9813718 Binary files /dev/null and b/src/assets/themes/black-and-white/last-pos.png differ diff --git a/src/assets/themes/black-and-white/mine.png b/src/assets/themes/black-and-white/mine.png new file mode 100644 index 0000000..45c2d6d Binary files /dev/null and b/src/assets/themes/black-and-white/mine.png differ diff --git a/src/assets/themes/black-and-white/question-mark.png b/src/assets/themes/black-and-white/question-mark.png new file mode 100644 index 0000000..afd85ba Binary files /dev/null and b/src/assets/themes/black-and-white/question-mark.png differ diff --git a/src/assets/themes/black-and-white/revealed.png b/src/assets/themes/black-and-white/revealed.png new file mode 100644 index 0000000..195129b Binary files /dev/null and b/src/assets/themes/black-and-white/revealed.png differ diff --git a/src/assets/themes/black-and-white/tile.png b/src/assets/themes/black-and-white/tile.png new file mode 100644 index 0000000..d8a8041 Binary files /dev/null and b/src/assets/themes/black-and-white/tile.png differ diff --git a/src/assets/themes/cats/1.png b/src/assets/themes/cats/1.png new file mode 100644 index 0000000..b7a3a50 Binary files /dev/null and b/src/assets/themes/cats/1.png differ diff --git a/src/assets/themes/cats/2.png b/src/assets/themes/cats/2.png new file mode 100644 index 0000000..0764d0f Binary files /dev/null and b/src/assets/themes/cats/2.png differ diff --git a/src/assets/themes/cats/3.png b/src/assets/themes/cats/3.png new file mode 100644 index 0000000..7459a50 Binary files /dev/null and b/src/assets/themes/cats/3.png differ diff --git a/src/assets/themes/cats/4.png b/src/assets/themes/cats/4.png new file mode 100644 index 0000000..c12bb0c Binary files /dev/null and b/src/assets/themes/cats/4.png differ diff --git a/src/assets/themes/cats/5.png b/src/assets/themes/cats/5.png new file mode 100644 index 0000000..53c3bed Binary files /dev/null and b/src/assets/themes/cats/5.png differ diff --git a/src/assets/themes/cats/6.png b/src/assets/themes/cats/6.png new file mode 100644 index 0000000..028ffb2 Binary files /dev/null and b/src/assets/themes/cats/6.png differ diff --git a/src/assets/themes/cats/7.png b/src/assets/themes/cats/7.png new file mode 100644 index 0000000..edafc40 Binary files /dev/null and b/src/assets/themes/cats/7.png differ diff --git a/src/assets/themes/cats/8.png b/src/assets/themes/cats/8.png new file mode 100644 index 0000000..645212e Binary files /dev/null and b/src/assets/themes/cats/8.png differ diff --git a/src/assets/themes/cats/cats.aseprite b/src/assets/themes/cats/cats.aseprite new file mode 100644 index 0000000..66a0551 Binary files /dev/null and b/src/assets/themes/cats/cats.aseprite differ diff --git a/src/assets/themes/cats/flag.png b/src/assets/themes/cats/flag.png new file mode 100644 index 0000000..28a7f77 Binary files /dev/null and b/src/assets/themes/cats/flag.png differ diff --git a/src/assets/themes/cats/last-pos.png b/src/assets/themes/cats/last-pos.png new file mode 100644 index 0000000..c927ac6 Binary files /dev/null and b/src/assets/themes/cats/last-pos.png differ diff --git a/src/assets/themes/cats/mine-1.png b/src/assets/themes/cats/mine-1.png new file mode 100644 index 0000000..85c638c Binary files /dev/null and b/src/assets/themes/cats/mine-1.png differ diff --git a/src/assets/themes/cats/mine-2.png b/src/assets/themes/cats/mine-2.png new file mode 100644 index 0000000..b56c69e Binary files /dev/null and b/src/assets/themes/cats/mine-2.png differ diff --git a/src/assets/themes/cats/question-mark.png b/src/assets/themes/cats/question-mark.png new file mode 100644 index 0000000..816406b Binary files /dev/null and b/src/assets/themes/cats/question-mark.png differ diff --git a/src/assets/themes/cats/revealed.png b/src/assets/themes/cats/revealed.png new file mode 100644 index 0000000..872f62b Binary files /dev/null and b/src/assets/themes/cats/revealed.png differ diff --git a/src/assets/themes/cats/tile.png b/src/assets/themes/cats/tile.png new file mode 100644 index 0000000..5d3ce0d Binary files /dev/null and b/src/assets/themes/cats/tile.png differ diff --git a/src/assets/themes/circuit/1.png b/src/assets/themes/circuit/1.png new file mode 100644 index 0000000..554000b Binary files /dev/null and b/src/assets/themes/circuit/1.png differ diff --git a/src/assets/themes/circuit/2.png b/src/assets/themes/circuit/2.png new file mode 100644 index 0000000..4023ff6 Binary files /dev/null and b/src/assets/themes/circuit/2.png differ diff --git a/src/assets/themes/circuit/3.png b/src/assets/themes/circuit/3.png new file mode 100644 index 0000000..3d43338 Binary files /dev/null and b/src/assets/themes/circuit/3.png differ diff --git a/src/assets/themes/circuit/4.png b/src/assets/themes/circuit/4.png new file mode 100644 index 0000000..802db0d Binary files /dev/null and b/src/assets/themes/circuit/4.png differ diff --git a/src/assets/themes/circuit/5.png b/src/assets/themes/circuit/5.png new file mode 100644 index 0000000..79d4fa7 Binary files /dev/null and b/src/assets/themes/circuit/5.png differ diff --git a/src/assets/themes/circuit/6.png b/src/assets/themes/circuit/6.png new file mode 100644 index 0000000..a361ab3 Binary files /dev/null and b/src/assets/themes/circuit/6.png differ diff --git a/src/assets/themes/circuit/7.png b/src/assets/themes/circuit/7.png new file mode 100644 index 0000000..ecbea9d Binary files /dev/null and b/src/assets/themes/circuit/7.png differ diff --git a/src/assets/themes/circuit/8.png b/src/assets/themes/circuit/8.png new file mode 100644 index 0000000..cc5f09f Binary files /dev/null and b/src/assets/themes/circuit/8.png differ diff --git a/src/assets/themes/circuit/binary/1.png b/src/assets/themes/circuit/binary/1.png new file mode 100644 index 0000000..4a86117 Binary files /dev/null and b/src/assets/themes/circuit/binary/1.png differ diff --git a/src/assets/themes/circuit/binary/2.png b/src/assets/themes/circuit/binary/2.png new file mode 100644 index 0000000..76c0cb7 Binary files /dev/null and b/src/assets/themes/circuit/binary/2.png differ diff --git a/src/assets/themes/circuit/binary/3.png b/src/assets/themes/circuit/binary/3.png new file mode 100644 index 0000000..43555ee Binary files /dev/null and b/src/assets/themes/circuit/binary/3.png differ diff --git a/src/assets/themes/circuit/binary/4.png b/src/assets/themes/circuit/binary/4.png new file mode 100644 index 0000000..d008888 Binary files /dev/null and b/src/assets/themes/circuit/binary/4.png differ diff --git a/src/assets/themes/circuit/binary/5.png b/src/assets/themes/circuit/binary/5.png new file mode 100644 index 0000000..3432e9e Binary files /dev/null and b/src/assets/themes/circuit/binary/5.png differ diff --git a/src/assets/themes/circuit/binary/6.png b/src/assets/themes/circuit/binary/6.png new file mode 100644 index 0000000..68e58bf Binary files /dev/null and b/src/assets/themes/circuit/binary/6.png differ diff --git a/src/assets/themes/circuit/binary/7.png b/src/assets/themes/circuit/binary/7.png new file mode 100644 index 0000000..237762d Binary files /dev/null and b/src/assets/themes/circuit/binary/7.png differ diff --git a/src/assets/themes/circuit/binary/8.png b/src/assets/themes/circuit/binary/8.png new file mode 100644 index 0000000..91544bb Binary files /dev/null and b/src/assets/themes/circuit/binary/8.png differ diff --git a/src/assets/themes/circuit/circuit.aseprite b/src/assets/themes/circuit/circuit.aseprite new file mode 100644 index 0000000..77fea59 Binary files /dev/null and b/src/assets/themes/circuit/circuit.aseprite differ diff --git a/src/assets/themes/circuit/flag.png b/src/assets/themes/circuit/flag.png new file mode 100644 index 0000000..fb3190d Binary files /dev/null and b/src/assets/themes/circuit/flag.png differ diff --git a/src/assets/themes/circuit/last-pos.png b/src/assets/themes/circuit/last-pos.png new file mode 100644 index 0000000..5fb4c6f Binary files /dev/null and b/src/assets/themes/circuit/last-pos.png differ diff --git a/src/assets/themes/circuit/mine.png b/src/assets/themes/circuit/mine.png new file mode 100644 index 0000000..d100862 Binary files /dev/null and b/src/assets/themes/circuit/mine.png differ diff --git a/src/assets/themes/circuit/question-mark.png b/src/assets/themes/circuit/question-mark.png new file mode 100644 index 0000000..5de5046 Binary files /dev/null and b/src/assets/themes/circuit/question-mark.png differ diff --git a/src/assets/themes/circuit/revealed.png b/src/assets/themes/circuit/revealed.png new file mode 100644 index 0000000..2a6f445 Binary files /dev/null and b/src/assets/themes/circuit/revealed.png differ diff --git a/src/assets/themes/circuit/tile.png b/src/assets/themes/circuit/tile.png new file mode 100644 index 0000000..52c2fe6 Binary files /dev/null and b/src/assets/themes/circuit/tile.png differ diff --git a/src/assets/themes/color-palettes/crimson/1.png b/src/assets/themes/color-palettes/crimson/1.png new file mode 100644 index 0000000..da8511d Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/1.png differ diff --git a/src/assets/themes/color-palettes/crimson/2.png b/src/assets/themes/color-palettes/crimson/2.png new file mode 100644 index 0000000..13538c9 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/2.png differ diff --git a/src/assets/themes/color-palettes/crimson/3.png b/src/assets/themes/color-palettes/crimson/3.png new file mode 100644 index 0000000..bed1b93 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/3.png differ diff --git a/src/assets/themes/color-palettes/crimson/4.png b/src/assets/themes/color-palettes/crimson/4.png new file mode 100644 index 0000000..f7feac6 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/4.png differ diff --git a/src/assets/themes/color-palettes/crimson/5.png b/src/assets/themes/color-palettes/crimson/5.png new file mode 100644 index 0000000..5febf87 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/5.png differ diff --git a/src/assets/themes/color-palettes/crimson/6.png b/src/assets/themes/color-palettes/crimson/6.png new file mode 100644 index 0000000..651ecf4 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/6.png differ diff --git a/src/assets/themes/color-palettes/crimson/7.png b/src/assets/themes/color-palettes/crimson/7.png new file mode 100644 index 0000000..22af2f9 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/7.png differ diff --git a/src/assets/themes/color-palettes/crimson/8.png b/src/assets/themes/color-palettes/crimson/8.png new file mode 100644 index 0000000..017f719 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/8.png differ diff --git a/src/assets/themes/color-palettes/crimson/crimson.aseprite b/src/assets/themes/color-palettes/crimson/crimson.aseprite new file mode 100644 index 0000000..1ead211 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/crimson.aseprite differ diff --git a/src/assets/themes/color-palettes/crimson/flag.png b/src/assets/themes/color-palettes/crimson/flag.png new file mode 100644 index 0000000..05f6fcd Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/flag.png differ diff --git a/src/assets/themes/color-palettes/crimson/last-pos.png b/src/assets/themes/color-palettes/crimson/last-pos.png new file mode 100644 index 0000000..5ecfcb4 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/last-pos.png differ diff --git a/src/assets/themes/color-palettes/crimson/mine.png b/src/assets/themes/color-palettes/crimson/mine.png new file mode 100644 index 0000000..42303c4 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/mine.png differ diff --git a/src/assets/themes/color-palettes/crimson/question-mark.png b/src/assets/themes/color-palettes/crimson/question-mark.png new file mode 100644 index 0000000..6731144 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/question-mark.png differ diff --git a/src/assets/themes/color-palettes/crimson/revealed.png b/src/assets/themes/color-palettes/crimson/revealed.png new file mode 100644 index 0000000..a050be0 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/revealed.png differ diff --git a/src/assets/themes/color-palettes/crimson/tile.png b/src/assets/themes/color-palettes/crimson/tile.png new file mode 100644 index 0000000..8ab66c1 Binary files /dev/null and b/src/assets/themes/color-palettes/crimson/tile.png differ diff --git a/src/assets/themes/color-palettes/nautical/1.png b/src/assets/themes/color-palettes/nautical/1.png new file mode 100644 index 0000000..2d13f65 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/1.png differ diff --git a/src/assets/themes/color-palettes/nautical/2.png b/src/assets/themes/color-palettes/nautical/2.png new file mode 100644 index 0000000..7a8e289 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/2.png differ diff --git a/src/assets/themes/color-palettes/nautical/3.png b/src/assets/themes/color-palettes/nautical/3.png new file mode 100644 index 0000000..c76252b Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/3.png differ diff --git a/src/assets/themes/color-palettes/nautical/4.png b/src/assets/themes/color-palettes/nautical/4.png new file mode 100644 index 0000000..d826166 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/4.png differ diff --git a/src/assets/themes/color-palettes/nautical/5.png b/src/assets/themes/color-palettes/nautical/5.png new file mode 100644 index 0000000..ba48ef5 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/5.png differ diff --git a/src/assets/themes/color-palettes/nautical/6.png b/src/assets/themes/color-palettes/nautical/6.png new file mode 100644 index 0000000..2c85747 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/6.png differ diff --git a/src/assets/themes/color-palettes/nautical/7.png b/src/assets/themes/color-palettes/nautical/7.png new file mode 100644 index 0000000..4b84a22 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/7.png differ diff --git a/src/assets/themes/color-palettes/nautical/8.png b/src/assets/themes/color-palettes/nautical/8.png new file mode 100644 index 0000000..b0413f5 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/8.png differ diff --git a/src/assets/themes/color-palettes/nautical/flag.png b/src/assets/themes/color-palettes/nautical/flag.png new file mode 100644 index 0000000..a3bb373 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/flag.png differ diff --git a/src/assets/themes/color-palettes/nautical/last-pos.png b/src/assets/themes/color-palettes/nautical/last-pos.png new file mode 100644 index 0000000..b480393 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/last-pos.png differ diff --git a/src/assets/themes/color-palettes/nautical/mine.png b/src/assets/themes/color-palettes/nautical/mine.png new file mode 100644 index 0000000..8a8f3dc Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/mine.png differ diff --git a/src/assets/themes/color-palettes/nautical/nautical.aseprite b/src/assets/themes/color-palettes/nautical/nautical.aseprite new file mode 100644 index 0000000..b8fad3f Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/nautical.aseprite differ diff --git a/src/assets/themes/color-palettes/nautical/question-mark.png b/src/assets/themes/color-palettes/nautical/question-mark.png new file mode 100644 index 0000000..057e8af Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/question-mark.png differ diff --git a/src/assets/themes/color-palettes/nautical/revealed.png b/src/assets/themes/color-palettes/nautical/revealed.png new file mode 100644 index 0000000..f7c6908 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/revealed.png differ diff --git a/src/assets/themes/color-palettes/nautical/tile.png b/src/assets/themes/color-palettes/nautical/tile.png new file mode 100644 index 0000000..f4882c5 Binary files /dev/null and b/src/assets/themes/color-palettes/nautical/tile.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/1.png b/src/assets/themes/color-palettes/shadow-warrior/1.png new file mode 100644 index 0000000..6549175 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/1.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/2.png b/src/assets/themes/color-palettes/shadow-warrior/2.png new file mode 100644 index 0000000..4f86baf Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/2.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/3.png b/src/assets/themes/color-palettes/shadow-warrior/3.png new file mode 100644 index 0000000..e76e23f Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/3.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/4.png b/src/assets/themes/color-palettes/shadow-warrior/4.png new file mode 100644 index 0000000..19d6dcc Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/4.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/5.png b/src/assets/themes/color-palettes/shadow-warrior/5.png new file mode 100644 index 0000000..b17b15d Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/5.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/6.png b/src/assets/themes/color-palettes/shadow-warrior/6.png new file mode 100644 index 0000000..3c09796 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/6.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/7.png b/src/assets/themes/color-palettes/shadow-warrior/7.png new file mode 100644 index 0000000..7d6dcdb Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/7.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/8.png b/src/assets/themes/color-palettes/shadow-warrior/8.png new file mode 100644 index 0000000..5d1a361 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/8.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/flag.png b/src/assets/themes/color-palettes/shadow-warrior/flag.png new file mode 100644 index 0000000..209577e Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/flag.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/last-pos.png b/src/assets/themes/color-palettes/shadow-warrior/last-pos.png new file mode 100644 index 0000000..c57e40a Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/last-pos.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/mine.png b/src/assets/themes/color-palettes/shadow-warrior/mine.png new file mode 100644 index 0000000..c6bc4c2 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/mine.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/question-mark.png b/src/assets/themes/color-palettes/shadow-warrior/question-mark.png new file mode 100644 index 0000000..6731144 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/question-mark.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/revealed.png b/src/assets/themes/color-palettes/shadow-warrior/revealed.png new file mode 100644 index 0000000..4d6551c Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/revealed.png differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/shadow-warrior.aseprite b/src/assets/themes/color-palettes/shadow-warrior/shadow-warrior.aseprite new file mode 100644 index 0000000..978bc56 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/shadow-warrior.aseprite differ diff --git a/src/assets/themes/color-palettes/shadow-warrior/tile.png b/src/assets/themes/color-palettes/shadow-warrior/tile.png new file mode 100644 index 0000000..c1589a5 Binary files /dev/null and b/src/assets/themes/color-palettes/shadow-warrior/tile.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/1.png b/src/assets/themes/color-palettes/up-in-smoke/1.png new file mode 100644 index 0000000..4761e16 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/1.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/2.png b/src/assets/themes/color-palettes/up-in-smoke/2.png new file mode 100644 index 0000000..c3ab5dd Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/2.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/3.png b/src/assets/themes/color-palettes/up-in-smoke/3.png new file mode 100644 index 0000000..ee05d28 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/3.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/4.png b/src/assets/themes/color-palettes/up-in-smoke/4.png new file mode 100644 index 0000000..4fbe100 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/4.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/5.png b/src/assets/themes/color-palettes/up-in-smoke/5.png new file mode 100644 index 0000000..8c24a34 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/5.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/6.png b/src/assets/themes/color-palettes/up-in-smoke/6.png new file mode 100644 index 0000000..563f2da Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/6.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/7.png b/src/assets/themes/color-palettes/up-in-smoke/7.png new file mode 100644 index 0000000..401b6dc Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/7.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/8.png b/src/assets/themes/color-palettes/up-in-smoke/8.png new file mode 100644 index 0000000..47ff597 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/8.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/flag.png b/src/assets/themes/color-palettes/up-in-smoke/flag.png new file mode 100644 index 0000000..051192e Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/flag.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/last-pos.png b/src/assets/themes/color-palettes/up-in-smoke/last-pos.png new file mode 100644 index 0000000..f8ddeaf Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/last-pos.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/mine.png b/src/assets/themes/color-palettes/up-in-smoke/mine.png new file mode 100644 index 0000000..bad4e08 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/mine.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/question-mark.png b/src/assets/themes/color-palettes/up-in-smoke/question-mark.png new file mode 100644 index 0000000..50061f8 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/question-mark.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/revealed.png b/src/assets/themes/color-palettes/up-in-smoke/revealed.png new file mode 100644 index 0000000..2ff5d9d Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/revealed.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/tile.png b/src/assets/themes/color-palettes/up-in-smoke/tile.png new file mode 100644 index 0000000..9063db6 Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/tile.png differ diff --git a/src/assets/themes/color-palettes/up-in-smoke/up-in-smoke.aseprite b/src/assets/themes/color-palettes/up-in-smoke/up-in-smoke.aseprite new file mode 100644 index 0000000..a1bbfed Binary files /dev/null and b/src/assets/themes/color-palettes/up-in-smoke/up-in-smoke.aseprite differ diff --git a/src/assets/themes/colors/blue/1.png b/src/assets/themes/colors/blue/1.png new file mode 100644 index 0000000..f471cdd Binary files /dev/null and b/src/assets/themes/colors/blue/1.png differ diff --git a/src/assets/themes/colors/blue/2.png b/src/assets/themes/colors/blue/2.png new file mode 100644 index 0000000..1b9e29f Binary files /dev/null and b/src/assets/themes/colors/blue/2.png differ diff --git a/src/assets/themes/colors/blue/3.png b/src/assets/themes/colors/blue/3.png new file mode 100644 index 0000000..996ac9e Binary files /dev/null and b/src/assets/themes/colors/blue/3.png differ diff --git a/src/assets/themes/colors/blue/4.png b/src/assets/themes/colors/blue/4.png new file mode 100644 index 0000000..9a96e05 Binary files /dev/null and b/src/assets/themes/colors/blue/4.png differ diff --git a/src/assets/themes/colors/blue/5.png b/src/assets/themes/colors/blue/5.png new file mode 100644 index 0000000..044e705 Binary files /dev/null and b/src/assets/themes/colors/blue/5.png differ diff --git a/src/assets/themes/colors/blue/6.png b/src/assets/themes/colors/blue/6.png new file mode 100644 index 0000000..3235fc3 Binary files /dev/null and b/src/assets/themes/colors/blue/6.png differ diff --git a/src/assets/themes/colors/blue/7.png b/src/assets/themes/colors/blue/7.png new file mode 100644 index 0000000..93d4438 Binary files /dev/null and b/src/assets/themes/colors/blue/7.png differ diff --git a/src/assets/themes/colors/blue/8.png b/src/assets/themes/colors/blue/8.png new file mode 100644 index 0000000..194f27c Binary files /dev/null and b/src/assets/themes/colors/blue/8.png differ diff --git a/src/assets/themes/colors/blue/blue.aseprite b/src/assets/themes/colors/blue/blue.aseprite new file mode 100644 index 0000000..9986a9c Binary files /dev/null and b/src/assets/themes/colors/blue/blue.aseprite differ diff --git a/src/assets/themes/colors/blue/flag.png b/src/assets/themes/colors/blue/flag.png new file mode 100644 index 0000000..e70eb08 Binary files /dev/null and b/src/assets/themes/colors/blue/flag.png differ diff --git a/src/assets/themes/colors/blue/last-pos.png b/src/assets/themes/colors/blue/last-pos.png new file mode 100644 index 0000000..ec164e9 Binary files /dev/null and b/src/assets/themes/colors/blue/last-pos.png differ diff --git a/src/assets/themes/colors/blue/mine.png b/src/assets/themes/colors/blue/mine.png new file mode 100644 index 0000000..7ed0901 Binary files /dev/null and b/src/assets/themes/colors/blue/mine.png differ diff --git a/src/assets/themes/colors/blue/question-mark.png b/src/assets/themes/colors/blue/question-mark.png new file mode 100644 index 0000000..49ef590 Binary files /dev/null and b/src/assets/themes/colors/blue/question-mark.png differ diff --git a/src/assets/themes/colors/blue/revealed.png b/src/assets/themes/colors/blue/revealed.png new file mode 100644 index 0000000..7dd62b5 Binary files /dev/null and b/src/assets/themes/colors/blue/revealed.png differ diff --git a/src/assets/themes/colors/blue/tile.png b/src/assets/themes/colors/blue/tile.png new file mode 100644 index 0000000..6a7564d Binary files /dev/null and b/src/assets/themes/colors/blue/tile.png differ diff --git a/src/assets/themes/colors/green/1.png b/src/assets/themes/colors/green/1.png new file mode 100644 index 0000000..648525e Binary files /dev/null and b/src/assets/themes/colors/green/1.png differ diff --git a/src/assets/themes/colors/green/2.png b/src/assets/themes/colors/green/2.png new file mode 100644 index 0000000..dbc9cdd Binary files /dev/null and b/src/assets/themes/colors/green/2.png differ diff --git a/src/assets/themes/colors/green/3.png b/src/assets/themes/colors/green/3.png new file mode 100644 index 0000000..841e9c1 Binary files /dev/null and b/src/assets/themes/colors/green/3.png differ diff --git a/src/assets/themes/colors/green/4.png b/src/assets/themes/colors/green/4.png new file mode 100644 index 0000000..d5007e1 Binary files /dev/null and b/src/assets/themes/colors/green/4.png differ diff --git a/src/assets/themes/colors/green/5.png b/src/assets/themes/colors/green/5.png new file mode 100644 index 0000000..46cdbde Binary files /dev/null and b/src/assets/themes/colors/green/5.png differ diff --git a/src/assets/themes/colors/green/6.png b/src/assets/themes/colors/green/6.png new file mode 100644 index 0000000..89912d5 Binary files /dev/null and b/src/assets/themes/colors/green/6.png differ diff --git a/src/assets/themes/colors/green/7.png b/src/assets/themes/colors/green/7.png new file mode 100644 index 0000000..d367939 Binary files /dev/null and b/src/assets/themes/colors/green/7.png differ diff --git a/src/assets/themes/colors/green/8.png b/src/assets/themes/colors/green/8.png new file mode 100644 index 0000000..5bb06fd Binary files /dev/null and b/src/assets/themes/colors/green/8.png differ diff --git a/src/assets/themes/colors/green/flag.png b/src/assets/themes/colors/green/flag.png new file mode 100644 index 0000000..c1c069f Binary files /dev/null and b/src/assets/themes/colors/green/flag.png differ diff --git a/src/assets/themes/colors/green/green.aseprite b/src/assets/themes/colors/green/green.aseprite new file mode 100644 index 0000000..239314e Binary files /dev/null and b/src/assets/themes/colors/green/green.aseprite differ diff --git a/src/assets/themes/colors/green/last-pos.png b/src/assets/themes/colors/green/last-pos.png new file mode 100644 index 0000000..3d21053 Binary files /dev/null and b/src/assets/themes/colors/green/last-pos.png differ diff --git a/src/assets/themes/colors/green/mine.png b/src/assets/themes/colors/green/mine.png new file mode 100644 index 0000000..4b0d03e Binary files /dev/null and b/src/assets/themes/colors/green/mine.png differ diff --git a/src/assets/themes/colors/green/question-mark.png b/src/assets/themes/colors/green/question-mark.png new file mode 100644 index 0000000..b475cab Binary files /dev/null and b/src/assets/themes/colors/green/question-mark.png differ diff --git a/src/assets/themes/colors/green/revealed.png b/src/assets/themes/colors/green/revealed.png new file mode 100644 index 0000000..5003b19 Binary files /dev/null and b/src/assets/themes/colors/green/revealed.png differ diff --git a/src/assets/themes/colors/green/tile.png b/src/assets/themes/colors/green/tile.png new file mode 100644 index 0000000..7d70ef3 Binary files /dev/null and b/src/assets/themes/colors/green/tile.png differ diff --git a/src/assets/themes/colors/orange/1.png b/src/assets/themes/colors/orange/1.png new file mode 100644 index 0000000..8e813e1 Binary files /dev/null and b/src/assets/themes/colors/orange/1.png differ diff --git a/src/assets/themes/colors/orange/2.png b/src/assets/themes/colors/orange/2.png new file mode 100644 index 0000000..88352a2 Binary files /dev/null and b/src/assets/themes/colors/orange/2.png differ diff --git a/src/assets/themes/colors/orange/3.png b/src/assets/themes/colors/orange/3.png new file mode 100644 index 0000000..a99b93d Binary files /dev/null and b/src/assets/themes/colors/orange/3.png differ diff --git a/src/assets/themes/colors/orange/4.png b/src/assets/themes/colors/orange/4.png new file mode 100644 index 0000000..f8de9a2 Binary files /dev/null and b/src/assets/themes/colors/orange/4.png differ diff --git a/src/assets/themes/colors/orange/5.png b/src/assets/themes/colors/orange/5.png new file mode 100644 index 0000000..5bdf3f4 Binary files /dev/null and b/src/assets/themes/colors/orange/5.png differ diff --git a/src/assets/themes/colors/orange/6.png b/src/assets/themes/colors/orange/6.png new file mode 100644 index 0000000..efcfa6c Binary files /dev/null and b/src/assets/themes/colors/orange/6.png differ diff --git a/src/assets/themes/colors/orange/7.png b/src/assets/themes/colors/orange/7.png new file mode 100644 index 0000000..10f6ef0 Binary files /dev/null and b/src/assets/themes/colors/orange/7.png differ diff --git a/src/assets/themes/colors/orange/8.png b/src/assets/themes/colors/orange/8.png new file mode 100644 index 0000000..b30d90e Binary files /dev/null and b/src/assets/themes/colors/orange/8.png differ diff --git a/src/assets/themes/colors/orange/flag.png b/src/assets/themes/colors/orange/flag.png new file mode 100644 index 0000000..b9e754d Binary files /dev/null and b/src/assets/themes/colors/orange/flag.png differ diff --git a/src/assets/themes/colors/orange/last-pos.png b/src/assets/themes/colors/orange/last-pos.png new file mode 100644 index 0000000..55c765f Binary files /dev/null and b/src/assets/themes/colors/orange/last-pos.png differ diff --git a/src/assets/themes/colors/orange/mine.png b/src/assets/themes/colors/orange/mine.png new file mode 100644 index 0000000..9d953f8 Binary files /dev/null and b/src/assets/themes/colors/orange/mine.png differ diff --git a/src/assets/themes/colors/orange/orange.aseprite b/src/assets/themes/colors/orange/orange.aseprite new file mode 100644 index 0000000..cb2ca9f Binary files /dev/null and b/src/assets/themes/colors/orange/orange.aseprite differ diff --git a/src/assets/themes/colors/orange/question-mark.png b/src/assets/themes/colors/orange/question-mark.png new file mode 100644 index 0000000..28994aa Binary files /dev/null and b/src/assets/themes/colors/orange/question-mark.png differ diff --git a/src/assets/themes/colors/orange/revealed.png b/src/assets/themes/colors/orange/revealed.png new file mode 100644 index 0000000..7611a03 Binary files /dev/null and b/src/assets/themes/colors/orange/revealed.png differ diff --git a/src/assets/themes/colors/orange/tile.png b/src/assets/themes/colors/orange/tile.png new file mode 100644 index 0000000..4782278 Binary files /dev/null and b/src/assets/themes/colors/orange/tile.png differ diff --git a/src/assets/themes/colors/pink/1.png b/src/assets/themes/colors/pink/1.png new file mode 100644 index 0000000..1aac07e Binary files /dev/null and b/src/assets/themes/colors/pink/1.png differ diff --git a/src/assets/themes/colors/pink/2.png b/src/assets/themes/colors/pink/2.png new file mode 100644 index 0000000..5af0864 Binary files /dev/null and b/src/assets/themes/colors/pink/2.png differ diff --git a/src/assets/themes/colors/pink/3.png b/src/assets/themes/colors/pink/3.png new file mode 100644 index 0000000..235595b Binary files /dev/null and b/src/assets/themes/colors/pink/3.png differ diff --git a/src/assets/themes/colors/pink/4.png b/src/assets/themes/colors/pink/4.png new file mode 100644 index 0000000..1534566 Binary files /dev/null and b/src/assets/themes/colors/pink/4.png differ diff --git a/src/assets/themes/colors/pink/5.png b/src/assets/themes/colors/pink/5.png new file mode 100644 index 0000000..bffd837 Binary files /dev/null and b/src/assets/themes/colors/pink/5.png differ diff --git a/src/assets/themes/colors/pink/6.png b/src/assets/themes/colors/pink/6.png new file mode 100644 index 0000000..8f0e90f Binary files /dev/null and b/src/assets/themes/colors/pink/6.png differ diff --git a/src/assets/themes/colors/pink/7.png b/src/assets/themes/colors/pink/7.png new file mode 100644 index 0000000..035df4e Binary files /dev/null and b/src/assets/themes/colors/pink/7.png differ diff --git a/src/assets/themes/colors/pink/8.png b/src/assets/themes/colors/pink/8.png new file mode 100644 index 0000000..9ad3f24 Binary files /dev/null and b/src/assets/themes/colors/pink/8.png differ diff --git a/src/assets/themes/colors/pink/flag.png b/src/assets/themes/colors/pink/flag.png new file mode 100644 index 0000000..ecade63 Binary files /dev/null and b/src/assets/themes/colors/pink/flag.png differ diff --git a/src/assets/themes/colors/pink/last-pos.png b/src/assets/themes/colors/pink/last-pos.png new file mode 100644 index 0000000..ab2feca Binary files /dev/null and b/src/assets/themes/colors/pink/last-pos.png differ diff --git a/src/assets/themes/colors/pink/mine.png b/src/assets/themes/colors/pink/mine.png new file mode 100644 index 0000000..a23b35e Binary files /dev/null and b/src/assets/themes/colors/pink/mine.png differ diff --git a/src/assets/themes/colors/pink/pink.aseprite b/src/assets/themes/colors/pink/pink.aseprite new file mode 100644 index 0000000..c4ae829 Binary files /dev/null and b/src/assets/themes/colors/pink/pink.aseprite differ diff --git a/src/assets/themes/colors/pink/question-mark.png b/src/assets/themes/colors/pink/question-mark.png new file mode 100644 index 0000000..5affe44 Binary files /dev/null and b/src/assets/themes/colors/pink/question-mark.png differ diff --git a/src/assets/themes/colors/pink/revealed.png b/src/assets/themes/colors/pink/revealed.png new file mode 100644 index 0000000..fecaad0 Binary files /dev/null and b/src/assets/themes/colors/pink/revealed.png differ diff --git a/src/assets/themes/colors/pink/tile.png b/src/assets/themes/colors/pink/tile.png new file mode 100644 index 0000000..78268e6 Binary files /dev/null and b/src/assets/themes/colors/pink/tile.png differ diff --git a/src/assets/themes/colors/purple/1.png b/src/assets/themes/colors/purple/1.png new file mode 100644 index 0000000..89a834f Binary files /dev/null and b/src/assets/themes/colors/purple/1.png differ diff --git a/src/assets/themes/colors/purple/2.png b/src/assets/themes/colors/purple/2.png new file mode 100644 index 0000000..4b02bc1 Binary files /dev/null and b/src/assets/themes/colors/purple/2.png differ diff --git a/src/assets/themes/colors/purple/3.png b/src/assets/themes/colors/purple/3.png new file mode 100644 index 0000000..100fb41 Binary files /dev/null and b/src/assets/themes/colors/purple/3.png differ diff --git a/src/assets/themes/colors/purple/4.png b/src/assets/themes/colors/purple/4.png new file mode 100644 index 0000000..0ce5dd2 Binary files /dev/null and b/src/assets/themes/colors/purple/4.png differ diff --git a/src/assets/themes/colors/purple/5.png b/src/assets/themes/colors/purple/5.png new file mode 100644 index 0000000..5c969d1 Binary files /dev/null and b/src/assets/themes/colors/purple/5.png differ diff --git a/src/assets/themes/colors/purple/6.png b/src/assets/themes/colors/purple/6.png new file mode 100644 index 0000000..d7928f8 Binary files /dev/null and b/src/assets/themes/colors/purple/6.png differ diff --git a/src/assets/themes/colors/purple/7.png b/src/assets/themes/colors/purple/7.png new file mode 100644 index 0000000..c471c6b Binary files /dev/null and b/src/assets/themes/colors/purple/7.png differ diff --git a/src/assets/themes/colors/purple/8.png b/src/assets/themes/colors/purple/8.png new file mode 100644 index 0000000..a6d2dfe Binary files /dev/null and b/src/assets/themes/colors/purple/8.png differ diff --git a/src/assets/themes/colors/purple/flag.png b/src/assets/themes/colors/purple/flag.png new file mode 100644 index 0000000..12ac377 Binary files /dev/null and b/src/assets/themes/colors/purple/flag.png differ diff --git a/src/assets/themes/colors/purple/last-pos.png b/src/assets/themes/colors/purple/last-pos.png new file mode 100644 index 0000000..1f4ceb0 Binary files /dev/null and b/src/assets/themes/colors/purple/last-pos.png differ diff --git a/src/assets/themes/colors/purple/mine.png b/src/assets/themes/colors/purple/mine.png new file mode 100644 index 0000000..b4d4dfb Binary files /dev/null and b/src/assets/themes/colors/purple/mine.png differ diff --git a/src/assets/themes/colors/purple/purple.aseprite b/src/assets/themes/colors/purple/purple.aseprite new file mode 100644 index 0000000..b0f9e58 Binary files /dev/null and b/src/assets/themes/colors/purple/purple.aseprite differ diff --git a/src/assets/themes/colors/purple/question-mark.png b/src/assets/themes/colors/purple/question-mark.png new file mode 100644 index 0000000..bdc5e81 Binary files /dev/null and b/src/assets/themes/colors/purple/question-mark.png differ diff --git a/src/assets/themes/colors/purple/revealed.png b/src/assets/themes/colors/purple/revealed.png new file mode 100644 index 0000000..166dcea Binary files /dev/null and b/src/assets/themes/colors/purple/revealed.png differ diff --git a/src/assets/themes/colors/purple/tile.png b/src/assets/themes/colors/purple/tile.png new file mode 100644 index 0000000..ef11fe4 Binary files /dev/null and b/src/assets/themes/colors/purple/tile.png differ diff --git a/src/assets/themes/colors/rainbow/last-pos.png b/src/assets/themes/colors/rainbow/last-pos.png new file mode 100644 index 0000000..cadbf00 Binary files /dev/null and b/src/assets/themes/colors/rainbow/last-pos.png differ diff --git a/src/assets/themes/colors/rainbow/mine.png b/src/assets/themes/colors/rainbow/mine.png new file mode 100644 index 0000000..d41298d Binary files /dev/null and b/src/assets/themes/colors/rainbow/mine.png differ diff --git a/src/assets/themes/colors/rainbow/rainbow.aseprite b/src/assets/themes/colors/rainbow/rainbow.aseprite new file mode 100644 index 0000000..039b954 Binary files /dev/null and b/src/assets/themes/colors/rainbow/rainbow.aseprite differ diff --git a/src/assets/themes/colors/red/1.png b/src/assets/themes/colors/red/1.png new file mode 100644 index 0000000..57cad2c Binary files /dev/null and b/src/assets/themes/colors/red/1.png differ diff --git a/src/assets/themes/colors/red/2.png b/src/assets/themes/colors/red/2.png new file mode 100644 index 0000000..b63d4a3 Binary files /dev/null and b/src/assets/themes/colors/red/2.png differ diff --git a/src/assets/themes/colors/red/3.png b/src/assets/themes/colors/red/3.png new file mode 100644 index 0000000..7c045ab Binary files /dev/null and b/src/assets/themes/colors/red/3.png differ diff --git a/src/assets/themes/colors/red/4.png b/src/assets/themes/colors/red/4.png new file mode 100644 index 0000000..5c8851d Binary files /dev/null and b/src/assets/themes/colors/red/4.png differ diff --git a/src/assets/themes/colors/red/5.png b/src/assets/themes/colors/red/5.png new file mode 100644 index 0000000..d61fb24 Binary files /dev/null and b/src/assets/themes/colors/red/5.png differ diff --git a/src/assets/themes/colors/red/6.png b/src/assets/themes/colors/red/6.png new file mode 100644 index 0000000..6067532 Binary files /dev/null and b/src/assets/themes/colors/red/6.png differ diff --git a/src/assets/themes/colors/red/7.png b/src/assets/themes/colors/red/7.png new file mode 100644 index 0000000..174e4f3 Binary files /dev/null and b/src/assets/themes/colors/red/7.png differ diff --git a/src/assets/themes/colors/red/8.png b/src/assets/themes/colors/red/8.png new file mode 100644 index 0000000..93b9672 Binary files /dev/null and b/src/assets/themes/colors/red/8.png differ diff --git a/src/assets/themes/colors/red/flag.png b/src/assets/themes/colors/red/flag.png new file mode 100644 index 0000000..2643cb2 Binary files /dev/null and b/src/assets/themes/colors/red/flag.png differ diff --git a/src/assets/themes/colors/red/last-pos.png b/src/assets/themes/colors/red/last-pos.png new file mode 100644 index 0000000..685a80f Binary files /dev/null and b/src/assets/themes/colors/red/last-pos.png differ diff --git a/src/assets/themes/colors/red/mine.png b/src/assets/themes/colors/red/mine.png new file mode 100644 index 0000000..c75d27b Binary files /dev/null and b/src/assets/themes/colors/red/mine.png differ diff --git a/src/assets/themes/colors/red/question-mark.png b/src/assets/themes/colors/red/question-mark.png new file mode 100644 index 0000000..df7bf2a Binary files /dev/null and b/src/assets/themes/colors/red/question-mark.png differ diff --git a/src/assets/themes/colors/red/red.aseprite b/src/assets/themes/colors/red/red.aseprite new file mode 100644 index 0000000..ec8df2f Binary files /dev/null and b/src/assets/themes/colors/red/red.aseprite differ diff --git a/src/assets/themes/colors/red/revealed.png b/src/assets/themes/colors/red/revealed.png new file mode 100644 index 0000000..67978a8 Binary files /dev/null and b/src/assets/themes/colors/red/revealed.png differ diff --git a/src/assets/themes/colors/red/tile.png b/src/assets/themes/colors/red/tile.png new file mode 100644 index 0000000..66fbb96 Binary files /dev/null and b/src/assets/themes/colors/red/tile.png differ diff --git a/src/assets/themes/colors/turquoise/1.png b/src/assets/themes/colors/turquoise/1.png new file mode 100644 index 0000000..2fef4dd Binary files /dev/null and b/src/assets/themes/colors/turquoise/1.png differ diff --git a/src/assets/themes/colors/turquoise/2.png b/src/assets/themes/colors/turquoise/2.png new file mode 100644 index 0000000..efd0a42 Binary files /dev/null and b/src/assets/themes/colors/turquoise/2.png differ diff --git a/src/assets/themes/colors/turquoise/3.png b/src/assets/themes/colors/turquoise/3.png new file mode 100644 index 0000000..d913c09 Binary files /dev/null and b/src/assets/themes/colors/turquoise/3.png differ diff --git a/src/assets/themes/colors/turquoise/4.png b/src/assets/themes/colors/turquoise/4.png new file mode 100644 index 0000000..ea42fef Binary files /dev/null and b/src/assets/themes/colors/turquoise/4.png differ diff --git a/src/assets/themes/colors/turquoise/5.png b/src/assets/themes/colors/turquoise/5.png new file mode 100644 index 0000000..cc7453d Binary files /dev/null and b/src/assets/themes/colors/turquoise/5.png differ diff --git a/src/assets/themes/colors/turquoise/6.png b/src/assets/themes/colors/turquoise/6.png new file mode 100644 index 0000000..a39d338 Binary files /dev/null and b/src/assets/themes/colors/turquoise/6.png differ diff --git a/src/assets/themes/colors/turquoise/7.png b/src/assets/themes/colors/turquoise/7.png new file mode 100644 index 0000000..49181ee Binary files /dev/null and b/src/assets/themes/colors/turquoise/7.png differ diff --git a/src/assets/themes/colors/turquoise/8.png b/src/assets/themes/colors/turquoise/8.png new file mode 100644 index 0000000..1bd5702 Binary files /dev/null and b/src/assets/themes/colors/turquoise/8.png differ diff --git a/src/assets/themes/colors/turquoise/flag.png b/src/assets/themes/colors/turquoise/flag.png new file mode 100644 index 0000000..5b29c0e Binary files /dev/null and b/src/assets/themes/colors/turquoise/flag.png differ diff --git a/src/assets/themes/colors/turquoise/last-pos.png b/src/assets/themes/colors/turquoise/last-pos.png new file mode 100644 index 0000000..e03f348 Binary files /dev/null and b/src/assets/themes/colors/turquoise/last-pos.png differ diff --git a/src/assets/themes/colors/turquoise/mine.png b/src/assets/themes/colors/turquoise/mine.png new file mode 100644 index 0000000..ae94e22 Binary files /dev/null and b/src/assets/themes/colors/turquoise/mine.png differ diff --git a/src/assets/themes/colors/turquoise/question-mark.png b/src/assets/themes/colors/turquoise/question-mark.png new file mode 100644 index 0000000..2616004 Binary files /dev/null and b/src/assets/themes/colors/turquoise/question-mark.png differ diff --git a/src/assets/themes/colors/turquoise/revealed.png b/src/assets/themes/colors/turquoise/revealed.png new file mode 100644 index 0000000..9b332ac Binary files /dev/null and b/src/assets/themes/colors/turquoise/revealed.png differ diff --git a/src/assets/themes/colors/turquoise/tile.png b/src/assets/themes/colors/turquoise/tile.png new file mode 100644 index 0000000..2e1eeeb Binary files /dev/null and b/src/assets/themes/colors/turquoise/tile.png differ diff --git a/src/assets/themes/colors/turquoise/turquoise.aseprite b/src/assets/themes/colors/turquoise/turquoise.aseprite new file mode 100644 index 0000000..eee1ba2 Binary files /dev/null and b/src/assets/themes/colors/turquoise/turquoise.aseprite differ diff --git a/src/assets/themes/colors/yellow/1.png b/src/assets/themes/colors/yellow/1.png new file mode 100644 index 0000000..a9c4a19 Binary files /dev/null and b/src/assets/themes/colors/yellow/1.png differ diff --git a/src/assets/themes/colors/yellow/2.png b/src/assets/themes/colors/yellow/2.png new file mode 100644 index 0000000..75ac087 Binary files /dev/null and b/src/assets/themes/colors/yellow/2.png differ diff --git a/src/assets/themes/colors/yellow/3.png b/src/assets/themes/colors/yellow/3.png new file mode 100644 index 0000000..783b091 Binary files /dev/null and b/src/assets/themes/colors/yellow/3.png differ diff --git a/src/assets/themes/colors/yellow/4.png b/src/assets/themes/colors/yellow/4.png new file mode 100644 index 0000000..5ce2b65 Binary files /dev/null and b/src/assets/themes/colors/yellow/4.png differ diff --git a/src/assets/themes/colors/yellow/5.png b/src/assets/themes/colors/yellow/5.png new file mode 100644 index 0000000..5429820 Binary files /dev/null and b/src/assets/themes/colors/yellow/5.png differ diff --git a/src/assets/themes/colors/yellow/6.png b/src/assets/themes/colors/yellow/6.png new file mode 100644 index 0000000..0ff2e70 Binary files /dev/null and b/src/assets/themes/colors/yellow/6.png differ diff --git a/src/assets/themes/colors/yellow/7.png b/src/assets/themes/colors/yellow/7.png new file mode 100644 index 0000000..aa6152c Binary files /dev/null and b/src/assets/themes/colors/yellow/7.png differ diff --git a/src/assets/themes/colors/yellow/8.png b/src/assets/themes/colors/yellow/8.png new file mode 100644 index 0000000..d87373d Binary files /dev/null and b/src/assets/themes/colors/yellow/8.png differ diff --git a/src/assets/themes/colors/yellow/flag.png b/src/assets/themes/colors/yellow/flag.png new file mode 100644 index 0000000..1b684c9 Binary files /dev/null and b/src/assets/themes/colors/yellow/flag.png differ diff --git a/src/assets/themes/colors/yellow/last-pos.png b/src/assets/themes/colors/yellow/last-pos.png new file mode 100644 index 0000000..befc1e1 Binary files /dev/null and b/src/assets/themes/colors/yellow/last-pos.png differ diff --git a/src/assets/themes/colors/yellow/mine.png b/src/assets/themes/colors/yellow/mine.png new file mode 100644 index 0000000..cec56da Binary files /dev/null and b/src/assets/themes/colors/yellow/mine.png differ diff --git a/src/assets/themes/colors/yellow/question-mark.png b/src/assets/themes/colors/yellow/question-mark.png new file mode 100644 index 0000000..4f2c226 Binary files /dev/null and b/src/assets/themes/colors/yellow/question-mark.png differ diff --git a/src/assets/themes/colors/yellow/revealed.png b/src/assets/themes/colors/yellow/revealed.png new file mode 100644 index 0000000..b01355d Binary files /dev/null and b/src/assets/themes/colors/yellow/revealed.png differ diff --git a/src/assets/themes/colors/yellow/tile.png b/src/assets/themes/colors/yellow/tile.png new file mode 100644 index 0000000..700f59e Binary files /dev/null and b/src/assets/themes/colors/yellow/tile.png differ diff --git a/src/assets/themes/colors/yellow/yellow.aseprite b/src/assets/themes/colors/yellow/yellow.aseprite new file mode 100644 index 0000000..1b61f91 Binary files /dev/null and b/src/assets/themes/colors/yellow/yellow.aseprite differ diff --git a/src/assets/themes/default/1.png b/src/assets/themes/default/1.png new file mode 100644 index 0000000..903d13e Binary files /dev/null and b/src/assets/themes/default/1.png differ diff --git a/src/assets/themes/default/2.png b/src/assets/themes/default/2.png new file mode 100644 index 0000000..10c6c65 Binary files /dev/null and b/src/assets/themes/default/2.png differ diff --git a/src/assets/themes/default/3.png b/src/assets/themes/default/3.png new file mode 100644 index 0000000..4752907 Binary files /dev/null and b/src/assets/themes/default/3.png differ diff --git a/src/assets/themes/default/4.png b/src/assets/themes/default/4.png new file mode 100644 index 0000000..180b785 Binary files /dev/null and b/src/assets/themes/default/4.png differ diff --git a/src/assets/themes/default/5.png b/src/assets/themes/default/5.png new file mode 100644 index 0000000..9e72f1d Binary files /dev/null and b/src/assets/themes/default/5.png differ diff --git a/src/assets/themes/default/6.png b/src/assets/themes/default/6.png new file mode 100644 index 0000000..9d78050 Binary files /dev/null and b/src/assets/themes/default/6.png differ diff --git a/src/assets/themes/default/7.png b/src/assets/themes/default/7.png new file mode 100644 index 0000000..edafc40 Binary files /dev/null and b/src/assets/themes/default/7.png differ diff --git a/src/assets/themes/default/8.png b/src/assets/themes/default/8.png new file mode 100644 index 0000000..645212e Binary files /dev/null and b/src/assets/themes/default/8.png differ diff --git a/src/assets/themes/default/default.aseprite b/src/assets/themes/default/default.aseprite new file mode 100644 index 0000000..02ab169 Binary files /dev/null and b/src/assets/themes/default/default.aseprite differ diff --git a/src/assets/themes/default/flag.png b/src/assets/themes/default/flag.png new file mode 100644 index 0000000..b9796ab Binary files /dev/null and b/src/assets/themes/default/flag.png differ diff --git a/src/assets/themes/default/last-pos.png b/src/assets/themes/default/last-pos.png new file mode 100644 index 0000000..f8f2226 Binary files /dev/null and b/src/assets/themes/default/last-pos.png differ diff --git a/src/assets/themes/default/mine.png b/src/assets/themes/default/mine.png new file mode 100644 index 0000000..45c2d6d Binary files /dev/null and b/src/assets/themes/default/mine.png differ diff --git a/src/assets/themes/default/question-mark.png b/src/assets/themes/default/question-mark.png new file mode 100644 index 0000000..afd85ba Binary files /dev/null and b/src/assets/themes/default/question-mark.png differ diff --git a/src/assets/themes/default/revealed.png b/src/assets/themes/default/revealed.png new file mode 100644 index 0000000..2cdf0f7 Binary files /dev/null and b/src/assets/themes/default/revealed.png differ diff --git a/src/assets/themes/default/tile.png b/src/assets/themes/default/tile.png new file mode 100644 index 0000000..1ded545 Binary files /dev/null and b/src/assets/themes/default/tile.png differ diff --git a/src/assets/themes/devart/flag.png b/src/assets/themes/devart/flag.png new file mode 100644 index 0000000..13d8f43 Binary files /dev/null and b/src/assets/themes/devart/flag.png differ diff --git a/src/assets/themes/devart/last-pos.png b/src/assets/themes/devart/last-pos.png new file mode 100644 index 0000000..51e3964 Binary files /dev/null and b/src/assets/themes/devart/last-pos.png differ diff --git a/src/assets/themes/devart/mine.png b/src/assets/themes/devart/mine.png new file mode 100644 index 0000000..1c78169 Binary files /dev/null and b/src/assets/themes/devart/mine.png differ diff --git a/src/assets/themes/devart/question-mark.png b/src/assets/themes/devart/question-mark.png new file mode 100644 index 0000000..0a44491 Binary files /dev/null and b/src/assets/themes/devart/question-mark.png differ diff --git a/src/assets/themes/devart/revealed.png b/src/assets/themes/devart/revealed.png new file mode 100644 index 0000000..c925de7 Binary files /dev/null and b/src/assets/themes/devart/revealed.png differ diff --git a/src/assets/themes/devart/tile.png b/src/assets/themes/devart/tile.png new file mode 100644 index 0000000..648e90e Binary files /dev/null and b/src/assets/themes/devart/tile.png differ diff --git a/src/assets/themes/dinos/1.png b/src/assets/themes/dinos/1.png new file mode 100644 index 0000000..17ff91b Binary files /dev/null and b/src/assets/themes/dinos/1.png differ diff --git a/src/assets/themes/dinos/2.png b/src/assets/themes/dinos/2.png new file mode 100644 index 0000000..0e34dda Binary files /dev/null and b/src/assets/themes/dinos/2.png differ diff --git a/src/assets/themes/dinos/3.png b/src/assets/themes/dinos/3.png new file mode 100644 index 0000000..d1680da Binary files /dev/null and b/src/assets/themes/dinos/3.png differ diff --git a/src/assets/themes/dinos/4.png b/src/assets/themes/dinos/4.png new file mode 100644 index 0000000..66a5517 Binary files /dev/null and b/src/assets/themes/dinos/4.png differ diff --git a/src/assets/themes/dinos/5.png b/src/assets/themes/dinos/5.png new file mode 100644 index 0000000..9a14be7 Binary files /dev/null and b/src/assets/themes/dinos/5.png differ diff --git a/src/assets/themes/dinos/6.png b/src/assets/themes/dinos/6.png new file mode 100644 index 0000000..94bfee9 Binary files /dev/null and b/src/assets/themes/dinos/6.png differ diff --git a/src/assets/themes/dinos/7.png b/src/assets/themes/dinos/7.png new file mode 100644 index 0000000..cfeff40 Binary files /dev/null and b/src/assets/themes/dinos/7.png differ diff --git a/src/assets/themes/dinos/8.png b/src/assets/themes/dinos/8.png new file mode 100644 index 0000000..e22e81e Binary files /dev/null and b/src/assets/themes/dinos/8.png differ diff --git a/src/assets/themes/dinos/dinos.aseprite b/src/assets/themes/dinos/dinos.aseprite new file mode 100644 index 0000000..80e42e1 Binary files /dev/null and b/src/assets/themes/dinos/dinos.aseprite differ diff --git a/src/assets/themes/dinos/flag.png b/src/assets/themes/dinos/flag.png new file mode 100644 index 0000000..5052527 Binary files /dev/null and b/src/assets/themes/dinos/flag.png differ diff --git a/src/assets/themes/dinos/last-pos.png b/src/assets/themes/dinos/last-pos.png new file mode 100644 index 0000000..fd8a213 Binary files /dev/null and b/src/assets/themes/dinos/last-pos.png differ diff --git a/src/assets/themes/dinos/mine-1.png b/src/assets/themes/dinos/mine-1.png new file mode 100644 index 0000000..1d9a0b6 Binary files /dev/null and b/src/assets/themes/dinos/mine-1.png differ diff --git a/src/assets/themes/dinos/mine-2.png b/src/assets/themes/dinos/mine-2.png new file mode 100644 index 0000000..c3bdb9c Binary files /dev/null and b/src/assets/themes/dinos/mine-2.png differ diff --git a/src/assets/themes/dinos/question-mark.png b/src/assets/themes/dinos/question-mark.png new file mode 100644 index 0000000..982dec1 Binary files /dev/null and b/src/assets/themes/dinos/question-mark.png differ diff --git a/src/assets/themes/dinos/revealed-1.png b/src/assets/themes/dinos/revealed-1.png new file mode 100644 index 0000000..99a27b2 Binary files /dev/null and b/src/assets/themes/dinos/revealed-1.png differ diff --git a/src/assets/themes/dinos/revealed-2.png b/src/assets/themes/dinos/revealed-2.png new file mode 100644 index 0000000..100874c Binary files /dev/null and b/src/assets/themes/dinos/revealed-2.png differ diff --git a/src/assets/themes/dinos/tile.png b/src/assets/themes/dinos/tile.png new file mode 100644 index 0000000..0e2cc31 Binary files /dev/null and b/src/assets/themes/dinos/tile.png differ diff --git a/src/assets/themes/elden-ring/1.png b/src/assets/themes/elden-ring/1.png new file mode 100644 index 0000000..5f5c929 Binary files /dev/null and b/src/assets/themes/elden-ring/1.png differ diff --git a/src/assets/themes/elden-ring/2.png b/src/assets/themes/elden-ring/2.png new file mode 100644 index 0000000..792f926 Binary files /dev/null and b/src/assets/themes/elden-ring/2.png differ diff --git a/src/assets/themes/elden-ring/3.png b/src/assets/themes/elden-ring/3.png new file mode 100644 index 0000000..64a6952 Binary files /dev/null and b/src/assets/themes/elden-ring/3.png differ diff --git a/src/assets/themes/elden-ring/4.png b/src/assets/themes/elden-ring/4.png new file mode 100644 index 0000000..14e9c49 Binary files /dev/null and b/src/assets/themes/elden-ring/4.png differ diff --git a/src/assets/themes/elden-ring/5.png b/src/assets/themes/elden-ring/5.png new file mode 100644 index 0000000..f91b370 Binary files /dev/null and b/src/assets/themes/elden-ring/5.png differ diff --git a/src/assets/themes/elden-ring/6.png b/src/assets/themes/elden-ring/6.png new file mode 100644 index 0000000..96047a0 Binary files /dev/null and b/src/assets/themes/elden-ring/6.png differ diff --git a/src/assets/themes/elden-ring/7.png b/src/assets/themes/elden-ring/7.png new file mode 100644 index 0000000..89df3d7 Binary files /dev/null and b/src/assets/themes/elden-ring/7.png differ diff --git a/src/assets/themes/elden-ring/8.png b/src/assets/themes/elden-ring/8.png new file mode 100644 index 0000000..c8be1fc Binary files /dev/null and b/src/assets/themes/elden-ring/8.png differ diff --git a/src/assets/themes/elden-ring/elden-ring.aseprite b/src/assets/themes/elden-ring/elden-ring.aseprite new file mode 100644 index 0000000..c783f19 Binary files /dev/null and b/src/assets/themes/elden-ring/elden-ring.aseprite differ diff --git a/src/assets/themes/elden-ring/flag.png b/src/assets/themes/elden-ring/flag.png new file mode 100644 index 0000000..9b32efe Binary files /dev/null and b/src/assets/themes/elden-ring/flag.png differ diff --git a/src/assets/themes/elden-ring/last-pos.png b/src/assets/themes/elden-ring/last-pos.png new file mode 100644 index 0000000..172ef27 Binary files /dev/null and b/src/assets/themes/elden-ring/last-pos.png differ diff --git a/src/assets/themes/elden-ring/mine.png b/src/assets/themes/elden-ring/mine.png new file mode 100644 index 0000000..9296273 Binary files /dev/null and b/src/assets/themes/elden-ring/mine.png differ diff --git a/src/assets/themes/elden-ring/question-mark.png b/src/assets/themes/elden-ring/question-mark.png new file mode 100644 index 0000000..a5a7a4f Binary files /dev/null and b/src/assets/themes/elden-ring/question-mark.png differ diff --git a/src/assets/themes/elden-ring/revealed.png b/src/assets/themes/elden-ring/revealed.png new file mode 100644 index 0000000..4d2fbef Binary files /dev/null and b/src/assets/themes/elden-ring/revealed.png differ diff --git a/src/assets/themes/elden-ring/tile.png b/src/assets/themes/elden-ring/tile.png new file mode 100644 index 0000000..1886c0c Binary files /dev/null and b/src/assets/themes/elden-ring/tile.png differ diff --git a/src/assets/themes/farm/1.png b/src/assets/themes/farm/1.png new file mode 100644 index 0000000..efc9452 Binary files /dev/null and b/src/assets/themes/farm/1.png differ diff --git a/src/assets/themes/farm/2.png b/src/assets/themes/farm/2.png new file mode 100644 index 0000000..f566225 Binary files /dev/null and b/src/assets/themes/farm/2.png differ diff --git a/src/assets/themes/farm/3.png b/src/assets/themes/farm/3.png new file mode 100644 index 0000000..5e11423 Binary files /dev/null and b/src/assets/themes/farm/3.png differ diff --git a/src/assets/themes/farm/4.png b/src/assets/themes/farm/4.png new file mode 100644 index 0000000..42cee55 Binary files /dev/null and b/src/assets/themes/farm/4.png differ diff --git a/src/assets/themes/farm/5.png b/src/assets/themes/farm/5.png new file mode 100644 index 0000000..5fe48eb Binary files /dev/null and b/src/assets/themes/farm/5.png differ diff --git a/src/assets/themes/farm/6.png b/src/assets/themes/farm/6.png new file mode 100644 index 0000000..908ac56 Binary files /dev/null and b/src/assets/themes/farm/6.png differ diff --git a/src/assets/themes/farm/7.png b/src/assets/themes/farm/7.png new file mode 100644 index 0000000..b0d8152 Binary files /dev/null and b/src/assets/themes/farm/7.png differ diff --git a/src/assets/themes/farm/8.png b/src/assets/themes/farm/8.png new file mode 100644 index 0000000..1df22f8 Binary files /dev/null and b/src/assets/themes/farm/8.png differ diff --git a/src/assets/themes/farm/farm.aseprite b/src/assets/themes/farm/farm.aseprite new file mode 100644 index 0000000..8650726 Binary files /dev/null and b/src/assets/themes/farm/farm.aseprite differ diff --git a/src/assets/themes/farm/flag.png b/src/assets/themes/farm/flag.png new file mode 100644 index 0000000..699e4ea Binary files /dev/null and b/src/assets/themes/farm/flag.png differ diff --git a/src/assets/themes/farm/last-pos.png b/src/assets/themes/farm/last-pos.png new file mode 100644 index 0000000..3325076 Binary files /dev/null and b/src/assets/themes/farm/last-pos.png differ diff --git a/src/assets/themes/farm/mine.png b/src/assets/themes/farm/mine.png new file mode 100644 index 0000000..a3cc38d Binary files /dev/null and b/src/assets/themes/farm/mine.png differ diff --git a/src/assets/themes/farm/question-mark.png b/src/assets/themes/farm/question-mark.png new file mode 100644 index 0000000..b475cab Binary files /dev/null and b/src/assets/themes/farm/question-mark.png differ diff --git a/src/assets/themes/farm/revealed.png b/src/assets/themes/farm/revealed.png new file mode 100644 index 0000000..929ae38 Binary files /dev/null and b/src/assets/themes/farm/revealed.png differ diff --git a/src/assets/themes/farm/tile.png b/src/assets/themes/farm/tile.png new file mode 100644 index 0000000..8588c0c Binary files /dev/null and b/src/assets/themes/farm/tile.png differ diff --git a/src/assets/themes/flowers/1.png b/src/assets/themes/flowers/1.png new file mode 100644 index 0000000..176b686 Binary files /dev/null and b/src/assets/themes/flowers/1.png differ diff --git a/src/assets/themes/flowers/2.png b/src/assets/themes/flowers/2.png new file mode 100644 index 0000000..d55b830 Binary files /dev/null and b/src/assets/themes/flowers/2.png differ diff --git a/src/assets/themes/flowers/3.png b/src/assets/themes/flowers/3.png new file mode 100644 index 0000000..92c447b Binary files /dev/null and b/src/assets/themes/flowers/3.png differ diff --git a/src/assets/themes/flowers/4.png b/src/assets/themes/flowers/4.png new file mode 100644 index 0000000..17b1c54 Binary files /dev/null and b/src/assets/themes/flowers/4.png differ diff --git a/src/assets/themes/flowers/5.png b/src/assets/themes/flowers/5.png new file mode 100644 index 0000000..2c1f13f Binary files /dev/null and b/src/assets/themes/flowers/5.png differ diff --git a/src/assets/themes/flowers/6.png b/src/assets/themes/flowers/6.png new file mode 100644 index 0000000..263941f Binary files /dev/null and b/src/assets/themes/flowers/6.png differ diff --git a/src/assets/themes/flowers/7.png b/src/assets/themes/flowers/7.png new file mode 100644 index 0000000..06cc6d5 Binary files /dev/null and b/src/assets/themes/flowers/7.png differ diff --git a/src/assets/themes/flowers/8.png b/src/assets/themes/flowers/8.png new file mode 100644 index 0000000..781b272 Binary files /dev/null and b/src/assets/themes/flowers/8.png differ diff --git a/src/assets/themes/flowers/flag.png b/src/assets/themes/flowers/flag.png new file mode 100644 index 0000000..c9b324a Binary files /dev/null and b/src/assets/themes/flowers/flag.png differ diff --git a/src/assets/themes/flowers/flowers.aseprite b/src/assets/themes/flowers/flowers.aseprite new file mode 100644 index 0000000..b091d1e Binary files /dev/null and b/src/assets/themes/flowers/flowers.aseprite differ diff --git a/src/assets/themes/flowers/last-pos.png b/src/assets/themes/flowers/last-pos.png new file mode 100644 index 0000000..0d67e8c Binary files /dev/null and b/src/assets/themes/flowers/last-pos.png differ diff --git a/src/assets/themes/flowers/mine.png b/src/assets/themes/flowers/mine.png new file mode 100644 index 0000000..9290432 Binary files /dev/null and b/src/assets/themes/flowers/mine.png differ diff --git a/src/assets/themes/flowers/question-mark.png b/src/assets/themes/flowers/question-mark.png new file mode 100644 index 0000000..8320f53 Binary files /dev/null and b/src/assets/themes/flowers/question-mark.png differ diff --git a/src/assets/themes/flowers/revealed.png b/src/assets/themes/flowers/revealed.png new file mode 100644 index 0000000..9d8e5dc Binary files /dev/null and b/src/assets/themes/flowers/revealed.png differ diff --git a/src/assets/themes/flowers/tile.png b/src/assets/themes/flowers/tile.png new file mode 100644 index 0000000..a9044ac Binary files /dev/null and b/src/assets/themes/flowers/tile.png differ diff --git a/src/assets/themes/halli-galli/1.png b/src/assets/themes/halli-galli/1.png new file mode 100644 index 0000000..af89178 Binary files /dev/null and b/src/assets/themes/halli-galli/1.png differ diff --git a/src/assets/themes/halli-galli/2.png b/src/assets/themes/halli-galli/2.png new file mode 100644 index 0000000..009631c Binary files /dev/null and b/src/assets/themes/halli-galli/2.png differ diff --git a/src/assets/themes/halli-galli/3.png b/src/assets/themes/halli-galli/3.png new file mode 100644 index 0000000..51bb902 Binary files /dev/null and b/src/assets/themes/halli-galli/3.png differ diff --git a/src/assets/themes/halli-galli/4.png b/src/assets/themes/halli-galli/4.png new file mode 100644 index 0000000..04d7da1 Binary files /dev/null and b/src/assets/themes/halli-galli/4.png differ diff --git a/src/assets/themes/halli-galli/5.png b/src/assets/themes/halli-galli/5.png new file mode 100644 index 0000000..129a60e Binary files /dev/null and b/src/assets/themes/halli-galli/5.png differ diff --git a/src/assets/themes/halli-galli/6.png b/src/assets/themes/halli-galli/6.png new file mode 100644 index 0000000..d427ff0 Binary files /dev/null and b/src/assets/themes/halli-galli/6.png differ diff --git a/src/assets/themes/halli-galli/7.png b/src/assets/themes/halli-galli/7.png new file mode 100644 index 0000000..c50991f Binary files /dev/null and b/src/assets/themes/halli-galli/7.png differ diff --git a/src/assets/themes/halli-galli/8.png b/src/assets/themes/halli-galli/8.png new file mode 100644 index 0000000..4ddc070 Binary files /dev/null and b/src/assets/themes/halli-galli/8.png differ diff --git a/src/assets/themes/halli-galli/flag.png b/src/assets/themes/halli-galli/flag.png new file mode 100644 index 0000000..b1c8abb Binary files /dev/null and b/src/assets/themes/halli-galli/flag.png differ diff --git a/src/assets/themes/halli-galli/halli-galli.aseprite b/src/assets/themes/halli-galli/halli-galli.aseprite new file mode 100644 index 0000000..be4d2fa Binary files /dev/null and b/src/assets/themes/halli-galli/halli-galli.aseprite differ diff --git a/src/assets/themes/halli-galli/last-pos.png b/src/assets/themes/halli-galli/last-pos.png new file mode 100644 index 0000000..af9ca08 Binary files /dev/null and b/src/assets/themes/halli-galli/last-pos.png differ diff --git a/src/assets/themes/halli-galli/mine.png b/src/assets/themes/halli-galli/mine.png new file mode 100644 index 0000000..54790e0 Binary files /dev/null and b/src/assets/themes/halli-galli/mine.png differ diff --git a/src/assets/themes/halli-galli/question-mark.png b/src/assets/themes/halli-galli/question-mark.png new file mode 100644 index 0000000..229798c Binary files /dev/null and b/src/assets/themes/halli-galli/question-mark.png differ diff --git a/src/assets/themes/halli-galli/revealed.png b/src/assets/themes/halli-galli/revealed.png new file mode 100644 index 0000000..50653fe Binary files /dev/null and b/src/assets/themes/halli-galli/revealed.png differ diff --git a/src/assets/themes/halli-galli/tile.png b/src/assets/themes/halli-galli/tile.png new file mode 100644 index 0000000..a298b73 Binary files /dev/null and b/src/assets/themes/halli-galli/tile.png differ diff --git a/src/assets/themes/insects/1.png b/src/assets/themes/insects/1.png new file mode 100644 index 0000000..5624d5d Binary files /dev/null and b/src/assets/themes/insects/1.png differ diff --git a/src/assets/themes/insects/2.png b/src/assets/themes/insects/2.png new file mode 100644 index 0000000..c1dd329 Binary files /dev/null and b/src/assets/themes/insects/2.png differ diff --git a/src/assets/themes/insects/3.png b/src/assets/themes/insects/3.png new file mode 100644 index 0000000..95d56b6 Binary files /dev/null and b/src/assets/themes/insects/3.png differ diff --git a/src/assets/themes/insects/4.png b/src/assets/themes/insects/4.png new file mode 100644 index 0000000..c9cb9c4 Binary files /dev/null and b/src/assets/themes/insects/4.png differ diff --git a/src/assets/themes/insects/5.png b/src/assets/themes/insects/5.png new file mode 100644 index 0000000..3f44809 Binary files /dev/null and b/src/assets/themes/insects/5.png differ diff --git a/src/assets/themes/insects/6.png b/src/assets/themes/insects/6.png new file mode 100644 index 0000000..212323a Binary files /dev/null and b/src/assets/themes/insects/6.png differ diff --git a/src/assets/themes/insects/7.png b/src/assets/themes/insects/7.png new file mode 100644 index 0000000..30ff0c6 Binary files /dev/null and b/src/assets/themes/insects/7.png differ diff --git a/src/assets/themes/insects/8.png b/src/assets/themes/insects/8.png new file mode 100644 index 0000000..dd08b41 Binary files /dev/null and b/src/assets/themes/insects/8.png differ diff --git a/src/assets/themes/insects/flag.png b/src/assets/themes/insects/flag.png new file mode 100644 index 0000000..3d4a31c Binary files /dev/null and b/src/assets/themes/insects/flag.png differ diff --git a/src/assets/themes/insects/insects.aseprite b/src/assets/themes/insects/insects.aseprite new file mode 100644 index 0000000..8d3f5e0 Binary files /dev/null and b/src/assets/themes/insects/insects.aseprite differ diff --git a/src/assets/themes/insects/last-pos.png b/src/assets/themes/insects/last-pos.png new file mode 100644 index 0000000..cde7867 Binary files /dev/null and b/src/assets/themes/insects/last-pos.png differ diff --git a/src/assets/themes/insects/mine-1.png b/src/assets/themes/insects/mine-1.png new file mode 100644 index 0000000..86ef348 Binary files /dev/null and b/src/assets/themes/insects/mine-1.png differ diff --git a/src/assets/themes/insects/mine-2.png b/src/assets/themes/insects/mine-2.png new file mode 100644 index 0000000..075284d Binary files /dev/null and b/src/assets/themes/insects/mine-2.png differ diff --git a/src/assets/themes/insects/mine.png b/src/assets/themes/insects/mine.png new file mode 100644 index 0000000..45c2d6d Binary files /dev/null and b/src/assets/themes/insects/mine.png differ diff --git a/src/assets/themes/insects/question-mark.png b/src/assets/themes/insects/question-mark.png new file mode 100644 index 0000000..3fd5d25 Binary files /dev/null and b/src/assets/themes/insects/question-mark.png differ diff --git a/src/assets/themes/insects/revealed.png b/src/assets/themes/insects/revealed.png new file mode 100644 index 0000000..bbe0926 Binary files /dev/null and b/src/assets/themes/insects/revealed.png differ diff --git a/src/assets/themes/insects/tile.png b/src/assets/themes/insects/tile.png new file mode 100644 index 0000000..68d9d37 Binary files /dev/null and b/src/assets/themes/insects/tile.png differ diff --git a/src/assets/themes/isaac/1.png b/src/assets/themes/isaac/1.png new file mode 100644 index 0000000..b18177f Binary files /dev/null and b/src/assets/themes/isaac/1.png differ diff --git a/src/assets/themes/isaac/2.png b/src/assets/themes/isaac/2.png new file mode 100644 index 0000000..de6ded6 Binary files /dev/null and b/src/assets/themes/isaac/2.png differ diff --git a/src/assets/themes/isaac/3.png b/src/assets/themes/isaac/3.png new file mode 100644 index 0000000..3fa72fc Binary files /dev/null and b/src/assets/themes/isaac/3.png differ diff --git a/src/assets/themes/isaac/4.png b/src/assets/themes/isaac/4.png new file mode 100644 index 0000000..6de5076 Binary files /dev/null and b/src/assets/themes/isaac/4.png differ diff --git a/src/assets/themes/isaac/5.png b/src/assets/themes/isaac/5.png new file mode 100644 index 0000000..4aade8c Binary files /dev/null and b/src/assets/themes/isaac/5.png differ diff --git a/src/assets/themes/isaac/6.png b/src/assets/themes/isaac/6.png new file mode 100644 index 0000000..2daec4b Binary files /dev/null and b/src/assets/themes/isaac/6.png differ diff --git a/src/assets/themes/isaac/7.png b/src/assets/themes/isaac/7.png new file mode 100644 index 0000000..1e280b3 Binary files /dev/null and b/src/assets/themes/isaac/7.png differ diff --git a/src/assets/themes/isaac/8.png b/src/assets/themes/isaac/8.png new file mode 100644 index 0000000..f935498 Binary files /dev/null and b/src/assets/themes/isaac/8.png differ diff --git a/src/assets/themes/isaac/flag.png b/src/assets/themes/isaac/flag.png new file mode 100644 index 0000000..6045baf Binary files /dev/null and b/src/assets/themes/isaac/flag.png differ diff --git a/src/assets/themes/isaac/isaac.aseprite b/src/assets/themes/isaac/isaac.aseprite new file mode 100644 index 0000000..0524973 Binary files /dev/null and b/src/assets/themes/isaac/isaac.aseprite differ diff --git a/src/assets/themes/isaac/last-pos.png b/src/assets/themes/isaac/last-pos.png new file mode 100644 index 0000000..536d892 Binary files /dev/null and b/src/assets/themes/isaac/last-pos.png differ diff --git a/src/assets/themes/isaac/mine-1.png b/src/assets/themes/isaac/mine-1.png new file mode 100644 index 0000000..9470638 Binary files /dev/null and b/src/assets/themes/isaac/mine-1.png differ diff --git a/src/assets/themes/isaac/mine-2.png b/src/assets/themes/isaac/mine-2.png new file mode 100644 index 0000000..5b54620 Binary files /dev/null and b/src/assets/themes/isaac/mine-2.png differ diff --git a/src/assets/themes/isaac/mine-3.png b/src/assets/themes/isaac/mine-3.png new file mode 100644 index 0000000..915f91b Binary files /dev/null and b/src/assets/themes/isaac/mine-3.png differ diff --git a/src/assets/themes/isaac/question-mark.png b/src/assets/themes/isaac/question-mark.png new file mode 100644 index 0000000..2f42098 Binary files /dev/null and b/src/assets/themes/isaac/question-mark.png differ diff --git a/src/assets/themes/isaac/revealed.png b/src/assets/themes/isaac/revealed.png new file mode 100644 index 0000000..80460b2 Binary files /dev/null and b/src/assets/themes/isaac/revealed.png differ diff --git a/src/assets/themes/isaac/tile.png b/src/assets/themes/isaac/tile.png new file mode 100644 index 0000000..98ed19c Binary files /dev/null and b/src/assets/themes/isaac/tile.png differ diff --git a/src/assets/themes/janitor-tresh/1.png b/src/assets/themes/janitor-tresh/1.png new file mode 100644 index 0000000..a7d58c0 Binary files /dev/null and b/src/assets/themes/janitor-tresh/1.png differ diff --git a/src/assets/themes/janitor-tresh/2.png b/src/assets/themes/janitor-tresh/2.png new file mode 100644 index 0000000..ed6d673 Binary files /dev/null and b/src/assets/themes/janitor-tresh/2.png differ diff --git a/src/assets/themes/janitor-tresh/3.png b/src/assets/themes/janitor-tresh/3.png new file mode 100644 index 0000000..90d0be0 Binary files /dev/null and b/src/assets/themes/janitor-tresh/3.png differ diff --git a/src/assets/themes/janitor-tresh/4.png b/src/assets/themes/janitor-tresh/4.png new file mode 100644 index 0000000..a31883d Binary files /dev/null and b/src/assets/themes/janitor-tresh/4.png differ diff --git a/src/assets/themes/janitor-tresh/5.png b/src/assets/themes/janitor-tresh/5.png new file mode 100644 index 0000000..61870b0 Binary files /dev/null and b/src/assets/themes/janitor-tresh/5.png differ diff --git a/src/assets/themes/janitor-tresh/6.png b/src/assets/themes/janitor-tresh/6.png new file mode 100644 index 0000000..25b00d1 Binary files /dev/null and b/src/assets/themes/janitor-tresh/6.png differ diff --git a/src/assets/themes/janitor-tresh/7.png b/src/assets/themes/janitor-tresh/7.png new file mode 100644 index 0000000..9bdaeea Binary files /dev/null and b/src/assets/themes/janitor-tresh/7.png differ diff --git a/src/assets/themes/janitor-tresh/8.png b/src/assets/themes/janitor-tresh/8.png new file mode 100644 index 0000000..ba0f04a Binary files /dev/null and b/src/assets/themes/janitor-tresh/8.png differ diff --git a/src/assets/themes/janitor-tresh/flag.png b/src/assets/themes/janitor-tresh/flag.png new file mode 100644 index 0000000..d46e6ba Binary files /dev/null and b/src/assets/themes/janitor-tresh/flag.png differ diff --git a/src/assets/themes/janitor-tresh/janitor-tresh.aseprite b/src/assets/themes/janitor-tresh/janitor-tresh.aseprite new file mode 100644 index 0000000..8b36f12 Binary files /dev/null and b/src/assets/themes/janitor-tresh/janitor-tresh.aseprite differ diff --git a/src/assets/themes/janitor-tresh/last-pos.png b/src/assets/themes/janitor-tresh/last-pos.png new file mode 100644 index 0000000..953e246 Binary files /dev/null and b/src/assets/themes/janitor-tresh/last-pos.png differ diff --git a/src/assets/themes/janitor-tresh/mine.png b/src/assets/themes/janitor-tresh/mine.png new file mode 100644 index 0000000..d7e8a2b Binary files /dev/null and b/src/assets/themes/janitor-tresh/mine.png differ diff --git a/src/assets/themes/janitor-tresh/question-mark.png b/src/assets/themes/janitor-tresh/question-mark.png new file mode 100644 index 0000000..fad8466 Binary files /dev/null and b/src/assets/themes/janitor-tresh/question-mark.png differ diff --git a/src/assets/themes/janitor-tresh/revealed.png b/src/assets/themes/janitor-tresh/revealed.png new file mode 100644 index 0000000..868f8a9 Binary files /dev/null and b/src/assets/themes/janitor-tresh/revealed.png differ diff --git a/src/assets/themes/janitor-tresh/tile.png b/src/assets/themes/janitor-tresh/tile.png new file mode 100644 index 0000000..43a794e Binary files /dev/null and b/src/assets/themes/janitor-tresh/tile.png differ diff --git a/src/assets/themes/league/1.png b/src/assets/themes/league/1.png new file mode 100644 index 0000000..551e65a Binary files /dev/null and b/src/assets/themes/league/1.png differ diff --git a/src/assets/themes/league/2.png b/src/assets/themes/league/2.png new file mode 100644 index 0000000..c03cc18 Binary files /dev/null and b/src/assets/themes/league/2.png differ diff --git a/src/assets/themes/league/3.png b/src/assets/themes/league/3.png new file mode 100644 index 0000000..17ef5b5 Binary files /dev/null and b/src/assets/themes/league/3.png differ diff --git a/src/assets/themes/league/4.png b/src/assets/themes/league/4.png new file mode 100644 index 0000000..8cb2344 Binary files /dev/null and b/src/assets/themes/league/4.png differ diff --git a/src/assets/themes/league/5.png b/src/assets/themes/league/5.png new file mode 100644 index 0000000..3e0c501 Binary files /dev/null and b/src/assets/themes/league/5.png differ diff --git a/src/assets/themes/league/6.png b/src/assets/themes/league/6.png new file mode 100644 index 0000000..1111e57 Binary files /dev/null and b/src/assets/themes/league/6.png differ diff --git a/src/assets/themes/league/7.png b/src/assets/themes/league/7.png new file mode 100644 index 0000000..04e6cf5 Binary files /dev/null and b/src/assets/themes/league/7.png differ diff --git a/src/assets/themes/league/8.png b/src/assets/themes/league/8.png new file mode 100644 index 0000000..33a13e4 Binary files /dev/null and b/src/assets/themes/league/8.png differ diff --git a/src/assets/themes/league/last-pos.png b/src/assets/themes/league/last-pos.png new file mode 100644 index 0000000..0b5fa37 Binary files /dev/null and b/src/assets/themes/league/last-pos.png differ diff --git a/src/assets/themes/league/league.aseprite b/src/assets/themes/league/league.aseprite new file mode 100644 index 0000000..db1d5cd Binary files /dev/null and b/src/assets/themes/league/league.aseprite differ diff --git a/src/assets/themes/league/question-mark.png b/src/assets/themes/league/question-mark.png new file mode 100644 index 0000000..422572d Binary files /dev/null and b/src/assets/themes/league/question-mark.png differ diff --git a/src/assets/themes/league/revealed.png b/src/assets/themes/league/revealed.png new file mode 100644 index 0000000..62c984d Binary files /dev/null and b/src/assets/themes/league/revealed.png differ diff --git a/src/assets/themes/league/teemo/flag.png b/src/assets/themes/league/teemo/flag.png new file mode 100644 index 0000000..5f4b69b Binary files /dev/null and b/src/assets/themes/league/teemo/flag.png differ diff --git a/src/assets/themes/league/teemo/mine.png b/src/assets/themes/league/teemo/mine.png new file mode 100644 index 0000000..b4b74b8 Binary files /dev/null and b/src/assets/themes/league/teemo/mine.png differ diff --git a/src/assets/themes/league/teemo/teemo.aseprite b/src/assets/themes/league/teemo/teemo.aseprite new file mode 100644 index 0000000..89ec107 Binary files /dev/null and b/src/assets/themes/league/teemo/teemo.aseprite differ diff --git a/src/assets/themes/league/tile-1.png b/src/assets/themes/league/tile-1.png new file mode 100644 index 0000000..8212e75 Binary files /dev/null and b/src/assets/themes/league/tile-1.png differ diff --git a/src/assets/themes/league/tile-2.png b/src/assets/themes/league/tile-2.png new file mode 100644 index 0000000..d830a09 Binary files /dev/null and b/src/assets/themes/league/tile-2.png differ diff --git a/src/assets/themes/league/ziggs/flag.png b/src/assets/themes/league/ziggs/flag.png new file mode 100644 index 0000000..02c454a Binary files /dev/null and b/src/assets/themes/league/ziggs/flag.png differ diff --git a/src/assets/themes/league/ziggs/mine.png b/src/assets/themes/league/ziggs/mine.png new file mode 100644 index 0000000..26d5b0f Binary files /dev/null and b/src/assets/themes/league/ziggs/mine.png differ diff --git a/src/assets/themes/league/ziggs/ziggs.aseprite b/src/assets/themes/league/ziggs/ziggs.aseprite new file mode 100644 index 0000000..f8fac8e Binary files /dev/null and b/src/assets/themes/league/ziggs/ziggs.aseprite differ diff --git a/src/assets/themes/mine-dogs/1.png b/src/assets/themes/mine-dogs/1.png new file mode 100644 index 0000000..bd6681f Binary files /dev/null and b/src/assets/themes/mine-dogs/1.png differ diff --git a/src/assets/themes/mine-dogs/2.png b/src/assets/themes/mine-dogs/2.png new file mode 100644 index 0000000..3f21360 Binary files /dev/null and b/src/assets/themes/mine-dogs/2.png differ diff --git a/src/assets/themes/mine-dogs/3.png b/src/assets/themes/mine-dogs/3.png new file mode 100644 index 0000000..3b74353 Binary files /dev/null and b/src/assets/themes/mine-dogs/3.png differ diff --git a/src/assets/themes/mine-dogs/4.png b/src/assets/themes/mine-dogs/4.png new file mode 100644 index 0000000..22db0cb Binary files /dev/null and b/src/assets/themes/mine-dogs/4.png differ diff --git a/src/assets/themes/mine-dogs/5.png b/src/assets/themes/mine-dogs/5.png new file mode 100644 index 0000000..09d6902 Binary files /dev/null and b/src/assets/themes/mine-dogs/5.png differ diff --git a/src/assets/themes/mine-dogs/6.png b/src/assets/themes/mine-dogs/6.png new file mode 100644 index 0000000..9ae7d0d Binary files /dev/null and b/src/assets/themes/mine-dogs/6.png differ diff --git a/src/assets/themes/mine-dogs/7.png b/src/assets/themes/mine-dogs/7.png new file mode 100644 index 0000000..e2333a3 Binary files /dev/null and b/src/assets/themes/mine-dogs/7.png differ diff --git a/src/assets/themes/mine-dogs/8.png b/src/assets/themes/mine-dogs/8.png new file mode 100644 index 0000000..ea58e2e Binary files /dev/null and b/src/assets/themes/mine-dogs/8.png differ diff --git a/src/assets/themes/mine-dogs/flag-1.png b/src/assets/themes/mine-dogs/flag-1.png new file mode 100644 index 0000000..4359665 Binary files /dev/null and b/src/assets/themes/mine-dogs/flag-1.png differ diff --git a/src/assets/themes/mine-dogs/flag-2.png b/src/assets/themes/mine-dogs/flag-2.png new file mode 100644 index 0000000..29fff0a Binary files /dev/null and b/src/assets/themes/mine-dogs/flag-2.png differ diff --git a/src/assets/themes/mine-dogs/last-pos.png b/src/assets/themes/mine-dogs/last-pos.png new file mode 100644 index 0000000..40c008a Binary files /dev/null and b/src/assets/themes/mine-dogs/last-pos.png differ diff --git a/src/assets/themes/mine-dogs/mine-dogs.aseprite b/src/assets/themes/mine-dogs/mine-dogs.aseprite new file mode 100644 index 0000000..4bc8f89 Binary files /dev/null and b/src/assets/themes/mine-dogs/mine-dogs.aseprite differ diff --git a/src/assets/themes/mine-dogs/mine.png b/src/assets/themes/mine-dogs/mine.png new file mode 100644 index 0000000..2b2b123 Binary files /dev/null and b/src/assets/themes/mine-dogs/mine.png differ diff --git a/src/assets/themes/mine-dogs/question-mark.png b/src/assets/themes/mine-dogs/question-mark.png new file mode 100644 index 0000000..0948157 Binary files /dev/null and b/src/assets/themes/mine-dogs/question-mark.png differ diff --git a/src/assets/themes/mine-dogs/revealed.png b/src/assets/themes/mine-dogs/revealed.png new file mode 100644 index 0000000..5bc4746 Binary files /dev/null and b/src/assets/themes/mine-dogs/revealed.png differ diff --git a/src/assets/themes/mine-dogs/tile.png b/src/assets/themes/mine-dogs/tile.png new file mode 100644 index 0000000..6eaa358 Binary files /dev/null and b/src/assets/themes/mine-dogs/tile.png differ diff --git a/src/assets/themes/minecraft-nether/1.png b/src/assets/themes/minecraft-nether/1.png new file mode 100644 index 0000000..52ce4f5 Binary files /dev/null and b/src/assets/themes/minecraft-nether/1.png differ diff --git a/src/assets/themes/minecraft-nether/2.png b/src/assets/themes/minecraft-nether/2.png new file mode 100644 index 0000000..b944a7b Binary files /dev/null and b/src/assets/themes/minecraft-nether/2.png differ diff --git a/src/assets/themes/minecraft-nether/3.png b/src/assets/themes/minecraft-nether/3.png new file mode 100644 index 0000000..ed934ad Binary files /dev/null and b/src/assets/themes/minecraft-nether/3.png differ diff --git a/src/assets/themes/minecraft-nether/4.png b/src/assets/themes/minecraft-nether/4.png new file mode 100644 index 0000000..b6d84f1 Binary files /dev/null and b/src/assets/themes/minecraft-nether/4.png differ diff --git a/src/assets/themes/minecraft-nether/5.png b/src/assets/themes/minecraft-nether/5.png new file mode 100644 index 0000000..759d5d9 Binary files /dev/null and b/src/assets/themes/minecraft-nether/5.png differ diff --git a/src/assets/themes/minecraft-nether/6.png b/src/assets/themes/minecraft-nether/6.png new file mode 100644 index 0000000..7407d0f Binary files /dev/null and b/src/assets/themes/minecraft-nether/6.png differ diff --git a/src/assets/themes/minecraft-nether/7.png b/src/assets/themes/minecraft-nether/7.png new file mode 100644 index 0000000..fcdca13 Binary files /dev/null and b/src/assets/themes/minecraft-nether/7.png differ diff --git a/src/assets/themes/minecraft-nether/8.png b/src/assets/themes/minecraft-nether/8.png new file mode 100644 index 0000000..aeb33af Binary files /dev/null and b/src/assets/themes/minecraft-nether/8.png differ diff --git a/src/assets/themes/minecraft-nether/flag.png b/src/assets/themes/minecraft-nether/flag.png new file mode 100644 index 0000000..adc1778 Binary files /dev/null and b/src/assets/themes/minecraft-nether/flag.png differ diff --git a/src/assets/themes/minecraft-nether/last-pos.png b/src/assets/themes/minecraft-nether/last-pos.png new file mode 100644 index 0000000..ac2a480 Binary files /dev/null and b/src/assets/themes/minecraft-nether/last-pos.png differ diff --git a/src/assets/themes/minecraft-nether/mine.png b/src/assets/themes/minecraft-nether/mine.png new file mode 100644 index 0000000..bad0c00 Binary files /dev/null and b/src/assets/themes/minecraft-nether/mine.png differ diff --git a/src/assets/themes/minecraft-nether/minecraft-nether.aseprite b/src/assets/themes/minecraft-nether/minecraft-nether.aseprite new file mode 100644 index 0000000..7b6336b Binary files /dev/null and b/src/assets/themes/minecraft-nether/minecraft-nether.aseprite differ diff --git a/src/assets/themes/minecraft-nether/question-mark.png b/src/assets/themes/minecraft-nether/question-mark.png new file mode 100644 index 0000000..d7719d9 Binary files /dev/null and b/src/assets/themes/minecraft-nether/question-mark.png differ diff --git a/src/assets/themes/minecraft-nether/revealed.png b/src/assets/themes/minecraft-nether/revealed.png new file mode 100644 index 0000000..cff40dd Binary files /dev/null and b/src/assets/themes/minecraft-nether/revealed.png differ diff --git a/src/assets/themes/minecraft-nether/tile.png b/src/assets/themes/minecraft-nether/tile.png new file mode 100644 index 0000000..1723fa1 Binary files /dev/null and b/src/assets/themes/minecraft-nether/tile.png differ diff --git a/src/assets/themes/minecraft-overworld/1.png b/src/assets/themes/minecraft-overworld/1.png new file mode 100644 index 0000000..2d98685 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/1.png differ diff --git a/src/assets/themes/minecraft-overworld/2.png b/src/assets/themes/minecraft-overworld/2.png new file mode 100644 index 0000000..86ecc45 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/2.png differ diff --git a/src/assets/themes/minecraft-overworld/3.png b/src/assets/themes/minecraft-overworld/3.png new file mode 100644 index 0000000..a05708e Binary files /dev/null and b/src/assets/themes/minecraft-overworld/3.png differ diff --git a/src/assets/themes/minecraft-overworld/4.png b/src/assets/themes/minecraft-overworld/4.png new file mode 100644 index 0000000..162eb0d Binary files /dev/null and b/src/assets/themes/minecraft-overworld/4.png differ diff --git a/src/assets/themes/minecraft-overworld/5.png b/src/assets/themes/minecraft-overworld/5.png new file mode 100644 index 0000000..6d57419 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/5.png differ diff --git a/src/assets/themes/minecraft-overworld/6.png b/src/assets/themes/minecraft-overworld/6.png new file mode 100644 index 0000000..e5f8176 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/6.png differ diff --git a/src/assets/themes/minecraft-overworld/7.png b/src/assets/themes/minecraft-overworld/7.png new file mode 100644 index 0000000..06046f4 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/7.png differ diff --git a/src/assets/themes/minecraft-overworld/8.png b/src/assets/themes/minecraft-overworld/8.png new file mode 100644 index 0000000..254790e Binary files /dev/null and b/src/assets/themes/minecraft-overworld/8.png differ diff --git a/src/assets/themes/minecraft-overworld/flag.png b/src/assets/themes/minecraft-overworld/flag.png new file mode 100644 index 0000000..953785a Binary files /dev/null and b/src/assets/themes/minecraft-overworld/flag.png differ diff --git a/src/assets/themes/minecraft-overworld/last-pos.png b/src/assets/themes/minecraft-overworld/last-pos.png new file mode 100644 index 0000000..fa0306a Binary files /dev/null and b/src/assets/themes/minecraft-overworld/last-pos.png differ diff --git a/src/assets/themes/minecraft-overworld/mine.png b/src/assets/themes/minecraft-overworld/mine.png new file mode 100644 index 0000000..855e604 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/mine.png differ diff --git a/src/assets/themes/minecraft-overworld/minecraft-overworld.aseprite b/src/assets/themes/minecraft-overworld/minecraft-overworld.aseprite new file mode 100644 index 0000000..68764f1 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/minecraft-overworld.aseprite differ diff --git a/src/assets/themes/minecraft-overworld/question-mark.png b/src/assets/themes/minecraft-overworld/question-mark.png new file mode 100644 index 0000000..e144f9c Binary files /dev/null and b/src/assets/themes/minecraft-overworld/question-mark.png differ diff --git a/src/assets/themes/minecraft-overworld/revealed.png b/src/assets/themes/minecraft-overworld/revealed.png new file mode 100644 index 0000000..36fbc27 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/revealed.png differ diff --git a/src/assets/themes/minecraft-overworld/tile.png b/src/assets/themes/minecraft-overworld/tile.png new file mode 100644 index 0000000..e585b78 Binary files /dev/null and b/src/assets/themes/minecraft-overworld/tile.png differ diff --git a/src/assets/themes/poop/1.png b/src/assets/themes/poop/1.png new file mode 100644 index 0000000..4b96a1e Binary files /dev/null and b/src/assets/themes/poop/1.png differ diff --git a/src/assets/themes/poop/2.png b/src/assets/themes/poop/2.png new file mode 100644 index 0000000..2c2b449 Binary files /dev/null and b/src/assets/themes/poop/2.png differ diff --git a/src/assets/themes/poop/3.png b/src/assets/themes/poop/3.png new file mode 100644 index 0000000..82eabec Binary files /dev/null and b/src/assets/themes/poop/3.png differ diff --git a/src/assets/themes/poop/4.png b/src/assets/themes/poop/4.png new file mode 100644 index 0000000..88b44f0 Binary files /dev/null and b/src/assets/themes/poop/4.png differ diff --git a/src/assets/themes/poop/5.png b/src/assets/themes/poop/5.png new file mode 100644 index 0000000..f224427 Binary files /dev/null and b/src/assets/themes/poop/5.png differ diff --git a/src/assets/themes/poop/6.png b/src/assets/themes/poop/6.png new file mode 100644 index 0000000..05e7e34 Binary files /dev/null and b/src/assets/themes/poop/6.png differ diff --git a/src/assets/themes/poop/7.png b/src/assets/themes/poop/7.png new file mode 100644 index 0000000..0bcf5c8 Binary files /dev/null and b/src/assets/themes/poop/7.png differ diff --git a/src/assets/themes/poop/8.png b/src/assets/themes/poop/8.png new file mode 100644 index 0000000..411ab36 Binary files /dev/null and b/src/assets/themes/poop/8.png differ diff --git a/src/assets/themes/poop/flag.png b/src/assets/themes/poop/flag.png new file mode 100644 index 0000000..bdc31ae Binary files /dev/null and b/src/assets/themes/poop/flag.png differ diff --git a/src/assets/themes/poop/last-pos.png b/src/assets/themes/poop/last-pos.png new file mode 100644 index 0000000..3b064b6 Binary files /dev/null and b/src/assets/themes/poop/last-pos.png differ diff --git a/src/assets/themes/poop/mine.png b/src/assets/themes/poop/mine.png new file mode 100644 index 0000000..8211f9b Binary files /dev/null and b/src/assets/themes/poop/mine.png differ diff --git a/src/assets/themes/poop/poop.aseprite b/src/assets/themes/poop/poop.aseprite new file mode 100644 index 0000000..aa7d67e Binary files /dev/null and b/src/assets/themes/poop/poop.aseprite differ diff --git a/src/assets/themes/poop/question-mark.png b/src/assets/themes/poop/question-mark.png new file mode 100644 index 0000000..de8bd9e Binary files /dev/null and b/src/assets/themes/poop/question-mark.png differ diff --git a/src/assets/themes/poop/revealed.png b/src/assets/themes/poop/revealed.png new file mode 100644 index 0000000..5f230a5 Binary files /dev/null and b/src/assets/themes/poop/revealed.png differ diff --git a/src/assets/themes/poop/tile.png b/src/assets/themes/poop/tile.png new file mode 100644 index 0000000..d8a8041 Binary files /dev/null and b/src/assets/themes/poop/tile.png differ diff --git a/src/assets/themes/retro-wave/1.png b/src/assets/themes/retro-wave/1.png new file mode 100644 index 0000000..42cdcbd Binary files /dev/null and b/src/assets/themes/retro-wave/1.png differ diff --git a/src/assets/themes/retro-wave/2.png b/src/assets/themes/retro-wave/2.png new file mode 100644 index 0000000..35f6531 Binary files /dev/null and b/src/assets/themes/retro-wave/2.png differ diff --git a/src/assets/themes/retro-wave/3.png b/src/assets/themes/retro-wave/3.png new file mode 100644 index 0000000..ffa674b Binary files /dev/null and b/src/assets/themes/retro-wave/3.png differ diff --git a/src/assets/themes/retro-wave/4.png b/src/assets/themes/retro-wave/4.png new file mode 100644 index 0000000..ddb863d Binary files /dev/null and b/src/assets/themes/retro-wave/4.png differ diff --git a/src/assets/themes/retro-wave/5.png b/src/assets/themes/retro-wave/5.png new file mode 100644 index 0000000..a14e8f6 Binary files /dev/null and b/src/assets/themes/retro-wave/5.png differ diff --git a/src/assets/themes/retro-wave/6.png b/src/assets/themes/retro-wave/6.png new file mode 100644 index 0000000..dc47302 Binary files /dev/null and b/src/assets/themes/retro-wave/6.png differ diff --git a/src/assets/themes/retro-wave/7.png b/src/assets/themes/retro-wave/7.png new file mode 100644 index 0000000..50a878b Binary files /dev/null and b/src/assets/themes/retro-wave/7.png differ diff --git a/src/assets/themes/retro-wave/8.png b/src/assets/themes/retro-wave/8.png new file mode 100644 index 0000000..9e9f20a Binary files /dev/null and b/src/assets/themes/retro-wave/8.png differ diff --git a/src/assets/themes/retro-wave/flag.png b/src/assets/themes/retro-wave/flag.png new file mode 100644 index 0000000..770576d Binary files /dev/null and b/src/assets/themes/retro-wave/flag.png differ diff --git a/src/assets/themes/retro-wave/last-pos.png b/src/assets/themes/retro-wave/last-pos.png new file mode 100644 index 0000000..4c52a11 Binary files /dev/null and b/src/assets/themes/retro-wave/last-pos.png differ diff --git a/src/assets/themes/retro-wave/mine.png b/src/assets/themes/retro-wave/mine.png new file mode 100644 index 0000000..1094ec2 Binary files /dev/null and b/src/assets/themes/retro-wave/mine.png differ diff --git a/src/assets/themes/retro-wave/question-mark.png b/src/assets/themes/retro-wave/question-mark.png new file mode 100644 index 0000000..e4cfd4d Binary files /dev/null and b/src/assets/themes/retro-wave/question-mark.png differ diff --git a/src/assets/themes/retro-wave/retro-wave.aseprite b/src/assets/themes/retro-wave/retro-wave.aseprite new file mode 100644 index 0000000..2ae44ff Binary files /dev/null and b/src/assets/themes/retro-wave/retro-wave.aseprite differ diff --git a/src/assets/themes/retro-wave/revealed.png b/src/assets/themes/retro-wave/revealed.png new file mode 100644 index 0000000..77480be Binary files /dev/null and b/src/assets/themes/retro-wave/revealed.png differ diff --git a/src/assets/themes/retro-wave/tile.png b/src/assets/themes/retro-wave/tile.png new file mode 100644 index 0000000..7552db2 Binary files /dev/null and b/src/assets/themes/retro-wave/tile.png differ diff --git a/src/assets/themes/romance/1.png b/src/assets/themes/romance/1.png new file mode 100644 index 0000000..2a74075 Binary files /dev/null and b/src/assets/themes/romance/1.png differ diff --git a/src/assets/themes/romance/2.png b/src/assets/themes/romance/2.png new file mode 100644 index 0000000..eef55a4 Binary files /dev/null and b/src/assets/themes/romance/2.png differ diff --git a/src/assets/themes/romance/3.png b/src/assets/themes/romance/3.png new file mode 100644 index 0000000..e849825 Binary files /dev/null and b/src/assets/themes/romance/3.png differ diff --git a/src/assets/themes/romance/4.png b/src/assets/themes/romance/4.png new file mode 100644 index 0000000..3fc1c77 Binary files /dev/null and b/src/assets/themes/romance/4.png differ diff --git a/src/assets/themes/romance/5.png b/src/assets/themes/romance/5.png new file mode 100644 index 0000000..3afd0ed Binary files /dev/null and b/src/assets/themes/romance/5.png differ diff --git a/src/assets/themes/romance/6.png b/src/assets/themes/romance/6.png new file mode 100644 index 0000000..049d696 Binary files /dev/null and b/src/assets/themes/romance/6.png differ diff --git a/src/assets/themes/romance/7.png b/src/assets/themes/romance/7.png new file mode 100644 index 0000000..bb3d0a1 Binary files /dev/null and b/src/assets/themes/romance/7.png differ diff --git a/src/assets/themes/romance/8.png b/src/assets/themes/romance/8.png new file mode 100644 index 0000000..60407d9 Binary files /dev/null and b/src/assets/themes/romance/8.png differ diff --git a/src/assets/themes/romance/flag.png b/src/assets/themes/romance/flag.png new file mode 100644 index 0000000..eea6c2d Binary files /dev/null and b/src/assets/themes/romance/flag.png differ diff --git a/src/assets/themes/romance/last-pos.png b/src/assets/themes/romance/last-pos.png new file mode 100644 index 0000000..ab2feca Binary files /dev/null and b/src/assets/themes/romance/last-pos.png differ diff --git a/src/assets/themes/romance/mine.png b/src/assets/themes/romance/mine.png new file mode 100644 index 0000000..ee23f57 Binary files /dev/null and b/src/assets/themes/romance/mine.png differ diff --git a/src/assets/themes/romance/question-mark.png b/src/assets/themes/romance/question-mark.png new file mode 100644 index 0000000..5affe44 Binary files /dev/null and b/src/assets/themes/romance/question-mark.png differ diff --git a/src/assets/themes/romance/revealed.png b/src/assets/themes/romance/revealed.png new file mode 100644 index 0000000..7bc1ead Binary files /dev/null and b/src/assets/themes/romance/revealed.png differ diff --git a/src/assets/themes/romance/romance.aseprite b/src/assets/themes/romance/romance.aseprite new file mode 100644 index 0000000..73ec746 Binary files /dev/null and b/src/assets/themes/romance/romance.aseprite differ diff --git a/src/assets/themes/romance/tile.png b/src/assets/themes/romance/tile.png new file mode 100644 index 0000000..59713bd Binary files /dev/null and b/src/assets/themes/romance/tile.png differ diff --git a/src/assets/themes/techies/dire/1.png b/src/assets/themes/techies/dire/1.png new file mode 100644 index 0000000..3b4f6f1 Binary files /dev/null and b/src/assets/themes/techies/dire/1.png differ diff --git a/src/assets/themes/techies/dire/2.png b/src/assets/themes/techies/dire/2.png new file mode 100644 index 0000000..41b1152 Binary files /dev/null and b/src/assets/themes/techies/dire/2.png differ diff --git a/src/assets/themes/techies/dire/3.png b/src/assets/themes/techies/dire/3.png new file mode 100644 index 0000000..ff7b5a4 Binary files /dev/null and b/src/assets/themes/techies/dire/3.png differ diff --git a/src/assets/themes/techies/dire/4.png b/src/assets/themes/techies/dire/4.png new file mode 100644 index 0000000..8bbe20d Binary files /dev/null and b/src/assets/themes/techies/dire/4.png differ diff --git a/src/assets/themes/techies/dire/5.png b/src/assets/themes/techies/dire/5.png new file mode 100644 index 0000000..6dd53d0 Binary files /dev/null and b/src/assets/themes/techies/dire/5.png differ diff --git a/src/assets/themes/techies/dire/6.png b/src/assets/themes/techies/dire/6.png new file mode 100644 index 0000000..b6a259b Binary files /dev/null and b/src/assets/themes/techies/dire/6.png differ diff --git a/src/assets/themes/techies/dire/7.png b/src/assets/themes/techies/dire/7.png new file mode 100644 index 0000000..d0660b7 Binary files /dev/null and b/src/assets/themes/techies/dire/7.png differ diff --git a/src/assets/themes/techies/dire/8.png b/src/assets/themes/techies/dire/8.png new file mode 100644 index 0000000..7ecab96 Binary files /dev/null and b/src/assets/themes/techies/dire/8.png differ diff --git a/src/assets/themes/techies/dire/dire.aseprite b/src/assets/themes/techies/dire/dire.aseprite new file mode 100644 index 0000000..0a154e2 Binary files /dev/null and b/src/assets/themes/techies/dire/dire.aseprite differ diff --git a/src/assets/themes/techies/dire/last-pos.png b/src/assets/themes/techies/dire/last-pos.png new file mode 100644 index 0000000..63a4aa2 Binary files /dev/null and b/src/assets/themes/techies/dire/last-pos.png differ diff --git a/src/assets/themes/techies/dire/mine-1.png b/src/assets/themes/techies/dire/mine-1.png new file mode 100644 index 0000000..f6dd5e4 Binary files /dev/null and b/src/assets/themes/techies/dire/mine-1.png differ diff --git a/src/assets/themes/techies/dire/mine-2.png b/src/assets/themes/techies/dire/mine-2.png new file mode 100644 index 0000000..1219db7 Binary files /dev/null and b/src/assets/themes/techies/dire/mine-2.png differ diff --git a/src/assets/themes/techies/dire/question-mark.png b/src/assets/themes/techies/dire/question-mark.png new file mode 100644 index 0000000..a9404aa Binary files /dev/null and b/src/assets/themes/techies/dire/question-mark.png differ diff --git a/src/assets/themes/techies/dire/revealed.png b/src/assets/themes/techies/dire/revealed.png new file mode 100644 index 0000000..9efd866 Binary files /dev/null and b/src/assets/themes/techies/dire/revealed.png differ diff --git a/src/assets/themes/techies/dire/tile-1.png b/src/assets/themes/techies/dire/tile-1.png new file mode 100644 index 0000000..e7fa90c Binary files /dev/null and b/src/assets/themes/techies/dire/tile-1.png differ diff --git a/src/assets/themes/techies/dire/tile-2.png b/src/assets/themes/techies/dire/tile-2.png new file mode 100644 index 0000000..6644915 Binary files /dev/null and b/src/assets/themes/techies/dire/tile-2.png differ diff --git a/src/assets/themes/techies/dire/tile-3.png b/src/assets/themes/techies/dire/tile-3.png new file mode 100644 index 0000000..d80f1b4 Binary files /dev/null and b/src/assets/themes/techies/dire/tile-3.png differ diff --git a/src/assets/themes/techies/flag.png b/src/assets/themes/techies/flag.png new file mode 100644 index 0000000..faf24c4 Binary files /dev/null and b/src/assets/themes/techies/flag.png differ diff --git a/src/assets/themes/techies/radiant/1.png b/src/assets/themes/techies/radiant/1.png new file mode 100644 index 0000000..0b40d23 Binary files /dev/null and b/src/assets/themes/techies/radiant/1.png differ diff --git a/src/assets/themes/techies/radiant/2.png b/src/assets/themes/techies/radiant/2.png new file mode 100644 index 0000000..112aa13 Binary files /dev/null and b/src/assets/themes/techies/radiant/2.png differ diff --git a/src/assets/themes/techies/radiant/3.png b/src/assets/themes/techies/radiant/3.png new file mode 100644 index 0000000..17dadbd Binary files /dev/null and b/src/assets/themes/techies/radiant/3.png differ diff --git a/src/assets/themes/techies/radiant/4.png b/src/assets/themes/techies/radiant/4.png new file mode 100644 index 0000000..a1ea14b Binary files /dev/null and b/src/assets/themes/techies/radiant/4.png differ diff --git a/src/assets/themes/techies/radiant/5.png b/src/assets/themes/techies/radiant/5.png new file mode 100644 index 0000000..00ee9c9 Binary files /dev/null and b/src/assets/themes/techies/radiant/5.png differ diff --git a/src/assets/themes/techies/radiant/6.png b/src/assets/themes/techies/radiant/6.png new file mode 100644 index 0000000..309fdab Binary files /dev/null and b/src/assets/themes/techies/radiant/6.png differ diff --git a/src/assets/themes/techies/radiant/7.png b/src/assets/themes/techies/radiant/7.png new file mode 100644 index 0000000..1b3a528 Binary files /dev/null and b/src/assets/themes/techies/radiant/7.png differ diff --git a/src/assets/themes/techies/radiant/8.png b/src/assets/themes/techies/radiant/8.png new file mode 100644 index 0000000..262e439 Binary files /dev/null and b/src/assets/themes/techies/radiant/8.png differ diff --git a/src/assets/themes/techies/radiant/last-pos.png b/src/assets/themes/techies/radiant/last-pos.png new file mode 100644 index 0000000..57fc926 Binary files /dev/null and b/src/assets/themes/techies/radiant/last-pos.png differ diff --git a/src/assets/themes/techies/radiant/mine-1.png b/src/assets/themes/techies/radiant/mine-1.png new file mode 100644 index 0000000..2925527 Binary files /dev/null and b/src/assets/themes/techies/radiant/mine-1.png differ diff --git a/src/assets/themes/techies/radiant/mine-2.png b/src/assets/themes/techies/radiant/mine-2.png new file mode 100644 index 0000000..8a557a2 Binary files /dev/null and b/src/assets/themes/techies/radiant/mine-2.png differ diff --git a/src/assets/themes/techies/radiant/question-mark.png b/src/assets/themes/techies/radiant/question-mark.png new file mode 100644 index 0000000..90505f1 Binary files /dev/null and b/src/assets/themes/techies/radiant/question-mark.png differ diff --git a/src/assets/themes/techies/radiant/radiant.aseprite b/src/assets/themes/techies/radiant/radiant.aseprite new file mode 100644 index 0000000..18c5c97 Binary files /dev/null and b/src/assets/themes/techies/radiant/radiant.aseprite differ diff --git a/src/assets/themes/techies/radiant/revealed-1.png b/src/assets/themes/techies/radiant/revealed-1.png new file mode 100644 index 0000000..6d0c32d Binary files /dev/null and b/src/assets/themes/techies/radiant/revealed-1.png differ diff --git a/src/assets/themes/techies/radiant/revealed-2.png b/src/assets/themes/techies/radiant/revealed-2.png new file mode 100644 index 0000000..5855318 Binary files /dev/null and b/src/assets/themes/techies/radiant/revealed-2.png differ diff --git a/src/assets/themes/techies/radiant/revealed-3.png b/src/assets/themes/techies/radiant/revealed-3.png new file mode 100644 index 0000000..38e5ae8 Binary files /dev/null and b/src/assets/themes/techies/radiant/revealed-3.png differ diff --git a/src/assets/themes/techies/radiant/tile-1.png b/src/assets/themes/techies/radiant/tile-1.png new file mode 100644 index 0000000..0fd588f Binary files /dev/null and b/src/assets/themes/techies/radiant/tile-1.png differ diff --git a/src/assets/themes/techies/radiant/tile-2.png b/src/assets/themes/techies/radiant/tile-2.png new file mode 100644 index 0000000..6a1152b Binary files /dev/null and b/src/assets/themes/techies/radiant/tile-2.png differ diff --git a/src/assets/themes/techies/radiant/tile-3.png b/src/assets/themes/techies/radiant/tile-3.png new file mode 100644 index 0000000..2d55d2e Binary files /dev/null and b/src/assets/themes/techies/radiant/tile-3.png differ diff --git a/src/assets/themes/tron/tron-blue/1.png b/src/assets/themes/tron/tron-blue/1.png new file mode 100644 index 0000000..0b1e9a7 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/1.png differ diff --git a/src/assets/themes/tron/tron-blue/2.png b/src/assets/themes/tron/tron-blue/2.png new file mode 100644 index 0000000..35f6531 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/2.png differ diff --git a/src/assets/themes/tron/tron-blue/3.png b/src/assets/themes/tron/tron-blue/3.png new file mode 100644 index 0000000..fc91ab5 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/3.png differ diff --git a/src/assets/themes/tron/tron-blue/4.png b/src/assets/themes/tron/tron-blue/4.png new file mode 100644 index 0000000..1fd7208 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/4.png differ diff --git a/src/assets/themes/tron/tron-blue/5.png b/src/assets/themes/tron/tron-blue/5.png new file mode 100644 index 0000000..9bff40b Binary files /dev/null and b/src/assets/themes/tron/tron-blue/5.png differ diff --git a/src/assets/themes/tron/tron-blue/6.png b/src/assets/themes/tron/tron-blue/6.png new file mode 100644 index 0000000..dc47302 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/6.png differ diff --git a/src/assets/themes/tron/tron-blue/7.png b/src/assets/themes/tron/tron-blue/7.png new file mode 100644 index 0000000..b49baba Binary files /dev/null and b/src/assets/themes/tron/tron-blue/7.png differ diff --git a/src/assets/themes/tron/tron-blue/8.png b/src/assets/themes/tron/tron-blue/8.png new file mode 100644 index 0000000..953d527 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/8.png differ diff --git a/src/assets/themes/tron/tron-blue/flag.png b/src/assets/themes/tron/tron-blue/flag.png new file mode 100644 index 0000000..899c07d Binary files /dev/null and b/src/assets/themes/tron/tron-blue/flag.png differ diff --git a/src/assets/themes/tron/tron-blue/last-pos.png b/src/assets/themes/tron/tron-blue/last-pos.png new file mode 100644 index 0000000..5d8bc34 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/last-pos.png differ diff --git a/src/assets/themes/tron/tron-blue/mine.png b/src/assets/themes/tron/tron-blue/mine.png new file mode 100644 index 0000000..f3c3472 Binary files /dev/null and b/src/assets/themes/tron/tron-blue/mine.png differ diff --git a/src/assets/themes/tron/tron-blue/question-mark.png b/src/assets/themes/tron/tron-blue/question-mark.png new file mode 100644 index 0000000..e4cfd4d Binary files /dev/null and b/src/assets/themes/tron/tron-blue/question-mark.png differ diff --git a/src/assets/themes/tron/tron-blue/revealed.png b/src/assets/themes/tron/tron-blue/revealed.png new file mode 100644 index 0000000..77480be Binary files /dev/null and b/src/assets/themes/tron/tron-blue/revealed.png differ diff --git a/src/assets/themes/tron/tron-blue/tile.png b/src/assets/themes/tron/tron-blue/tile.png new file mode 100644 index 0000000..be335be Binary files /dev/null and b/src/assets/themes/tron/tron-blue/tile.png differ diff --git a/src/assets/themes/tron/tron-blue/tron-blue.aseprite b/src/assets/themes/tron/tron-blue/tron-blue.aseprite new file mode 100644 index 0000000..b6156eb Binary files /dev/null and b/src/assets/themes/tron/tron-blue/tron-blue.aseprite differ diff --git a/src/assets/themes/tron/tron-orange/1.png b/src/assets/themes/tron/tron-orange/1.png new file mode 100644 index 0000000..169533e Binary files /dev/null and b/src/assets/themes/tron/tron-orange/1.png differ diff --git a/src/assets/themes/tron/tron-orange/2.png b/src/assets/themes/tron/tron-orange/2.png new file mode 100644 index 0000000..7b5b864 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/2.png differ diff --git a/src/assets/themes/tron/tron-orange/3.png b/src/assets/themes/tron/tron-orange/3.png new file mode 100644 index 0000000..9799bd7 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/3.png differ diff --git a/src/assets/themes/tron/tron-orange/4.png b/src/assets/themes/tron/tron-orange/4.png new file mode 100644 index 0000000..6b26520 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/4.png differ diff --git a/src/assets/themes/tron/tron-orange/5.png b/src/assets/themes/tron/tron-orange/5.png new file mode 100644 index 0000000..08090a1 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/5.png differ diff --git a/src/assets/themes/tron/tron-orange/6.png b/src/assets/themes/tron/tron-orange/6.png new file mode 100644 index 0000000..ff66dcf Binary files /dev/null and b/src/assets/themes/tron/tron-orange/6.png differ diff --git a/src/assets/themes/tron/tron-orange/7.png b/src/assets/themes/tron/tron-orange/7.png new file mode 100644 index 0000000..5bf68be Binary files /dev/null and b/src/assets/themes/tron/tron-orange/7.png differ diff --git a/src/assets/themes/tron/tron-orange/8.png b/src/assets/themes/tron/tron-orange/8.png new file mode 100644 index 0000000..25d1705 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/8.png differ diff --git a/src/assets/themes/tron/tron-orange/flag.png b/src/assets/themes/tron/tron-orange/flag.png new file mode 100644 index 0000000..375c9f8 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/flag.png differ diff --git a/src/assets/themes/tron/tron-orange/last-pos.png b/src/assets/themes/tron/tron-orange/last-pos.png new file mode 100644 index 0000000..5d8bc34 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/last-pos.png differ diff --git a/src/assets/themes/tron/tron-orange/mine.png b/src/assets/themes/tron/tron-orange/mine.png new file mode 100644 index 0000000..b7352d1 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/mine.png differ diff --git a/src/assets/themes/tron/tron-orange/question-mark.png b/src/assets/themes/tron/tron-orange/question-mark.png new file mode 100644 index 0000000..61213dc Binary files /dev/null and b/src/assets/themes/tron/tron-orange/question-mark.png differ diff --git a/src/assets/themes/tron/tron-orange/revealed.png b/src/assets/themes/tron/tron-orange/revealed.png new file mode 100644 index 0000000..d7db19d Binary files /dev/null and b/src/assets/themes/tron/tron-orange/revealed.png differ diff --git a/src/assets/themes/tron/tron-orange/tile.png b/src/assets/themes/tron/tron-orange/tile.png new file mode 100644 index 0000000..d4cc2c3 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/tile.png differ diff --git a/src/assets/themes/tron/tron-orange/tron-orange.aseprite b/src/assets/themes/tron/tron-orange/tron-orange.aseprite new file mode 100644 index 0000000..d20bf92 Binary files /dev/null and b/src/assets/themes/tron/tron-orange/tron-orange.aseprite differ diff --git a/src/assets/themes/underwater/1.png b/src/assets/themes/underwater/1.png new file mode 100644 index 0000000..3ecb5f1 Binary files /dev/null and b/src/assets/themes/underwater/1.png differ diff --git a/src/assets/themes/underwater/2.png b/src/assets/themes/underwater/2.png new file mode 100644 index 0000000..3544305 Binary files /dev/null and b/src/assets/themes/underwater/2.png differ diff --git a/src/assets/themes/underwater/3.png b/src/assets/themes/underwater/3.png new file mode 100644 index 0000000..aa4062d Binary files /dev/null and b/src/assets/themes/underwater/3.png differ diff --git a/src/assets/themes/underwater/4.png b/src/assets/themes/underwater/4.png new file mode 100644 index 0000000..6a4fdec Binary files /dev/null and b/src/assets/themes/underwater/4.png differ diff --git a/src/assets/themes/underwater/5.png b/src/assets/themes/underwater/5.png new file mode 100644 index 0000000..9c163ca Binary files /dev/null and b/src/assets/themes/underwater/5.png differ diff --git a/src/assets/themes/underwater/6.png b/src/assets/themes/underwater/6.png new file mode 100644 index 0000000..8aa98f3 Binary files /dev/null and b/src/assets/themes/underwater/6.png differ diff --git a/src/assets/themes/underwater/7.png b/src/assets/themes/underwater/7.png new file mode 100644 index 0000000..caab15a Binary files /dev/null and b/src/assets/themes/underwater/7.png differ diff --git a/src/assets/themes/underwater/8.png b/src/assets/themes/underwater/8.png new file mode 100644 index 0000000..8d0d272 Binary files /dev/null and b/src/assets/themes/underwater/8.png differ diff --git a/src/assets/themes/underwater/flag.png b/src/assets/themes/underwater/flag.png new file mode 100644 index 0000000..e0b7fbe Binary files /dev/null and b/src/assets/themes/underwater/flag.png differ diff --git a/src/assets/themes/underwater/last-pos.png b/src/assets/themes/underwater/last-pos.png new file mode 100644 index 0000000..f96e9b3 Binary files /dev/null and b/src/assets/themes/underwater/last-pos.png differ diff --git a/src/assets/themes/underwater/mine.png b/src/assets/themes/underwater/mine.png new file mode 100644 index 0000000..296c2cc Binary files /dev/null and b/src/assets/themes/underwater/mine.png differ diff --git a/src/assets/themes/underwater/question-mark.png b/src/assets/themes/underwater/question-mark.png new file mode 100644 index 0000000..98e3667 Binary files /dev/null and b/src/assets/themes/underwater/question-mark.png differ diff --git a/src/assets/themes/underwater/revealed.png b/src/assets/themes/underwater/revealed.png new file mode 100644 index 0000000..55a7d2a Binary files /dev/null and b/src/assets/themes/underwater/revealed.png differ diff --git a/src/assets/themes/underwater/tile.png b/src/assets/themes/underwater/tile.png new file mode 100644 index 0000000..03d730e Binary files /dev/null and b/src/assets/themes/underwater/tile.png differ diff --git a/src/assets/themes/underwater/underwater.aseprite b/src/assets/themes/underwater/underwater.aseprite new file mode 100644 index 0000000..eeee769 Binary files /dev/null and b/src/assets/themes/underwater/underwater.aseprite differ diff --git a/src/atoms.ts b/src/atoms.ts new file mode 100644 index 0000000..6331c7f --- /dev/null +++ b/src/atoms.ts @@ -0,0 +1,17 @@ +import { atom } from "jotai"; +import { atomWithStorage } from "jotai/utils"; +import { FeedItem } from "./components/Feed/FeedItem"; + +export const gameIdAtom = atom(undefined); +export const loginTokenAtom = atomWithStorage( + "loginToken", + undefined, +); +export const cursorXAtom = atom(0); +export const cursorYAtom = atom(0); +export const feedItemsAtom = atom([]); +interface LootboxResult { + result: string; + lootbox: string; +} +export const lootboxResultAtom = atom(); diff --git a/src/components/Auth/LoginButton.tsx b/src/components/Auth/LoginButton.tsx new file mode 100644 index 0000000..66b1c23 --- /dev/null +++ b/src/components/Auth/LoginButton.tsx @@ -0,0 +1,81 @@ +import { useEffect, useState } from "react"; +import { Button } from "../Button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../Dialog"; +import { useQueryClient } from "@tanstack/react-query"; +import { useWSMutation } from "../../hooks"; +import { useAtom } from "jotai"; +import { loginTokenAtom } from "../../atoms"; +import PasswordInput from "./PasswordInput"; +import { wsClient } from "../../wsClient"; + +const LoginButton = () => { + const [isOpen, setIsOpen] = useState(false); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const queryClient = useQueryClient(); + const login = useWSMutation("user.login"); + const [, setToken] = useAtom(loginTokenAtom); + + useEffect(() => { + setUsername(""); + setPassword(""); + }, [isOpen]); + + return ( + + + + + + + Login + +
+ + setUsername(e.target.value)} + /> + + +
+ {error &&

{error}

} + + + + +
+
+ ); +}; + +export default LoginButton; diff --git a/src/components/Auth/PasswordInput.tsx b/src/components/Auth/PasswordInput.tsx new file mode 100644 index 0000000..595d7e8 --- /dev/null +++ b/src/components/Auth/PasswordInput.tsx @@ -0,0 +1,32 @@ +import { Eye, EyeOff } from "lucide-react"; +import { useState } from "react"; + +interface PasswordInputProps { + value: string; + onChange: (value: string) => void; +} + +const PasswordInput = ({ value, onChange }: PasswordInputProps) => { + const [show, setShow] = useState(false); + return ( +
+ onChange(e.target.value)} + className="w-full p-2 border-white/10 border-1 rounded-md" + /> +
+ +
+
+ ); +}; + +export default PasswordInput; diff --git a/src/components/Auth/RegisterButton.tsx b/src/components/Auth/RegisterButton.tsx new file mode 100644 index 0000000..a0cc880 --- /dev/null +++ b/src/components/Auth/RegisterButton.tsx @@ -0,0 +1,81 @@ +import { useEffect, useState } from "react"; +import { Button } from "../Button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../Dialog"; +import { useWSMutation } from "../../hooks"; +import { useAtom } from "jotai"; +import { loginTokenAtom } from "../../atoms"; +import { useQueryClient } from "@tanstack/react-query"; +import PasswordInput from "./PasswordInput"; +import { wsClient } from "../../wsClient"; + +const RegisterButton = () => { + const [isOpen, setIsOpen] = useState(false); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const queryClient = useQueryClient(); + const register = useWSMutation("user.register"); + const [, setToken] = useAtom(loginTokenAtom); + + useEffect(() => { + setUsername(""); + setPassword(""); + }, [isOpen]); + + return ( + + + + + + + Register + +
+ + setUsername(e.target.value)} + /> + + +
+ {error &&

{error}

} + + + + +
+
+ ); +}; + +export default RegisterButton; diff --git a/src/components/Board.tsx b/src/components/Board.tsx new file mode 100644 index 0000000..76e3327 --- /dev/null +++ b/src/components/Board.tsx @@ -0,0 +1,383 @@ +import { + ReactNode, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { LoadedTheme, Theme, useTheme } from "../themes/Theme"; +import { Container, Sprite, Stage, useTick } from "@pixi/react"; +import Viewport from "./pixi/PixiViewport"; +import type { Viewport as PixiViewport } from "pixi-viewport"; +import { + ClientGame, + getValue, + isServerGame, + ServerGame, +} from "../../shared/game"; +import { useWSQuery } from "../hooks"; +import { Texture } from "pixi.js"; +import { useAtom } from "jotai"; +import { cursorXAtom, cursorYAtom } from "../atoms"; +import Coords from "./Coords"; +import { cn } from "../lib/utils"; +import { Button } from "./Button"; +import { Maximize2, Minimize2, RotateCcw } from "lucide-react"; +import useSound from "use-sound"; +import explosion from "../sound/explosion.mp3"; +import "@pixi/canvas-display"; +import "@pixi/canvas-extract"; +import "@pixi/canvas-graphics"; +import "@pixi/canvas-mesh"; +import "@pixi/canvas-particle-container"; +import "@pixi/canvas-prepare"; +import "@pixi/canvas-renderer"; +import "@pixi/canvas-sprite-tiling"; +import "@pixi/canvas-sprite"; +import "@pixi/canvas-text"; +import { themes } from "../themes"; + +interface BoardProps { + className?: string; + game: ServerGame | ClientGame; + onLeftClick: (x: number, y: number) => void; + onRightClick: (x: number, y: number) => void; + restartGame: () => void; + width?: number; + height?: number; +} + +interface ViewportInfo { + width: number; + height: number; + x: number; + y: number; +} + +const toViewportInfo = (viewport: PixiViewport) => { + return { + x: -viewport.x / viewport.scaled, + y: -viewport.y / viewport.scaled, + width: viewport.screenWidth / viewport.scaled, + height: viewport.screenHeight / viewport.scaled, + }; +}; + +const Board: React.FC = (props) => { + const { game, restartGame } = props; + const { data: user } = useWSQuery("user.getSelf", null); + const ref = useRef(null); + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + const showLastPos = game.user !== user || isServerGame(game); + const [playSound] = useSound(explosion, { + volume: 0.5, + }); + + useEffect(() => { + if (isServerGame(game) && game.finished > Date.now() - 100) { + playSound(); + } + }, [game, playSound]); + + const [viewport, setViewport] = useState({ + width: 0, + height: 0, + x: 0, + y: 0, + }); + + const onViewportChange = useCallback((viewport: PixiViewport) => { + setViewport((v) => { + const { width, height, x, y } = toViewportInfo(viewport); + if (v.width !== width || v.height !== height) { + return { width, height, x, y }; + } + if (Math.abs(v.x - x) > 16 || Math.abs(v.y - y) > 16) { + return { width, height, x, y }; + } + return v; + }); + }, []); + useEffect(() => { + setInterval(() => { + if (viewportRef.current) onViewportChange(viewportRef.current); + }, 200); + }, [game.width, game.height, onViewportChange]); + useEffect(() => { + if (!ref.current) return; + setWidth(ref.current.clientWidth); + setHeight(ref.current.clientHeight); + if (viewportRef.current) onViewportChange(viewportRef.current); + const resizeObserver = new ResizeObserver(() => { + if (ref.current) { + setWidth(ref.current.clientWidth); + setHeight(ref.current.clientHeight); + if (viewportRef.current) onViewportChange(viewportRef.current); + } + }); + resizeObserver.observe(ref.current); + return () => resizeObserver.disconnect(); + }, [onViewportChange]); + const theme = useTheme( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + themes.find((t) => t.id === (game.theme as any))!.theme, + ); + const boardWidth = game.width * (theme?.size || 0); + const boardHeight = game.height * (theme?.size || 0); + + const viewportRef = useRef(null); + const [zenMode, setZenMode] = useState(false); + useEffect(() => { + if (ref.current) { + ref.current.addEventListener("wheel", (e) => { + e.preventDefault(); + }); + } + }, [ref]); + useEffect(() => { + const listener = (e: KeyboardEvent) => { + if (e.key === "Escape") { + setZenMode(false); + } + }; + document.addEventListener("keydown", listener); + return () => { + document.removeEventListener("keydown", listener); + }; + }, []); + + return ( +
+
+
+ {!props.width && !props.height && ( +
+ + +
+ )} + {zenMode && ( +
+ {game.minesCount - game.isFlagged.flat().filter((f) => f).length} + {" | "} + Stage {game.stage} +
+ )} +
+ {theme && ( + + + {Array.from({ length: game.width }).map((_, i) => { + return Array.from({ length: game.height }).map((_, j) => { + const tollerance = theme.size * 3; + if (i * theme.size > viewport.x + viewport.width + tollerance) + return null; + if (i * theme.size < viewport.x - tollerance) return null; + if ( + j * theme.size > + viewport.y + viewport.height + tollerance + ) + return null; + if (j * theme.size < viewport.y - tollerance) return null; + return ( + + ); + }); + })} + + + )} +
+ {!props.width && !props.height && } +
+ ); +}; + +interface TileProps { + x: number; + y: number; + game: ServerGame | ClientGame; + theme: LoadedTheme; + showLastPos: boolean; + onLeftClick: (x: number, y: number) => void; + onRightClick: (x: number, y: number) => void; +} + +const Tile = ({ + game, + x, + y, + theme, + showLastPos, + onRightClick, + onLeftClick, +}: TileProps) => { + const i = x; + const j = y; + const isRevealed = game.isRevealed[i][j]; + const value = isServerGame(game) + ? getValue(game.mines, i, j) + : game.values[i][j]; + const isMine = isServerGame(game) ? game.mines[i][j] : false; + const isLastPos = showLastPos + ? game.lastClick[0] === i && game.lastClick[1] === j + : false; + const isFlagged = game.isFlagged[i][j]; + const isQuestionMark = game.isQuestionMark[i][j]; + const base = + isRevealed || (isMine && !isFlagged) ? ( + + ) : ( + + ); + const extra = isLastPos ? : null; + const touchStart = useRef(0); + const isMove = useRef(false); + const startX = useRef(0); + const startY = useRef(0); + const oldState = useRef(`${isRevealed},${isMine},${value}`); + const [scale, setScale] = useState(1); + const [doTick, setDoTick] = useState(true); + const frame = useRef(0); + useEffect(() => { + if (oldState.current !== `${isRevealed},${isMine},${value}`) { + oldState.current = `${isRevealed},${isMine},${value}`; + frame.current = 0; + setDoTick(true); + } + }, [isMine, isRevealed, value]); + useTick((delta) => { + frame.current += delta * 0.1; + if (frame.current > 3) { + setDoTick(false); + } + setScale(Math.max(1, -2 * Math.pow(frame.current - 0.5, 2) + 1.2)); + }, doTick); + const baseProps = useMemo( + () => ({ + scale, + x: theme.size * 0.5, + y: theme.size * 0.5, + anchor: 0.5, + }), + [scale, theme.size], + ); + let content: ReactNode = null; + if (isFlagged) { + content = ; + } else if (isMine) { + content = ; + } else if (value !== -1 && isRevealed) { + const img = theme[value.toString() as keyof Theme] as Texture; + content = img ? : null; + } else if (isQuestionMark) { + content = ; + } + const [, setCursorX] = useAtom(cursorXAtom); + const [, setCursorY] = useAtom(cursorYAtom); + + return ( + { + onRightClick(i, j); + }} + onpointerup={(e) => { + if (e.button !== 0) return; + if (isMove.current) return; + if (Date.now() - touchStart.current > 300) { + onRightClick(i, j); + } else { + onLeftClick(i, j); + } + }} + onpointerdown={(e) => { + isMove.current = false; + touchStart.current = Date.now(); + startX.current = e.global.x; + startY.current = e.global.y; + }} + onpointerenter={() => { + setCursorX(i); + setCursorY(j); + }} + onpointermove={(e) => { + if ( + Math.abs(startX.current - e.global.x) > 10 || + Math.abs(startY.current - e.global.y) > 10 + ) { + isMove.current = true; + } + }} + > + {base} + {content} + {extra} + + ); +}; + +export default Board; diff --git a/src/components/BounceImg.tsx b/src/components/BounceImg.tsx new file mode 100644 index 0000000..5818449 --- /dev/null +++ b/src/components/BounceImg.tsx @@ -0,0 +1,27 @@ +import { animate, motion } from "framer-motion"; +import { useRef } from "react"; + +const BounceImg = ({ src, className }: { src: string; className?: string }) => { + const ref = useRef(null); + return ( + { + if (ref.current) { + animate(ref.current, { scale: 1.2 }, { duration: 0.3 }); + setTimeout(() => { + if (ref.current) + animate(ref.current, { scale: 1 }, { duration: 0.3 }); + }, 300); + } + }} + transition={{ + type: "spring", + }} + className={className} + /> + ); +}; + +export default BounceImg; diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..b609125 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,45 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import { forwardRef } from "react"; +import { cn } from "../lib/utils"; + +const buttonVariants = cva("font-semibold py-2 px-4 rounded-md flex gap-2", { + variants: { + variant: { + default: "bg-gray-900 text-white/95", + ghost: "bg-transparent text-white/95 hover:bg-white/05", + outline: + "bg-transparent text-white/95 hover:bg-white/05 border-white/10 border-1", + primary: + "[background:var(--bg-brand)] text-white/95 hover:bg-white/05 hover:animate-gradientmove", + }, + size: { + default: "h-10 py-2 px-4", + sm: "h-9 px-3 rounded-md", + lg: "h-11 px-8 rounded-md", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, +}); + +export type ButtonProps = React.ButtonHTMLAttributes & + VariantProps & { + as?: React.FC; + }; + +export const Button = forwardRef( + ( + { className, variant, size, as: Comp = "button" as const, ...props }, + ref, + ) => { + return ( + + ); + }, +); diff --git a/src/components/Coords.tsx b/src/components/Coords.tsx new file mode 100644 index 0000000..7ae212b --- /dev/null +++ b/src/components/Coords.tsx @@ -0,0 +1,14 @@ +import { useAtom } from "jotai"; +import { cursorXAtom, cursorYAtom } from "../atoms"; + +const Coords = () => { + const [cursorX] = useAtom(cursorXAtom); + const [cursorY] = useAtom(cursorYAtom); + return ( +
+ {cursorX},{cursorY} +
+ ); +}; + +export default Coords; diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx new file mode 100644 index 0000000..97f6b15 --- /dev/null +++ b/src/components/Dialog.tsx @@ -0,0 +1,122 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import { cn } from "../lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + overlayClassName?: string; + } +>(({ className, children, overlayClassName, ...props }, ref) => ( + + + + {children} + + + Close + + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/src/components/DropdownMenu.tsx b/src/components/DropdownMenu.tsx new file mode 100644 index 0000000..6f6adbd --- /dev/null +++ b/src/components/DropdownMenu.tsx @@ -0,0 +1,197 @@ +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import { cn } from "../lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/src/components/Feed/Feed.tsx b/src/components/Feed/Feed.tsx new file mode 100644 index 0000000..824ca57 --- /dev/null +++ b/src/components/Feed/Feed.tsx @@ -0,0 +1,92 @@ +import { AnimatePresence, motion } from "framer-motion"; +import { useAtom } from "jotai"; +import { feedItemsAtom, lootboxResultAtom } from "../../atoms"; +import FeedItemElement from "./FeedItem"; +import { useEffect } from "react"; +import { addMessageListener, removeMessageListener } from "../../wsClient"; +import type { Events } from "../../../shared/events"; +import { useWSQuery } from "../../hooks"; + +const Feed: React.FC = () => { + const [items, setItems] = useAtom(feedItemsAtom); + const [, setLootboxResult] = useAtom(lootboxResultAtom); + const { data: user } = useWSQuery("user.getSelf", null); + + useEffect(() => { + const interval = setInterval(() => { + setItems((items) => items.filter((item) => item.decay > Date.now())); + }, 1000); + return () => clearInterval(interval); + }, [setItems]); + + useEffect(() => { + const listener = async (event: MessageEvent) => { + const data = JSON.parse(event.data) as Events; + const newItems = [...items]; + if (data.type === "new" && data.user !== user) { + newItems.push({ + type: "gameStarted", + user: data.user, + id: crypto.randomUUID(), + decay: Date.now() + 1000 * 3, + }); + } + if (data.type === "loss") { + newItems.push({ + type: "gameFinished", + user: data.user, + id: crypto.randomUUID(), + decay: Date.now() + 1000 * 3 + data.stage * 500, + stage: data.stage, + time: data.time, + }); + } + if (data.type === "gemsRewarded" && data.gems > 0) { + newItems.push({ + type: "gemsEarned", + id: crypto.randomUUID(), + decay: Date.now() + 1000 * 3 + data.stage * 1500, + stage: data.stage, + gems: data.gems, + }); + } + if (data.type === "lootboxPurchased" && data.user !== user) { + newItems.push({ + type: "lootboxPurchased", + id: crypto.randomUUID(), + decay: Date.now() + 20_000, + lootbox: data.lootbox, + user: data.user, + reward: data.reward, + rarity: data.rarity, + }); + await new Promise((res) => setTimeout(res, 2_000)); + } + if (data.type === "lootboxPurchased" && data.user === user) { + setLootboxResult({ + lootbox: data.lootbox, + result: data.reward, + }); + } + setItems(newItems); + }; + addMessageListener(listener); + return () => removeMessageListener(listener); + }, [items, setItems, setLootboxResult, user]); + + return ( +
+
+ + + {items.map((item) => ( + + ))} + + +
+
+ ); +}; + +export default Feed; diff --git a/src/components/Feed/FeedItem.tsx b/src/components/Feed/FeedItem.tsx new file mode 100644 index 0000000..aa72434 --- /dev/null +++ b/src/components/Feed/FeedItem.tsx @@ -0,0 +1,89 @@ +import { motion } from "framer-motion"; +import { PropsWithChildren } from "react"; +import { formatTimeSpan } from "../../../shared/time"; +import GemsIcon from "../GemIcon"; +import { Rarity as RarityType } from "../../../shared/lootboxes"; +import { Rarity } from "../Rarity"; +import { themes } from "../../themes"; + +interface BaseFeedItem { + decay: number; + id: string; +} + +interface GameStartedItem extends BaseFeedItem { + type: "gameStarted"; + user: string; +} + +interface GameFinishedItem extends BaseFeedItem { + type: "gameFinished"; + user: string; + stage: number; + time: number; +} + +interface GemsEarnedItem extends BaseFeedItem { + type: "gemsEarned"; + gems: number; + stage: number; +} + +interface LootboxPurchasedItem extends BaseFeedItem { + type: "lootboxPurchased"; + lootbox: string; + user: string; + reward: string; + rarity: RarityType; +} + +export type FeedItem = + | GameStartedItem + | GameFinishedItem + | GemsEarnedItem + | LootboxPurchasedItem; + +const FeedItemWrapper: React.FC = ({ children }) => { + return ( + + {children} + + ); +}; + +const FeedItemElement: React.FC<{ item: FeedItem }> = ({ item }) => { + switch (item.type) { + case "gameStarted": + return {item.user} started a game; + case "gameFinished": + return ( + + {item.user} finished in{" "} + stage {item.stage} after{" "} + {formatTimeSpan(item.time)} + + ); + case "gemsEarned": + return ( + + You got {item.gems} for stage {item.stage} + + ); + case "lootboxPurchased": + return ( + + {item.user} got{" "} + + {themes.find((i) => i.id == item.reward)?.name} + + + ); + } +}; + +export default FeedItemElement; diff --git a/src/components/GemIcon.tsx b/src/components/GemIcon.tsx new file mode 100644 index 0000000..a3bb923 --- /dev/null +++ b/src/components/GemIcon.tsx @@ -0,0 +1,7 @@ +import gem from "../assets/gem.png?w=20&h=20&inline"; + +const GemsIcon = () => { + return ; +}; + +export default GemsIcon; diff --git a/src/components/Gems.tsx b/src/components/Gems.tsx new file mode 100644 index 0000000..449caff --- /dev/null +++ b/src/components/Gems.tsx @@ -0,0 +1,17 @@ +import { Tag } from "./Tag"; +import GemsIcon from "./GemIcon"; + +interface GemsProps { + count: number; +} + +const Gems: React.FC = ({ count }) => { + return ( + + {count} + + + ); +}; + +export default Gems; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..d706469 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,66 @@ +import { UserRound } from "lucide-react"; +import { Button } from "./Button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "./DropdownMenu"; +import { useLocation } from "wouter"; +import LoginButton from "./Auth/LoginButton"; +import { useWSMutation, useWSQuery } from "../hooks"; +import RegisterButton from "./Auth/RegisterButton"; +import { useQueryClient } from "@tanstack/react-query"; +import { useAtom } from "jotai"; +import { loginTokenAtom } from "../atoms"; +import Gems from "./Gems"; + +const Header = () => { + const [, setLocation] = useLocation(); + const { data: username } = useWSQuery("user.getSelf", null); + const queryClient = useQueryClient(); + const [, setToken] = useAtom(loginTokenAtom); + const logout = useWSMutation("user.logout", () => { + setToken(undefined); + queryClient.resetQueries(); + }); + const { data: gems } = useWSQuery("user.getOwnGems", null); + + return ( +
+
+ + {username ? ( + + {typeof gems?.count === "number" && } + + + + + setLocation("/profile")}> + Profile + + setLocation("/settings")}> + Settings + + + logout.mutate(null)}> + Logout + + + + ) : ( + <> + + + + )} +
+ ); +}; + +export default Header; diff --git a/src/components/Hr.tsx b/src/components/Hr.tsx new file mode 100644 index 0000000..126ab4a --- /dev/null +++ b/src/components/Hr.tsx @@ -0,0 +1,5 @@ +const Hr = () => { + return
; +}; + +export default Hr; diff --git a/src/components/LeaderboardButton.tsx b/src/components/LeaderboardButton.tsx new file mode 100644 index 0000000..18cb18c --- /dev/null +++ b/src/components/LeaderboardButton.tsx @@ -0,0 +1,49 @@ +import { Fragment } from "react"; +import { useWSQuery } from "../hooks"; +import { Button } from "./Button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "./Dialog"; + +interface LeaderboardButtonProps { + label?: string; +} + +const LeaderboardButton = ({ + label = "View Full Leaderboard", +}: LeaderboardButtonProps) => { + const { data: leaderboard } = useWSQuery("scoreboard.getScoreBoard", 100); + return ( + + + + + + + Leaderboard + +
+ {leaderboard?.map((_, i) => ( + +
{i + 1}.
+
+ {leaderboard?.[i]?.user ?? "No User"} +
+
+ Stage {leaderboard?.[i]?.stage ?? 0} +
+
+ ))} +
+
+
+ ); +}; + +export default LeaderboardButton; diff --git a/src/components/NavLink.tsx b/src/components/NavLink.tsx new file mode 100644 index 0000000..94461d5 --- /dev/null +++ b/src/components/NavLink.tsx @@ -0,0 +1,22 @@ +import { Link } from "wouter"; + +interface NavLinkProps { + href: string; + children: React.ReactNode; + external?: boolean; +} + +const NavLink: React.FC = ({ href, children, external }) => { + const Comp = external ? "a" : Link; + return ( + + {children} + + ); +}; + +export default NavLink; diff --git a/src/components/PastMatch.tsx b/src/components/PastMatch.tsx new file mode 100644 index 0000000..43a446c --- /dev/null +++ b/src/components/PastMatch.tsx @@ -0,0 +1,41 @@ +import { Link } from "wouter"; +import { ServerGame } from "../../shared/game"; +import { formatRelativeTime, formatTimeSpan } from "../../shared/time"; +import { Button } from "./Button"; + +interface PastMatchProps { + game: ServerGame; +} + +const PastMatch = ({ game }: PastMatchProps) => { + return ( +
+
+
+
Endless
+
+ {formatRelativeTime(game.finished)} +
+
+
+
Stage {game.stage}
+
+ Mines Remaining:{" "} + {game.minesCount - game.isFlagged.flat().filter((f) => f).length} +
+
+
+
Duration: {formatTimeSpan(game.finished - game.started)}
+
+
+ {/* @ts-expect-error as is cheaply typed */} + +
+
+
+ ); +}; + +export default PastMatch; diff --git a/src/components/Popover.tsx b/src/components/Popover.tsx new file mode 100644 index 0000000..fe5daa2 --- /dev/null +++ b/src/components/Popover.tsx @@ -0,0 +1,32 @@ +import * as PopoverPrimitive from "@radix-ui/react-popover"; +import { forwardRef, PropsWithChildren } from "react"; +import { cn } from "../lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger: React.FC = ({ children }) => { + return ( + {children} + ); +}; + +const PopoverContent = forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/src/components/Rarity.tsx b/src/components/Rarity.tsx new file mode 100644 index 0000000..4e19d4b --- /dev/null +++ b/src/components/Rarity.tsx @@ -0,0 +1,22 @@ +import { PropsWithChildren } from "react"; +import { cn } from "../lib/utils"; +import { rarities } from "../../shared/lootboxes"; + +export const Rarity: React.FC> = ({ + rarity, + children, +}) => { + return ( + + {children ?? rarities.find((r) => r.id === rarity)?.name} + + ); +}; diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx new file mode 100644 index 0000000..e6e26cc --- /dev/null +++ b/src/components/Switch.tsx @@ -0,0 +1,28 @@ +"use client"; + +import * as React from "react"; +import * as SwitchPrimitives from "@radix-ui/react-switch"; +import { cn } from "../lib/utils"; + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +Switch.displayName = SwitchPrimitives.Root.displayName; + +export { Switch }; diff --git a/src/components/Tag.tsx b/src/components/Tag.tsx new file mode 100644 index 0000000..43bcd08 --- /dev/null +++ b/src/components/Tag.tsx @@ -0,0 +1,45 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import { forwardRef } from "react"; +import { cn } from "../lib/utils"; + +const tagVariants = cva( + "font-semibold py-2 px-4 rounded-md flex whitespace-pre", + { + variants: { + variant: { + default: "bg-gray-900 text-white/95", + ghost: "bg-transparent text-white/95 hover:bg-white/05", + outline: + "bg-transparent text-white/95 hover:bg-white/05 border-white/10 border-1", + outline2: + "bg-transparent [background:var(--bg-brand)] [-webkit-text-fill-color:transparent] [-webkit-background-clip:text!important] bg-white/05 border-primary border-1", + primary: + "[background:var(--bg-brand)] text-white/95 hover:bg-white/05 hover:animate-gradientmove", + }, + size: { + default: "h-10 py-2 px-4", + sm: "h-7 py-2 px-2 rounded-md text-xs", + lg: "h-11 px-8 rounded-md", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export type ButtonProps = React.HTMLAttributes & + VariantProps; + +export const Tag = forwardRef( + ({ className, variant, size, ...props }, ref) => { + return ( +
+ ); + }, +); diff --git a/src/components/pixi/PixiViewport.tsx b/src/components/pixi/PixiViewport.tsx new file mode 100644 index 0000000..2822e25 --- /dev/null +++ b/src/components/pixi/PixiViewport.tsx @@ -0,0 +1,97 @@ +import React from "react"; +import type { Application } from "pixi.js"; +import { IClampZoomOptions, Viewport as PixiViewport } from "pixi-viewport"; +import { PixiComponent, useApp } from "@pixi/react"; +import { BaseTexture, SCALE_MODES } from "pixi.js"; +BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST; + +export interface ViewportProps { + width: number; + height: number; + worldWidth: number; + worldHeight: number; + children?: React.ReactNode; + clamp?: { + left: number; + right: number; + top: number; + bottom: number; + }; + clampZoom?: IClampZoomOptions; + onViewportChange?: (viewport: PixiViewport) => void; + viewportRef?: React.RefObject; +} + +export interface PixiComponentViewportProps extends ViewportProps { + app: Application; +} + +const PixiComponentViewport = PixiComponent("Viewport", { + create: (props: PixiComponentViewportProps) => { + const viewport = new PixiViewport({ + screenWidth: props.width, + screenHeight: props.height, + worldWidth: props.worldWidth, + worldHeight: props.worldHeight, + ticker: props.app.ticker, + events: props.app.renderer.events, + disableOnContextMenu: true, + allowPreserveDragOutside: true, + }); + viewport + .drag({ + ignoreKeyToPressOnTouch: true, + mouseButtons: "middle", + }) + .pinch() + .wheel(); + if (props.clamp) { + viewport.clamp(props.clamp); + } + if (props.clampZoom) { + viewport.clampZoom(props.clampZoom); + } + viewport.on("moved", () => { + props.onViewportChange?.(viewport); + }); + viewport.on("zoomed-end", () => { + props.onViewportChange?.(viewport); + }); + + if (props.viewportRef) { + // @ts-expect-error We dont care since this is internal api + props.viewportRef.current = viewport; + } + + return viewport; + }, + applyProps: ( + viewport: PixiViewport, + oldProps: ViewportProps, + newProps: ViewportProps, + ) => { + if ( + oldProps.width !== newProps.width || + oldProps.height !== newProps.height || + oldProps.worldWidth !== newProps.worldWidth || + oldProps.worldHeight !== newProps.worldHeight + ) { + viewport.resize( + newProps.width, + newProps.height, + newProps.worldWidth, + newProps.worldHeight, + ); + } + if (oldProps.clamp !== newProps.clamp) { + viewport.clamp(newProps.clamp); + } + }, +}); + +const Viewport = (props: ViewportProps) => { + const app = useApp(); + return ; +}; + +export default Viewport; diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..652626c --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,99 @@ +import { + keepPreviousData, + useInfiniteQuery, + useMutation, + type UseMutationResult, + useQuery, + useQueryClient, + type UseQueryResult, +} from "@tanstack/react-query"; +import type { Routes } from "../backend/router"; +import { wsClient } from "./wsClient"; +import type { z } from "zod"; + +export const useWSQuery = < + TController extends keyof Routes, + TAction extends keyof Routes[TController] & string, +>( + action: `${TController}.${TAction}`, + // @ts-expect-error We dont care since this is internal api + payload: z.input, + enabled?: boolean, +): UseQueryResult< + // @ts-expect-error We dont care since this is internal api + Awaited> +> => { + return useQuery({ + queryKey: [action, payload], + queryFn: async () => { + const result = await wsClient.dispatch(action, payload); + return result; + }, + enabled, + placeholderData: keepPreviousData, + }); +}; + +export const useWSMutation = < + TController extends keyof Routes, + TAction extends keyof Routes[TController] & string, +>( + action: `${TController}.${TAction}`, + onSuccess?: ( + data: Awaited< + // @ts-expect-error We dont care since this is internal api + ReturnType + >, + ) => void, +): UseMutationResult< + // @ts-expect-error We dont care since this is internal api + Awaited>, + unknown, + // @ts-expect-error We dont care since this is internal api + Routes[TController][TAction]["validate"]["_input"] +> => { + return useMutation({ + // @ts-expect-error We dont care since this is internal api + mutationFn: async ( + // @ts-expect-error We dont care since this is internal api + payload: Routes[TController][TAction]["validate"]["_input"], + ) => { + const result = await wsClient.dispatch(action, payload); + return result; + }, + onSuccess, + }); +}; + +export const useWSInvalidation = < + TController extends keyof Routes, + TAction extends keyof Routes[TController] & string, +>() => { + const queryClient = useQueryClient(); + return (action: `${TController}.${TAction}`) => { + queryClient.invalidateQueries({ queryKey: [action] }); + }; +}; + +export const useInfiniteGames = (user: string | null | undefined) => { + const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = + useInfiniteQuery({ + queryKey: ["game.getGames", { user }], + enabled: !!user, + queryFn: async ({ pageParam }) => { + const result = await wsClient.dispatch("game.getGames", { + user: user!, + page: pageParam, + }); + return result; + }, + getNextPageParam: (lastPage) => lastPage.nextPage, + initialPageParam: 0, + }); + return { + data, + hasNextPage, + fetchNextPage, + isFetchingNextPage, + }; +}; diff --git a/src/images/banner.png b/src/images/banner.png new file mode 100644 index 0000000..baff3b0 Binary files /dev/null and b/src/images/banner.png differ diff --git a/src/images/expert.png b/src/images/expert.png new file mode 100644 index 0000000..8401028 Binary files /dev/null and b/src/images/expert.png differ diff --git a/src/images/mine.png b/src/images/mine.png new file mode 100644 index 0000000..5ecdd47 Binary files /dev/null and b/src/images/mine.png differ diff --git a/src/index.css b/src/index.css index 1ad06f6..31b1d71 100644 --- a/src/index.css +++ b/src/index.css @@ -1,116 +1,131 @@ -.game-board { - display: grid; - gap: 2px; - max-width: fit-content; -} - -.game-wrapper { - display: flex; - flex-direction: column; - align-items: center; -} - -.mine-button { - background-color: #666; - border: 1px solid black; - width: 2rem; - height: 2rem; - font-size: 1.25rem; - user-select: none; - display: flex; - justify-content: center; - align-items: center; - font-weight: bold; - font-family: monospace; - box-sizing: border-box; - transition: all 0.2s ease-in-out; -} - -html { - background: #111; - color: #eee; -} - -body { - margin: auto; - max-width: 1400px; - padding: 1rem; - font-family: monospace; -} - -.timer { - flex-grow: 1; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 2rem; - font-family: monospace; -} - -.footer { - display: flex; - flex-direction: column; - /* justify-content: space-between; */ - align-items: center; - font-size: 1rem; - font-family: monospace; -} - -pre { - margin: 0; -} - -.stage { - font-size: 1rem; - font-family: monospace; -} - -input { - font-size: 14px; - margin: 12px; - padding: 6px 12px 6px 12px; - border-radius: 0.7em; - background: #333; - color: #eee; - border: 1px solid rgb(251, 21, 242); +@import "tailwindcss"; +@theme { + --color-primary: #D9AFD9; + --color-input: color-mix(in srgb, var(--color-white, #fff) 20%, transparent); + --color-background: black; + --color-common: var(--color-sky-500); + --color-uncommon: var(--color-green-400); + --color-rare: var(--color-red-500); + --color-legendary: var(--color-amber-500); + --bg-brand: -webkit-linear-gradient(225deg, rgb(251, 175, 21), rgb(251, 21, 242), + rgb(21, 198, 251)) 0% 0% / 100% 300%; + --bg-secondary: linear-gradient(90deg, #D9AFD9 0%, #97D9E1 100%) 0% 0% / 100% 300%; + --animate-gradientmove: gradientmove 1s ease 0s 1 normal forwards; + @keyframes gradientmove { + 0%{background-position: 0% 0%} + 100%{background-position: 0% 100%} + } } button { - color: white; - font-weight: 600; - font-size: 14px; - margin: 12px; - padding: 6px 12px 6px 12px; - border-radius: 0.7em; - background: -webkit-linear-gradient(225deg, rgb(251, 175, 21), rgb(251, 21, 242), - rgb(21, 198, 251)) 0% 0% / 300% 300%; - background-size: 200% auto; + cursor: pointer; } -button:hover { - animation: gradient_move 1s ease infinite; +.grid-border-b div:not(:nth-last-child(-n+3)) { + @apply border-b border-white/10; } -@keyframes gradient_move { - 0%{background-position: 0% 92%} - 50%{background-position: 100% 9%} - 100%{background-position: 0% 92%} -} - -/* .scores { */ -/* position: fixed; */ -/* top: 0; */ -/* left: 0; */ -/* padding: 1rem; */ +/* .game-board { */ +/* display: grid; */ +/* gap: 2px; */ +/* max-width: fit-content; */ +/* } */ +/**/ +/* .game-wrapper { */ +/* display: flex; */ +/* flex-direction: column; */ +/* align-items: center; */ +/* } */ +/**/ +/* .mine-button { */ +/* background-color: #666; */ +/* border: 1px solid black; */ +/* width: 2rem; */ +/* height: 2rem; */ +/* font-size: 1.25rem; */ +/* user-select: none; */ +/* display: flex; */ +/* justify-content: center; */ +/* align-items: center; */ +/* font-weight: bold; */ +/* font-family: monospace; */ +/* box-sizing: border-box; */ +/* transition: all 0.2s ease-in-out; */ +/* } */ +/**/ +/* html { */ +/* background: #111; */ +/* color: #eee; */ +/* } */ +/**/ +/* body { */ +/* margin: auto; */ +/* max-width: 1400px; */ +/* padding: 1rem; */ +/* font-family: monospace; */ +/* } */ +/**/ +/* .timer { */ +/* flex-grow: 1; */ +/* display: flex; */ +/* justify-content: space-between; */ +/* align-items: center; */ +/* font-size: 2rem; */ +/* font-family: monospace; */ +/* } */ +/**/ +/* .footer { */ +/* display: flex; */ +/* flex-direction: column; */ +/* align-items: center; */ +/* font-size: 1rem; */ +/* font-family: monospace; */ +/* } */ +/**/ +/* pre { */ +/* margin: 0; */ +/* } */ +/**/ +/* .stage { */ +/* font-size: 1rem; */ +/* font-family: monospace; */ +/* } */ +/**/ +/* input { */ +/* font-size: 14px; */ +/* margin: 12px; */ +/* padding: 6px 12px 6px 12px; */ +/* border-radius: 0.7em; */ +/* background: #333; */ +/* color: #eee; */ +/* border: 1px solid rgb(251, 21, 242); */ +/**/ +/* } */ +/**/ +/* button { */ +/* color: white; */ +/* font-weight: 600; */ +/* font-size: 14px; */ +/* margin: 12px; */ +/* padding: 6px 12px 6px 12px; */ +/* border-radius: 0.7em; */ +/* background: -webkit-linear-gradient(225deg, rgb(251, 175, 21), rgb(251, 21, 242), */ +/* rgb(21, 198, 251)) 0% 0% / 300% 300%; */ +/* background-size: 200% auto; */ +/* } */ +/**/ +/* button:hover { */ +/* animation: gradient_move 1s ease infinite; */ +/* } */ +/**/ +/**/ +/* .header { */ +/* display: grid; */ +/* grid-template-columns: 1fr 1fr; */ +/* margin-bottom: 1rem; */ +/* } */ +/**/ +/* .scores { */ +/* text-align: right; */ /* } */ - -.header { - display: grid; - grid-template-columns: 1fr 1fr; - margin-bottom: 1rem; -} - -.scores { - text-align: right; -} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/main.tsx b/src/main.tsx index 99a4fdc..6564ed3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,19 +1,54 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; import "./index.css"; -import { connectWS } from "./ws.ts"; -import { Toaster } from "react-hot-toast"; +import { QueryClientProvider } from "@tanstack/react-query"; +import Shell from "./Shell.tsx"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { wsClient } from "./wsClient.ts"; +import { Route, Switch } from "wouter"; +import Endless from "./views/endless/Endless.tsx"; +import { queryClient } from "./queryClient.ts"; +import Home from "./views/home/Home.tsx"; +import Settings from "./views/settings/Settings.tsx"; +import MatchHistory from "./views/match-history/MatchHistory.tsx"; +import Collection from "./views/collection/Collection.tsx"; +import { AnimatePresence } from "framer-motion"; +import Store from "./views/store/Store.tsx"; -document.addEventListener("contextmenu", (event) => { - event.preventDefault(); +const setup = async () => { + const token = localStorage.getItem("loginToken"); + + if (token) { + try { + await wsClient.dispatch("user.loginWithToken", { + token: JSON.parse(token), + }); + } catch (e) { + console.error(e); + } + } +}; + +setup().then(() => { + createRoot(document.getElementById("root")!).render( + + + + + + + + {(params) => } + + + + + + + + + + + , + ); }); - -connectWS(); - -createRoot(document.getElementById("root")!).render( - - - - , -); diff --git a/src/queryClient.ts b/src/queryClient.ts new file mode 100644 index 0000000..af1fcfe --- /dev/null +++ b/src/queryClient.ts @@ -0,0 +1,9 @@ +import { QueryClient } from "@tanstack/react-query"; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: true, + }, + }, +}); diff --git a/src/themes/MLG.ts b/src/themes/MLG.ts new file mode 100644 index 0000000..d7dd438 --- /dev/null +++ b/src/themes/MLG.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const MLGTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/MLG/mine.png"), + tile: () => import("../assets/themes/MLG/tile.png"), + revealed: () => import("../assets/themes/MLG/revealed.png"), + flag: () => import("../assets/themes/MLG/flag-2.png"), + questionMark: () => import("../assets/themes/MLG/question-mark.png"), + lastPos: () => import("../assets/themes/MLG/last-pos.png"), + 1: () => import("../assets/themes/MLG/1.png"), + 2: () => import("../assets/themes/MLG/2.png"), + 3: () => import("../assets/themes/MLG/3.png"), + 4: () => import("../assets/themes/MLG/4.png"), + 5: () => import("../assets/themes/MLG/5.png"), + 6: () => import("../assets/themes/MLG/6.png"), + 7: () => import("../assets/themes/MLG/7.png"), + 8: () => import("../assets/themes/MLG/8.png"), +}; diff --git a/src/themes/Theme.ts b/src/themes/Theme.ts new file mode 100644 index 0000000..be87a12 --- /dev/null +++ b/src/themes/Theme.ts @@ -0,0 +1,49 @@ +import { Assets, Texture } from "pixi.js"; +import { useEffect, useState } from "react"; + +type Png = typeof import("*.png"); +type LazySprite = () => Promise; + +export interface Theme { + size: number; + mine: LazySprite; + tile: LazySprite; + revealed: LazySprite; + flag: LazySprite; + questionMark: LazySprite; + lastPos: LazySprite; + 1: LazySprite; + 2: LazySprite; + 3: LazySprite; + 4: LazySprite; + 5: LazySprite; + 6: LazySprite; + 7: LazySprite; + 8: LazySprite; +} + +export type LoadedTheme = Record, Texture> & { + size: number; +}; + +export const useTheme = (theme: Theme) => { + const [loadedTheme, setLoadedTheme] = useState( + undefined, + ); + useEffect(() => { + const loadTheme = async () => { + const loadedEntries = await Promise.all( + Object.entries(theme).map(async ([key, value]) => { + const loaded = + typeof value === "function" + ? await Assets.load((await value()).default) + : value; + return [key, loaded] as const; + }), + ); + setLoadedTheme(Object.fromEntries(loadedEntries) as LoadedTheme); + }; + loadTheme(); + }, [theme]); + return loadedTheme; +}; diff --git a/src/themes/basic.ts b/src/themes/basic.ts new file mode 100644 index 0000000..07d59e5 --- /dev/null +++ b/src/themes/basic.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const basicTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/basic/mine.png"), + tile: () => import("../assets/themes/basic/tile.png"), + revealed: () => import("../assets/themes/basic/revealed.png"), + flag: () => import("../assets/themes/basic/flag.png"), + questionMark: () => import("../assets/themes/basic/question-mark.png"), + lastPos: () => import("../assets/themes/basic/last-pos.png"), + 1: () => import("../assets/themes/basic/1.png"), + 2: () => import("../assets/themes/basic/2.png"), + 3: () => import("../assets/themes/basic/3.png"), + 4: () => import("../assets/themes/basic/4.png"), + 5: () => import("../assets/themes/basic/5.png"), + 6: () => import("../assets/themes/basic/6.png"), + 7: () => import("../assets/themes/basic/7.png"), + 8: () => import("../assets/themes/basic/8.png"), +}; diff --git a/src/themes/black-and-white.ts b/src/themes/black-and-white.ts new file mode 100644 index 0000000..fc9b7aa --- /dev/null +++ b/src/themes/black-and-white.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const blackAndWhiteTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/black-and-white/mine.png"), + tile: () => import("../assets/themes/black-and-white/tile.png"), + revealed: () => import("../assets/themes/black-and-white/revealed.png"), + flag: () => import("../assets/themes/black-and-white/flag.png"), + questionMark: () => import("../assets/themes/black-and-white/question-mark.png"), + lastPos: () => import("../assets/themes/black-and-white/last-pos.png"), + 1: () => import("../assets/themes/black-and-white/1.png"), + 2: () => import("../assets/themes/black-and-white/2.png"), + 3: () => import("../assets/themes/black-and-white/3.png"), + 4: () => import("../assets/themes/black-and-white/4.png"), + 5: () => import("../assets/themes/black-and-white/5.png"), + 6: () => import("../assets/themes/black-and-white/6.png"), + 7: () => import("../assets/themes/black-and-white/7.png"), + 8: () => import("../assets/themes/black-and-white/8.png"), +}; diff --git a/src/themes/cats.ts b/src/themes/cats.ts new file mode 100644 index 0000000..1887011 --- /dev/null +++ b/src/themes/cats.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const catsTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/cats/mine-2.png"), + tile: () => import("../assets/themes/cats/tile.png"), + revealed: () => import("../assets/themes/cats/revealed.png"), + flag: () => import("../assets/themes/cats/flag.png"), + questionMark: () => import("../assets/themes/cats/question-mark.png"), + lastPos: () => import("../assets/themes/cats/last-pos.png"), + 1: () => import("../assets/themes/cats/1.png"), + 2: () => import("../assets/themes/cats/2.png"), + 3: () => import("../assets/themes/cats/3.png"), + 4: () => import("../assets/themes/cats/4.png"), + 5: () => import("../assets/themes/cats/5.png"), + 6: () => import("../assets/themes/cats/6.png"), + 7: () => import("../assets/themes/cats/7.png"), + 8: () => import("../assets/themes/cats/8.png"), +}; diff --git a/src/themes/circuit-binary.ts b/src/themes/circuit-binary.ts new file mode 100644 index 0000000..2a5d85e --- /dev/null +++ b/src/themes/circuit-binary.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const circuitBinaryTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/circuit/mine.png"), + tile: () => import("../assets/themes/circuit/tile.png"), + revealed: () => import("../assets/themes/circuit/revealed.png"), + flag: () => import("../assets/themes/circuit/flag.png"), + questionMark: () => import("../assets/themes/circuit/question-mark.png"), + lastPos: () => import("../assets/themes/circuit/last-pos.png"), + 1: () => import("../assets/themes/circuit/binary/5.png"), + 2: () => import("../assets/themes/circuit/binary/6.png"), + 3: () => import("../assets/themes/circuit/binary/7.png"), + 4: () => import("../assets/themes/circuit/binary/8.png"), + 5: () => import("../assets/themes/circuit/binary/5.png"), + 6: () => import("../assets/themes/circuit/binary/6.png"), + 7: () => import("../assets/themes/circuit/binary/7.png"), + 8: () => import("../assets/themes/circuit/binary/8.png"), +}; diff --git a/src/themes/circuit.ts b/src/themes/circuit.ts new file mode 100644 index 0000000..0ad59f6 --- /dev/null +++ b/src/themes/circuit.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const circuitTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/circuit/mine.png"), + tile: () => import("../assets/themes/circuit/tile.png"), + revealed: () => import("../assets/themes/circuit/revealed.png"), + flag: () => import("../assets/themes/circuit/flag.png"), + questionMark: () => import("../assets/themes/circuit/question-mark.png"), + lastPos: () => import("../assets/themes/circuit/last-pos.png"), + 1: () => import("../assets/themes/circuit/1.png"), + 2: () => import("../assets/themes/circuit/2.png"), + 3: () => import("../assets/themes/circuit/3.png"), + 4: () => import("../assets/themes/circuit/4.png"), + 5: () => import("../assets/themes/circuit/5.png"), + 6: () => import("../assets/themes/circuit/6.png"), + 7: () => import("../assets/themes/circuit/7.png"), + 8: () => import("../assets/themes/circuit/8.png"), +}; diff --git a/src/themes/color-palettes/crimson.ts b/src/themes/color-palettes/crimson.ts new file mode 100644 index 0000000..9383914 --- /dev/null +++ b/src/themes/color-palettes/crimson.ts @@ -0,0 +1,22 @@ +import { Theme } from "../Theme"; + +export const crimson: Theme = { + size: 32, + mine: () => import("../../assets/themes/color-palettes/crimson/mine.png"), + tile: () => import("../../assets/themes/color-palettes/crimson/tile.png"), + revealed: () => + import("../../assets/themes/color-palettes/crimson/revealed.png"), + flag: () => import("../../assets/themes/color-palettes/crimson/flag.png"), + questionMark: () => + import("../../assets/themes/color-palettes/crimson/question-mark.png"), + lastPos: () => + import("../../assets/themes/color-palettes/crimson/last-pos.png"), + 1: () => import("../../assets/themes/color-palettes/crimson/1.png"), + 2: () => import("../../assets/themes/color-palettes/crimson/2.png"), + 3: () => import("../../assets/themes/color-palettes/crimson/3.png"), + 4: () => import("../../assets/themes/color-palettes/crimson/4.png"), + 5: () => import("../../assets/themes/color-palettes/crimson/5.png"), + 6: () => import("../../assets/themes/color-palettes/crimson/6.png"), + 7: () => import("../../assets/themes/color-palettes/crimson/7.png"), + 8: () => import("../../assets/themes/color-palettes/crimson/8.png"), +}; diff --git a/src/themes/color-palettes/nautical.ts b/src/themes/color-palettes/nautical.ts new file mode 100644 index 0000000..8c58789 --- /dev/null +++ b/src/themes/color-palettes/nautical.ts @@ -0,0 +1,20 @@ +import { Theme } from "../Theme"; + +export const nauticalTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/color-palettes/nautical/mine.png"), + tile: () => import("../../assets/themes/color-palettes/nautical/tile.png"), + revealed: () => import("../../assets/themes/color-palettes/nautical/revealed.png"), + flag: () => import("../../assets/themes/color-palettes/nautical/flag.png"), + questionMark: () => + import("../../assets/themes/color-palettes/nautical/question-mark.png"), + lastPos: () => import("../../assets/themes/color-palettes/nautical/last-pos.png"), + 1: () => import("../../assets/themes/color-palettes/nautical/1.png"), + 2: () => import("../../assets/themes/color-palettes/nautical/2.png"), + 3: () => import("../../assets/themes/color-palettes/nautical/3.png"), + 4: () => import("../../assets/themes/color-palettes/nautical/4.png"), + 5: () => import("../../assets/themes/color-palettes/nautical/5.png"), + 6: () => import("../../assets/themes/color-palettes/nautical/6.png"), + 7: () => import("../../assets/themes/color-palettes/nautical/7.png"), + 8: () => import("../../assets/themes/color-palettes/nautical/8.png"), +}; diff --git a/src/themes/color-palettes/shadow-warrior.ts b/src/themes/color-palettes/shadow-warrior.ts new file mode 100644 index 0000000..45ca92c --- /dev/null +++ b/src/themes/color-palettes/shadow-warrior.ts @@ -0,0 +1,20 @@ +import { Theme } from "../Theme"; + +export const shadowWarriorTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/color-palettes/shadow-warrior/mine.png"), + tile: () => import("../../assets/themes/color-palettes/shadow-warrior/tile.png"), + revealed: () => import("../../assets/themes/color-palettes/shadow-warrior/revealed.png"), + flag: () => import("../../assets/themes/color-palettes/shadow-warrior/flag.png"), + questionMark: () => + import("../../assets/themes/color-palettes/shadow-warrior/question-mark.png"), + lastPos: () => import("../../assets/themes/color-palettes/shadow-warrior/last-pos.png"), + 1: () => import("../../assets/themes/color-palettes/shadow-warrior/1.png"), + 2: () => import("../../assets/themes/color-palettes/shadow-warrior/2.png"), + 3: () => import("../../assets/themes/color-palettes/shadow-warrior/3.png"), + 4: () => import("../../assets/themes/color-palettes/shadow-warrior/4.png"), + 5: () => import("../../assets/themes/color-palettes/shadow-warrior/5.png"), + 6: () => import("../../assets/themes/color-palettes/shadow-warrior/6.png"), + 7: () => import("../../assets/themes/color-palettes/shadow-warrior/7.png"), + 8: () => import("../../assets/themes/color-palettes/shadow-warrior/8.png"), +}; diff --git a/src/themes/color-palettes/up-in-smoke.ts b/src/themes/color-palettes/up-in-smoke.ts new file mode 100644 index 0000000..a44ab94 --- /dev/null +++ b/src/themes/color-palettes/up-in-smoke.ts @@ -0,0 +1,22 @@ +import { Theme } from "../Theme"; + +export const upInSmokeTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/color-palettes/up-in-smoke/mine.png"), + tile: () => import("../../assets/themes/color-palettes/up-in-smoke/tile.png"), + revealed: () => + import("../../assets/themes/color-palettes/up-in-smoke/revealed.png"), + flag: () => import("../../assets/themes/color-palettes/up-in-smoke/flag.png"), + questionMark: () => + import("../../assets/themes/color-palettes/up-in-smoke/question-mark.png"), + lastPos: () => + import("../../assets/themes/color-palettes/up-in-smoke/last-pos.png"), + 1: () => import("../../assets/themes/color-palettes/up-in-smoke/1.png"), + 2: () => import("../../assets/themes/color-palettes/up-in-smoke/2.png"), + 3: () => import("../../assets/themes/color-palettes/up-in-smoke/3.png"), + 4: () => import("../../assets/themes/color-palettes/up-in-smoke/4.png"), + 5: () => import("../../assets/themes/color-palettes/up-in-smoke/5.png"), + 6: () => import("../../assets/themes/color-palettes/up-in-smoke/6.png"), + 7: () => import("../../assets/themes/color-palettes/up-in-smoke/7.png"), + 8: () => import("../../assets/themes/color-palettes/up-in-smoke/8.png"), +}; diff --git a/src/themes/colors/blue.ts b/src/themes/colors/blue.ts new file mode 100644 index 0000000..2071d49 --- /dev/null +++ b/src/themes/colors/blue.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const blueTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/blue/mine.png"), + tile: () => import("../../assets/themes/colors/blue/tile.png"), + revealed: () => import("../../assets/themes/colors/blue/revealed.png"), + flag: () => import("../../assets/themes/colors/blue/flag.png"), + questionMark: () => import("../../assets/themes/colors/blue/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/blue/last-pos.png"), + 1: () => import("../../assets/themes/colors/blue/1.png"), + 2: () => import("../../assets/themes/colors/blue/2.png"), + 3: () => import("../../assets/themes/colors/blue/3.png"), + 4: () => import("../../assets/themes/colors/blue/4.png"), + 5: () => import("../../assets/themes/colors/blue/5.png"), + 6: () => import("../../assets/themes/colors/blue/6.png"), + 7: () => import("../../assets/themes/colors/blue/7.png"), + 8: () => import("../../assets/themes/colors/blue/8.png"), +}; diff --git a/src/themes/colors/green.ts b/src/themes/colors/green.ts new file mode 100644 index 0000000..466cec7 --- /dev/null +++ b/src/themes/colors/green.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const greenTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/green/mine.png"), + tile: () => import("../../assets/themes/colors/green/tile.png"), + revealed: () => import("../../assets/themes/colors/green/revealed.png"), + flag: () => import("../../assets/themes/colors/green/flag.png"), + questionMark: () => import("../../assets/themes/colors/green/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/green/last-pos.png"), + 1: () => import("../../assets/themes/colors/green/1.png"), + 2: () => import("../../assets/themes/colors/green/2.png"), + 3: () => import("../../assets/themes/colors/green/3.png"), + 4: () => import("../../assets/themes/colors/green/4.png"), + 5: () => import("../../assets/themes/colors/green/5.png"), + 6: () => import("../../assets/themes/colors/green/6.png"), + 7: () => import("../../assets/themes/colors/green/7.png"), + 8: () => import("../../assets/themes/colors/green/8.png"), +}; diff --git a/src/themes/colors/orange.ts b/src/themes/colors/orange.ts new file mode 100644 index 0000000..b9622a5 --- /dev/null +++ b/src/themes/colors/orange.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const orangeTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/orange/mine.png"), + tile: () => import("../../assets/themes/colors/orange/tile.png"), + revealed: () => import("../../assets/themes/colors/orange/revealed.png"), + flag: () => import("../../assets/themes/colors/orange/flag.png"), + questionMark: () => import("../../assets/themes/colors/orange/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/orange/last-pos.png"), + 1: () => import("../../assets/themes/colors/orange/1.png"), + 2: () => import("../../assets/themes/colors/orange/2.png"), + 3: () => import("../../assets/themes/colors/orange/3.png"), + 4: () => import("../../assets/themes/colors/orange/4.png"), + 5: () => import("../../assets/themes/colors/orange/5.png"), + 6: () => import("../../assets/themes/colors/orange/6.png"), + 7: () => import("../../assets/themes/colors/orange/7.png"), + 8: () => import("../../assets/themes/colors/orange/8.png"), +}; diff --git a/src/themes/colors/pink.ts b/src/themes/colors/pink.ts new file mode 100644 index 0000000..9b50983 --- /dev/null +++ b/src/themes/colors/pink.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const pinkTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/pink/mine.png"), + tile: () => import("../../assets/themes/colors/pink/tile.png"), + revealed: () => import("../../assets/themes/colors/pink/revealed.png"), + flag: () => import("../../assets/themes/colors/pink/flag.png"), + questionMark: () => import("../../assets/themes/colors/pink/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/pink/last-pos.png"), + 1: () => import("../../assets/themes/colors/pink/1.png"), + 2: () => import("../../assets/themes/colors/pink/2.png"), + 3: () => import("../../assets/themes/colors/pink/3.png"), + 4: () => import("../../assets/themes/colors/pink/4.png"), + 5: () => import("../../assets/themes/colors/pink/5.png"), + 6: () => import("../../assets/themes/colors/pink/6.png"), + 7: () => import("../../assets/themes/colors/pink/7.png"), + 8: () => import("../../assets/themes/colors/pink/8.png"), +}; diff --git a/src/themes/colors/purple.ts b/src/themes/colors/purple.ts new file mode 100644 index 0000000..10c3bd9 --- /dev/null +++ b/src/themes/colors/purple.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const purpleTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/purple/mine.png"), + tile: () => import("../../assets/themes/colors/purple/tile.png"), + revealed: () => import("../../assets/themes/colors/purple/revealed.png"), + flag: () => import("../../assets/themes/colors/purple/flag.png"), + questionMark: () => import("../../assets/themes/colors/purple/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/purple/last-pos.png"), + 1: () => import("../../assets/themes/colors/purple/1.png"), + 2: () => import("../../assets/themes/colors/purple/2.png"), + 3: () => import("../../assets/themes/colors/purple/3.png"), + 4: () => import("../../assets/themes/colors/purple/4.png"), + 5: () => import("../../assets/themes/colors/purple/5.png"), + 6: () => import("../../assets/themes/colors/purple/6.png"), + 7: () => import("../../assets/themes/colors/purple/7.png"), + 8: () => import("../../assets/themes/colors/purple/8.png"), +}; diff --git a/src/themes/colors/red.ts b/src/themes/colors/red.ts new file mode 100644 index 0000000..48558f8 --- /dev/null +++ b/src/themes/colors/red.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const redTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/red/mine.png"), + tile: () => import("../../assets/themes/colors/red/tile.png"), + revealed: () => import("../../assets/themes/colors/red/revealed.png"), + flag: () => import("../../assets/themes/colors/red/flag.png"), + questionMark: () => import("../../assets/themes/colors/red/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/red/last-pos.png"), + 1: () => import("../../assets/themes/colors/red/1.png"), + 2: () => import("../../assets/themes/colors/red/2.png"), + 3: () => import("../../assets/themes/colors/red/3.png"), + 4: () => import("../../assets/themes/colors/red/4.png"), + 5: () => import("../../assets/themes/colors/red/5.png"), + 6: () => import("../../assets/themes/colors/red/6.png"), + 7: () => import("../../assets/themes/colors/red/7.png"), + 8: () => import("../../assets/themes/colors/red/8.png"), +}; diff --git a/src/themes/colors/turquoise.ts b/src/themes/colors/turquoise.ts new file mode 100644 index 0000000..8a365d3 --- /dev/null +++ b/src/themes/colors/turquoise.ts @@ -0,0 +1,19 @@ +import { Theme } from "../Theme"; + +export const turquoiseTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/turquoise/mine.png"), + tile: () => import("../../assets/themes/colors/turquoise/tile.png"), + revealed: () => import("../../assets/themes/colors/turquoise/revealed.png"), + flag: () => import("../../assets/themes/colors/turquoise/flag.png"), + questionMark: () => import("../../assets/themes/colors/turquoise/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/turquoise/last-pos.png"), + 1: () => import("../../assets/themes/colors/turquoise/1.png"), + 2: () => import("../../assets/themes/colors/turquoise/2.png"), + 3: () => import("../../assets/themes/colors/turquoise/3.png"), + 4: () => import("../../assets/themes/colors/turquoise/4.png"), + 5: () => import("../../assets/themes/colors/turquoise/5.png"), + 6: () => import("../../assets/themes/colors/turquoise/6.png"), + 7: () => import("../../assets/themes/colors/turquoise/7.png"), + 8: () => import("../../assets/themes/colors/turquoise/8.png"), +}; diff --git a/src/themes/colors/yellow.ts b/src/themes/colors/yellow.ts new file mode 100644 index 0000000..ac805f1 --- /dev/null +++ b/src/themes/colors/yellow.ts @@ -0,0 +1,20 @@ +import { Theme } from "../Theme"; + +export const yellowTheme: Theme = { + size: 32, + mine: () => import("../../assets/themes/colors/yellow/mine.png"), + tile: () => import("../../assets/themes/colors/yellow/tile.png"), + revealed: () => import("../../assets/themes/colors/yellow/revealed.png"), + flag: () => import("../../assets/themes/colors/yellow/flag.png"), + questionMark: () => + import("../../assets/themes/colors/yellow/question-mark.png"), + lastPos: () => import("../../assets/themes/colors/yellow/last-pos.png"), + 1: () => import("../../assets/themes/colors/yellow/1.png"), + 2: () => import("../../assets/themes/colors/yellow/2.png"), + 3: () => import("../../assets/themes/colors/yellow/3.png"), + 4: () => import("../../assets/themes/colors/yellow/4.png"), + 5: () => import("../../assets/themes/colors/yellow/5.png"), + 6: () => import("../../assets/themes/colors/yellow/6.png"), + 7: () => import("../../assets/themes/colors/yellow/7.png"), + 8: () => import("../../assets/themes/colors/yellow/8.png"), +}; diff --git a/src/themes/default.ts b/src/themes/default.ts new file mode 100644 index 0000000..e56766e --- /dev/null +++ b/src/themes/default.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const defaultTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/default/mine.png"), + tile: () => import("../assets/themes/default/tile.png"), + revealed: () => import("../assets/themes/default/revealed.png"), + flag: () => import("../assets/themes/default/flag.png"), + questionMark: () => import("../assets/themes/default/question-mark.png"), + lastPos: () => import("../assets/themes/default/last-pos.png"), + 1: () => import("../assets/themes/default/1.png"), + 2: () => import("../assets/themes/default/2.png"), + 3: () => import("../assets/themes/default/3.png"), + 4: () => import("../assets/themes/default/4.png"), + 5: () => import("../assets/themes/default/5.png"), + 6: () => import("../assets/themes/default/6.png"), + 7: () => import("../assets/themes/default/7.png"), + 8: () => import("../assets/themes/default/8.png"), +}; diff --git a/src/themes/dinos.ts b/src/themes/dinos.ts new file mode 100644 index 0000000..0124a15 --- /dev/null +++ b/src/themes/dinos.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const dinoTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/dinos/mine-1.png"), + tile: () => import("../assets/themes/dinos/tile.png"), + revealed: () => import("../assets/themes/dinos/revealed-1.png"), + flag: () => import("../assets/themes/dinos/flag.png"), + questionMark: () => import("../assets/themes/dinos/question-mark.png"), + lastPos: () => import("../assets/themes/dinos/last-pos.png"), + 1: () => import("../assets/themes/dinos/1.png"), + 2: () => import("../assets/themes/dinos/2.png"), + 3: () => import("../assets/themes/dinos/3.png"), + 4: () => import("../assets/themes/dinos/4.png"), + 5: () => import("../assets/themes/dinos/5.png"), + 6: () => import("../assets/themes/dinos/6.png"), + 7: () => import("../assets/themes/dinos/7.png"), + 8: () => import("../assets/themes/dinos/8.png"), +}; diff --git a/src/themes/elden-ring.ts b/src/themes/elden-ring.ts new file mode 100644 index 0000000..a477d5f --- /dev/null +++ b/src/themes/elden-ring.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const eldenRingTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/elden-ring/mine.png"), + tile: () => import("../assets/themes/elden-ring/tile.png"), + revealed: () => import("../assets/themes/elden-ring/revealed.png"), + flag: () => import("../assets/themes/elden-ring/flag.png"), + questionMark: () => import("../assets/themes/elden-ring/question-mark.png"), + lastPos: () => import("../assets/themes/elden-ring/last-pos.png"), + 1: () => import("../assets/themes/elden-ring/1.png"), + 2: () => import("../assets/themes/elden-ring/2.png"), + 3: () => import("../assets/themes/elden-ring/3.png"), + 4: () => import("../assets/themes/elden-ring/4.png"), + 5: () => import("../assets/themes/elden-ring/5.png"), + 6: () => import("../assets/themes/elden-ring/6.png"), + 7: () => import("../assets/themes/elden-ring/7.png"), + 8: () => import("../assets/themes/elden-ring/8.png"), +}; diff --git a/src/themes/farm.ts b/src/themes/farm.ts new file mode 100644 index 0000000..d6f393f --- /dev/null +++ b/src/themes/farm.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const farmTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/farm/mine.png"), + tile: () => import("../assets/themes/farm/tile.png"), + revealed: () => import("../assets/themes/farm/revealed.png"), + flag: () => import("../assets/themes/farm/flag.png"), + questionMark: () => import("../assets/themes/farm/question-mark.png"), + lastPos: () => import("../assets/themes/farm/last-pos.png"), + 1: () => import("../assets/themes/farm/1.png"), + 2: () => import("../assets/themes/farm/2.png"), + 3: () => import("../assets/themes/farm/3.png"), + 4: () => import("../assets/themes/farm/4.png"), + 5: () => import("../assets/themes/farm/5.png"), + 6: () => import("../assets/themes/farm/6.png"), + 7: () => import("../assets/themes/farm/7.png"), + 8: () => import("../assets/themes/farm/8.png"), +}; diff --git a/src/themes/flowers.ts b/src/themes/flowers.ts new file mode 100644 index 0000000..c99d127 --- /dev/null +++ b/src/themes/flowers.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const flowersTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/flowers/mine.png"), + tile: () => import("../assets/themes/flowers/tile.png"), + revealed: () => import("../assets/themes/flowers/revealed.png"), + flag: () => import("../assets/themes/flowers/flag.png"), + questionMark: () => import("../assets/themes/flowers/question-mark.png"), + lastPos: () => import("../assets/themes/flowers/last-pos.png"), + 1: () => import("../assets/themes/flowers/1.png"), + 2: () => import("../assets/themes/flowers/2.png"), + 3: () => import("../assets/themes/flowers/3.png"), + 4: () => import("../assets/themes/flowers/4.png"), + 5: () => import("../assets/themes/flowers/5.png"), + 6: () => import("../assets/themes/flowers/6.png"), + 7: () => import("../assets/themes/flowers/7.png"), + 8: () => import("../assets/themes/flowers/8.png"), +}; diff --git a/src/themes/halli-galli.ts b/src/themes/halli-galli.ts new file mode 100644 index 0000000..d86545b --- /dev/null +++ b/src/themes/halli-galli.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const halliGalliTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/halli-galli/mine.png"), + tile: () => import("../assets/themes/halli-galli/tile.png"), + revealed: () => import("../assets/themes/halli-galli/revealed.png"), + flag: () => import("../assets/themes/halli-galli/flag.png"), + questionMark: () => import("../assets/themes/halli-galli/question-mark.png"), + lastPos: () => import("../assets/themes/halli-galli/last-pos.png"), + 1: () => import("../assets/themes/halli-galli/1.png"), + 2: () => import("../assets/themes/halli-galli/2.png"), + 3: () => import("../assets/themes/halli-galli/3.png"), + 4: () => import("../assets/themes/halli-galli/4.png"), + 5: () => import("../assets/themes/halli-galli/5.png"), + 6: () => import("../assets/themes/halli-galli/6.png"), + 7: () => import("../assets/themes/halli-galli/7.png"), + 8: () => import("../assets/themes/halli-galli/8.png"), +}; diff --git a/src/themes/index.ts b/src/themes/index.ts new file mode 100644 index 0000000..dd252ad --- /dev/null +++ b/src/themes/index.ts @@ -0,0 +1,292 @@ +import { basicTheme } from "./basic"; +import { blackAndWhiteTheme } from "./black-and-white"; +import { catsTheme } from "./cats"; +import { circuitTheme } from "./circuit"; +import { circuitBinaryTheme } from "./circuit-binary"; +import { crimson } from "./color-palettes/crimson"; +import { nauticalTheme } from "./color-palettes/nautical"; +import { shadowWarriorTheme } from "./color-palettes/shadow-warrior"; +import { upInSmokeTheme } from "./color-palettes/up-in-smoke"; +import { blueTheme } from "./colors/blue"; +import { greenTheme } from "./colors/green"; +import { orangeTheme } from "./colors/orange"; +import { pinkTheme } from "./colors/pink"; +import { purpleTheme } from "./colors/purple"; +import { redTheme } from "./colors/red"; +import { turquoiseTheme } from "./colors/turquoise"; +import { yellowTheme } from "./colors/yellow"; +import { defaultTheme } from "./default"; +import { dinoTheme } from "./dinos"; +import { eldenRingTheme } from "./elden-ring"; +import { farmTheme } from "./farm"; +import { flowersTheme } from "./flowers"; +import { halliGalliTheme } from "./halli-galli"; +import { insectsTheme } from "./insects"; +import { isaacTheme } from "./isaac"; +import { janitorTreshTheme } from "./janitor-tresh"; +import { leagueTeemoTheme } from "./league-teemo"; +import { leagueZiggsTheme } from "./league-ziggs"; +import { mineDogsTheme } from "./mine-dogs"; +import { minecraftNetherTheme } from "./minecraft-nether"; +import { minecraftOverworldTheme } from "./minecraft-overworld"; +import { MLGTheme } from "./MLG"; +import { poopTheme } from "./poop"; +import { retroWaveTheme } from "./retro-wave"; +import { romanceTheme } from "./romance"; +import { techiesDireTheme } from "./techies-dire"; +import { techiesRadiantTheme } from "./techies-radiant"; +import { Theme } from "./Theme"; +import { tronBlueTheme } from "./tron-blue"; +import { tronOrangeTheme } from "./tron-orange"; +import { underwaterTheme } from "./underwater"; + +interface ThemeEntry { + name: string; + tags: string[]; + /** dont't ever change this! */ + id: string; + theme: Readonly; +} + +export const themes = [ + { + name: "Default", + tags: ["Simple"], + id: "default", + theme: defaultTheme, + }, + { + name: "Basic", + tags: ["Simple"], + id: "basic", + theme: basicTheme, + }, + { + name: "Black and White", + tags: ["Simple", "Monochrome"], + id: "black-and-white", + theme: blackAndWhiteTheme, + }, + { + name: "Cats", + tags: ["Animals"], + id: "cats", + theme: catsTheme, + }, + { + name: "Retro Wave", + tags: ["Retro", "High Contrast"], + id: "retro-wave", + theme: retroWaveTheme, + }, + { + name: "Dinos", + tags: ["Animals"], + id: "dinos", + theme: dinoTheme, + }, + { + name: "Elden Ring", + tags: ["Video Games"], + id: "elden-ring", + theme: eldenRingTheme, + }, + { + name: "Flowers", + tags: ["No Numbers"], + id: "flowers", + theme: flowersTheme, + }, + { + name: "Janitor Tresh", + tags: ["Video Games"], + id: "janitor-tresh", + theme: janitorTreshTheme, + }, + { + name: "Teemo", + tags: ["Video Games"], + id: "teemo", + theme: leagueTeemoTheme, + }, + { + name: "Ziggs", + tags: ["Video Games"], + id: "ziggs", + theme: leagueZiggsTheme, + }, + { + name: "Mine Dogs", + tags: ["Animals"], + id: "mine-dogs", + theme: mineDogsTheme, + }, + { + name: "Minecraft Nether", + tags: ["Video Games"], + id: "minecraft-nether", + theme: minecraftNetherTheme, + }, + { + name: "Minecraft", + tags: ["Video Games"], + id: "minecraft-overworld", + theme: minecraftOverworldTheme, + }, + { + name: "Romance", + tags: [], + id: "romance", + theme: romanceTheme, + }, + { + name: "Techies Dire", + tags: ["Video Games"], + id: "techies-dire", + theme: techiesDireTheme, + }, + { + name: "Techies Radiant", + tags: ["Video Games"], + id: "techies-radiant", + theme: techiesRadiantTheme, + }, + { + name: "Tron Blue", + tags: ["Video Games"], + id: "tron-blue", + theme: tronBlueTheme, + }, + { + name: "Tron Orange", + tags: ["Video Games"], + id: "tron-orange", + theme: tronOrangeTheme, + }, + { + name: "Blue", + tags: ["Monochrome"], + id: "blue", + theme: blueTheme, + }, + { + name: "Green", + tags: ["Monochrome"], + id: "green", + theme: greenTheme, + }, + { + name: "Orange", + tags: ["Monochrome"], + id: "orange", + theme: orangeTheme, + }, + { + name: "Pink", + tags: ["Monochrome"], + id: "pink", + theme: pinkTheme, + }, + { + name: "Purple", + tags: ["Monochrome"], + id: "purple", + theme: purpleTheme, + }, + { + name: "Red", + tags: ["Monochrome"], + id: "red", + theme: redTheme, + }, + { + name: "Turquoise", + tags: ["Monochrome"], + id: "turquoise", + theme: turquoiseTheme, + }, + { + name: "Yellow", + tags: ["Monochrome"], + id: "yellow", + theme: yellowTheme, + }, + { + name: "Circuit", + tags: [], + id: "circuit", + theme: circuitTheme, + }, + { + name: "Circuit Binary", + tags: ["No Numbers"], + id: "circuit-binary", + theme: circuitBinaryTheme, + }, + { + name: "Farm", + tags: [], + id: "farm", + theme: farmTheme, + }, + { + name: "Halli Galli", + tags: ["No Numbers"], + id: "halli-galli", + theme: halliGalliTheme, + }, + { + name: "Insects", + tags: ["No Numbers"], + id: "insects", + theme: insectsTheme, + }, + { + name: "Binding of Isaac", + tags: ["Video Games"], + id: "isaac", + theme: isaacTheme, + }, + { + name: "MLG", + tags: [], + id: "mlg", + theme: MLGTheme, + }, + { + name: "Poop", + tags: [], + id: "poop", + theme: poopTheme, + }, + { + name: "Underwater", + tags: [], + id: "underwater", + theme: underwaterTheme, + }, + { + name: "Nautical", + tags: [], + id: "nautical", + theme: nauticalTheme, + }, + { + name: "Up in Smoke", + tags: [], + id: "up-in-smoke", + theme: upInSmokeTheme, + }, + { + name: "Shadow Warrior", + tags: [], + id: "shadow-warrior", + theme: shadowWarriorTheme, + }, + { + name: "Crimson", + tags: [], + id: "crimson", + theme: crimson, + }, +] as const satisfies ThemeEntry[]; diff --git a/src/themes/insects.ts b/src/themes/insects.ts new file mode 100644 index 0000000..ebd6cc3 --- /dev/null +++ b/src/themes/insects.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const insectsTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/insects/mine-1.png"), + tile: () => import("../assets/themes/insects/tile.png"), + revealed: () => import("../assets/themes/insects/revealed.png"), + flag: () => import("../assets/themes/insects/flag.png"), + questionMark: () => import("../assets/themes/insects/question-mark.png"), + lastPos: () => import("../assets/themes/insects/last-pos.png"), + 1: () => import("../assets/themes/insects/1.png"), + 2: () => import("../assets/themes/insects/2.png"), + 3: () => import("../assets/themes/insects/3.png"), + 4: () => import("../assets/themes/insects/4.png"), + 5: () => import("../assets/themes/insects/5.png"), + 6: () => import("../assets/themes/insects/6.png"), + 7: () => import("../assets/themes/insects/7.png"), + 8: () => import("../assets/themes/insects/8.png"), +}; diff --git a/src/themes/isaac.ts b/src/themes/isaac.ts new file mode 100644 index 0000000..7c16f1f --- /dev/null +++ b/src/themes/isaac.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const isaacTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/isaac/mine-3.png"), + tile: () => import("../assets/themes/isaac/tile.png"), + revealed: () => import("../assets/themes/isaac/revealed.png"), + flag: () => import("../assets/themes/isaac/flag.png"), + questionMark: () => import("../assets/themes/isaac/question-mark.png"), + lastPos: () => import("../assets/themes/isaac/last-pos.png"), + 1: () => import("../assets/themes/isaac/1.png"), + 2: () => import("../assets/themes/isaac/2.png"), + 3: () => import("../assets/themes/isaac/3.png"), + 4: () => import("../assets/themes/isaac/4.png"), + 5: () => import("../assets/themes/isaac/5.png"), + 6: () => import("../assets/themes/isaac/6.png"), + 7: () => import("../assets/themes/isaac/7.png"), + 8: () => import("../assets/themes/isaac/8.png"), +}; diff --git a/src/themes/janitor-tresh.ts b/src/themes/janitor-tresh.ts new file mode 100644 index 0000000..ab5f60a --- /dev/null +++ b/src/themes/janitor-tresh.ts @@ -0,0 +1,20 @@ +import { Theme } from "./Theme"; + +export const janitorTreshTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/janitor-tresh/mine.png"), + tile: () => import("../assets/themes/janitor-tresh/tile.png"), + revealed: () => import("../assets/themes/janitor-tresh/revealed.png"), + flag: () => import("../assets/themes/janitor-tresh/flag.png"), + questionMark: () => + import("../assets/themes/janitor-tresh/question-mark.png"), + lastPos: () => import("../assets/themes/janitor-tresh/last-pos.png"), + 1: () => import("../assets/themes/janitor-tresh/1.png"), + 2: () => import("../assets/themes/janitor-tresh/2.png"), + 3: () => import("../assets/themes/janitor-tresh/3.png"), + 4: () => import("../assets/themes/janitor-tresh/4.png"), + 5: () => import("../assets/themes/janitor-tresh/5.png"), + 6: () => import("../assets/themes/janitor-tresh/6.png"), + 7: () => import("../assets/themes/janitor-tresh/7.png"), + 8: () => import("../assets/themes/janitor-tresh/8.png"), +}; diff --git a/src/themes/league-teemo.ts b/src/themes/league-teemo.ts new file mode 100644 index 0000000..22de55f --- /dev/null +++ b/src/themes/league-teemo.ts @@ -0,0 +1,20 @@ +import { Theme } from "./Theme"; + +export const leagueTeemoTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/league/teemo/mine.png"), + tile: () => import("../assets/themes/league/tile-1.png"), + revealed: () => import("../assets/themes/league/revealed.png"), + flag: () => import("../assets/themes/league/teemo/flag.png"), + questionMark: () => + import("../assets/themes/league/question-mark.png"), + lastPos: () => import("../assets/themes/league/last-pos.png"), + 1: () => import("../assets/themes/league/1.png"), + 2: () => import("../assets/themes/league/2.png"), + 3: () => import("../assets/themes/league/3.png"), + 4: () => import("../assets/themes/league/4.png"), + 5: () => import("../assets/themes/league/5.png"), + 6: () => import("../assets/themes/league/6.png"), + 7: () => import("../assets/themes/league/7.png"), + 8: () => import("../assets/themes/league/8.png"), +}; diff --git a/src/themes/league-ziggs.ts b/src/themes/league-ziggs.ts new file mode 100644 index 0000000..d3f1bf4 --- /dev/null +++ b/src/themes/league-ziggs.ts @@ -0,0 +1,20 @@ +import { Theme } from "./Theme"; + +export const leagueZiggsTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/league/ziggs/mine.png"), + tile: () => import("../assets/themes/league/tile-1.png"), + revealed: () => import("../assets/themes/league/revealed.png"), + flag: () => import("../assets/themes/league/ziggs/flag.png"), + questionMark: () => + import("../assets/themes/league/question-mark.png"), + lastPos: () => import("../assets/themes/league/last-pos.png"), + 1: () => import("../assets/themes/league/1.png"), + 2: () => import("../assets/themes/league/2.png"), + 3: () => import("../assets/themes/league/3.png"), + 4: () => import("../assets/themes/league/4.png"), + 5: () => import("../assets/themes/league/5.png"), + 6: () => import("../assets/themes/league/6.png"), + 7: () => import("../assets/themes/league/7.png"), + 8: () => import("../assets/themes/league/8.png"), +}; diff --git a/src/themes/mine-dogs.ts b/src/themes/mine-dogs.ts new file mode 100644 index 0000000..4f3a953 --- /dev/null +++ b/src/themes/mine-dogs.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const mineDogsTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/mine-dogs/mine.png"), + tile: () => import("../assets/themes/mine-dogs/tile.png"), + revealed: () => import("../assets/themes/mine-dogs/revealed.png"), + flag: () => import("../assets/themes/mine-dogs/flag-2.png"), + questionMark: () => import("../assets/themes/mine-dogs/question-mark.png"), + lastPos: () => import("../assets/themes/mine-dogs/last-pos.png"), + 1: () => import("../assets/themes/mine-dogs/1.png"), + 2: () => import("../assets/themes/mine-dogs/2.png"), + 3: () => import("../assets/themes/mine-dogs/3.png"), + 4: () => import("../assets/themes/mine-dogs/4.png"), + 5: () => import("../assets/themes/mine-dogs/5.png"), + 6: () => import("../assets/themes/mine-dogs/6.png"), + 7: () => import("../assets/themes/mine-dogs/7.png"), + 8: () => import("../assets/themes/mine-dogs/8.png"), +}; diff --git a/src/themes/minecraft-nether.ts b/src/themes/minecraft-nether.ts new file mode 100644 index 0000000..dcd09d1 --- /dev/null +++ b/src/themes/minecraft-nether.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const minecraftNetherTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/minecraft-nether/mine.png"), + tile: () => import("../assets/themes/minecraft-nether/tile.png"), + revealed: () => import("../assets/themes/minecraft-nether/revealed.png"), + flag: () => import("../assets/themes/minecraft-nether/flag.png"), + questionMark: () => import("../assets/themes/minecraft-nether/question-mark.png"), + lastPos: () => import("../assets/themes/minecraft-nether/last-pos.png"), + 1: () => import("../assets/themes/minecraft-nether/1.png"), + 2: () => import("../assets/themes/minecraft-nether/2.png"), + 3: () => import("../assets/themes/minecraft-nether/3.png"), + 4: () => import("../assets/themes/minecraft-nether/4.png"), + 5: () => import("../assets/themes/minecraft-nether/5.png"), + 6: () => import("../assets/themes/minecraft-nether/6.png"), + 7: () => import("../assets/themes/minecraft-nether/7.png"), + 8: () => import("../assets/themes/minecraft-nether/8.png"), +}; diff --git a/src/themes/minecraft-overworld.ts b/src/themes/minecraft-overworld.ts new file mode 100644 index 0000000..4024cb9 --- /dev/null +++ b/src/themes/minecraft-overworld.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const minecraftOverworldTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/minecraft-overworld/mine.png"), + tile: () => import("../assets/themes/minecraft-overworld/tile.png"), + revealed: () => import("../assets/themes/minecraft-overworld/revealed.png"), + flag: () => import("../assets/themes/minecraft-overworld/flag.png"), + questionMark: () => import("../assets/themes/minecraft-overworld/question-mark.png"), + lastPos: () => import("../assets/themes/minecraft-overworld/last-pos.png"), + 1: () => import("../assets/themes/minecraft-overworld/1.png"), + 2: () => import("../assets/themes/minecraft-overworld/2.png"), + 3: () => import("../assets/themes/minecraft-overworld/3.png"), + 4: () => import("../assets/themes/minecraft-overworld/4.png"), + 5: () => import("../assets/themes/minecraft-overworld/5.png"), + 6: () => import("../assets/themes/minecraft-overworld/6.png"), + 7: () => import("../assets/themes/minecraft-overworld/7.png"), + 8: () => import("../assets/themes/minecraft-overworld/8.png"), +}; diff --git a/src/themes/poop.ts b/src/themes/poop.ts new file mode 100644 index 0000000..77e6e4c --- /dev/null +++ b/src/themes/poop.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const poopTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/poop/mine.png"), + tile: () => import("../assets/themes/poop/tile.png"), + revealed: () => import("../assets/themes/poop/revealed.png"), + flag: () => import("../assets/themes/poop/flag.png"), + questionMark: () => import("../assets/themes/poop/question-mark.png"), + lastPos: () => import("../assets/themes/poop/last-pos.png"), + 1: () => import("../assets/themes/poop/1.png"), + 2: () => import("../assets/themes/poop/2.png"), + 3: () => import("../assets/themes/poop/3.png"), + 4: () => import("../assets/themes/poop/4.png"), + 5: () => import("../assets/themes/poop/5.png"), + 6: () => import("../assets/themes/poop/6.png"), + 7: () => import("../assets/themes/poop/7.png"), + 8: () => import("../assets/themes/poop/8.png"), +}; diff --git a/src/themes/retro-wave.ts b/src/themes/retro-wave.ts new file mode 100644 index 0000000..8b859f9 --- /dev/null +++ b/src/themes/retro-wave.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const retroWaveTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/retro-wave/mine.png"), + tile: () => import("../assets/themes/retro-wave/tile.png"), + revealed: () => import("../assets/themes/retro-wave/revealed.png"), + flag: () => import("../assets/themes/retro-wave/flag.png"), + questionMark: () => import("../assets/themes/retro-wave/question-mark.png"), + lastPos: () => import("../assets/themes/retro-wave/last-pos.png"), + 1: () => import("../assets/themes/retro-wave/1.png"), + 2: () => import("../assets/themes/retro-wave/2.png"), + 3: () => import("../assets/themes/retro-wave/3.png"), + 4: () => import("../assets/themes/retro-wave/4.png"), + 5: () => import("../assets/themes/retro-wave/5.png"), + 6: () => import("../assets/themes/retro-wave/6.png"), + 7: () => import("../assets/themes/retro-wave/7.png"), + 8: () => import("../assets/themes/retro-wave/8.png"), +}; diff --git a/src/themes/romance.ts b/src/themes/romance.ts new file mode 100644 index 0000000..a39555b --- /dev/null +++ b/src/themes/romance.ts @@ -0,0 +1,19 @@ +import type { Theme } from "./Theme"; + +export const romanceTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/romance/mine.png"), + tile: () => import("../assets/themes/romance/tile.png"), + revealed: () => import("../assets/themes/romance/revealed.png"), + flag: () => import("../assets/themes/romance/flag.png"), + questionMark: () => import("../assets/themes/romance/question-mark.png"), + lastPos: () => import("../assets/themes/romance/last-pos.png"), + 1: () => import("../assets/themes/romance/1.png"), + 2: () => import("../assets/themes/romance/2.png"), + 3: () => import("../assets/themes/romance/3.png"), + 4: () => import("../assets/themes/romance/4.png"), + 5: () => import("../assets/themes/romance/5.png"), + 6: () => import("../assets/themes/romance/6.png"), + 7: () => import("../assets/themes/romance/7.png"), + 8: () => import("../assets/themes/romance/8.png"), +}; diff --git a/src/themes/techies-dire.ts b/src/themes/techies-dire.ts new file mode 100644 index 0000000..ad7a799 --- /dev/null +++ b/src/themes/techies-dire.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const techiesDireTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/techies/dire/mine-1.png"), + tile: () => import("../assets/themes/techies/dire/tile-1.png"), + revealed: () => import("../assets/themes/techies/dire/revealed.png"), + flag: () => import("../assets/themes/techies/flag.png"), + questionMark: () => import("../assets/themes/techies/dire/question-mark.png"), + lastPos: () => import("../assets/themes/techies/dire/last-pos.png"), + 1: () => import("../assets/themes/techies/dire/1.png"), + 2: () => import("../assets/themes/techies/dire/2.png"), + 3: () => import("../assets/themes/techies/dire/3.png"), + 4: () => import("../assets/themes/techies/dire/4.png"), + 5: () => import("../assets/themes/techies/dire/5.png"), + 6: () => import("../assets/themes/techies/dire/6.png"), + 7: () => import("../assets/themes/techies/dire/7.png"), + 8: () => import("../assets/themes/techies/dire/8.png"), +}; diff --git a/src/themes/techies-radiant.ts b/src/themes/techies-radiant.ts new file mode 100644 index 0000000..117f5df --- /dev/null +++ b/src/themes/techies-radiant.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const techiesRadiantTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/techies/radiant/mine-1.png"), + tile: () => import("../assets/themes/techies/radiant/tile-1.png"), + revealed: () => import("../assets/themes/techies/radiant/revealed-1.png"), + flag: () => import("../assets/themes/techies/flag.png"), + questionMark: () => import("../assets/themes/techies/radiant/question-mark.png"), + lastPos: () => import("../assets/themes/techies/radiant/last-pos.png"), + 1: () => import("../assets/themes/techies/radiant/1.png"), + 2: () => import("../assets/themes/techies/radiant/2.png"), + 3: () => import("../assets/themes/techies/radiant/3.png"), + 4: () => import("../assets/themes/techies/radiant/4.png"), + 5: () => import("../assets/themes/techies/radiant/5.png"), + 6: () => import("../assets/themes/techies/radiant/6.png"), + 7: () => import("../assets/themes/techies/radiant/7.png"), + 8: () => import("../assets/themes/techies/radiant/8.png"), +}; diff --git a/src/themes/tron-blue.ts b/src/themes/tron-blue.ts new file mode 100644 index 0000000..59d56b2 --- /dev/null +++ b/src/themes/tron-blue.ts @@ -0,0 +1,20 @@ +import { Theme } from "./Theme"; + +export const tronBlueTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/tron/tron-blue/mine.png"), + tile: () => import("../assets/themes/tron/tron-blue/tile.png"), + revealed: () => import("../assets/themes/tron/tron-blue/revealed.png"), + flag: () => import("../assets/themes/tron/tron-blue/flag.png"), + questionMark: () => + import("../assets/themes/tron/tron-blue/question-mark.png"), + lastPos: () => import("../assets/themes/tron/tron-blue/last-pos.png"), + 1: () => import("../assets/themes/tron/tron-blue/1.png"), + 2: () => import("../assets/themes/tron/tron-blue/2.png"), + 3: () => import("../assets/themes/tron/tron-blue/3.png"), + 4: () => import("../assets/themes/tron/tron-blue/4.png"), + 5: () => import("../assets/themes/tron/tron-blue/5.png"), + 6: () => import("../assets/themes/tron/tron-blue/6.png"), + 7: () => import("../assets/themes/tron/tron-blue/7.png"), + 8: () => import("../assets/themes/tron/tron-blue/8.png"), +}; diff --git a/src/themes/tron-orange.ts b/src/themes/tron-orange.ts new file mode 100644 index 0000000..32108bb --- /dev/null +++ b/src/themes/tron-orange.ts @@ -0,0 +1,20 @@ +import { Theme } from "./Theme"; + +export const tronOrangeTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/tron/tron-orange/mine.png"), + tile: () => import("../assets/themes/tron/tron-orange/tile.png"), + revealed: () => import("../assets/themes/tron/tron-orange/revealed.png"), + flag: () => import("../assets/themes/tron/tron-orange/flag.png"), + questionMark: () => + import("../assets/themes/tron/tron-orange/question-mark.png"), + lastPos: () => import("../assets/themes/tron/tron-orange/last-pos.png"), + 1: () => import("../assets/themes/tron/tron-orange/1.png"), + 2: () => import("../assets/themes/tron/tron-orange/2.png"), + 3: () => import("../assets/themes/tron/tron-orange/3.png"), + 4: () => import("../assets/themes/tron/tron-orange/4.png"), + 5: () => import("../assets/themes/tron/tron-orange/5.png"), + 6: () => import("../assets/themes/tron/tron-orange/6.png"), + 7: () => import("../assets/themes/tron/tron-orange/7.png"), + 8: () => import("../assets/themes/tron/tron-orange/8.png"), +}; diff --git a/src/themes/underwater.ts b/src/themes/underwater.ts new file mode 100644 index 0000000..9469c9a --- /dev/null +++ b/src/themes/underwater.ts @@ -0,0 +1,19 @@ +import { Theme } from "./Theme"; + +export const underwaterTheme: Theme = { + size: 32, + mine: () => import("../assets/themes/underwater/mine.png"), + tile: () => import("../assets/themes/underwater/tile.png"), + revealed: () => import("../assets/themes/underwater/revealed.png"), + flag: () => import("../assets/themes/underwater/flag.png"), + questionMark: () => import("../assets/themes/underwater/question-mark.png"), + lastPos: () => import("../assets/themes/underwater/last-pos.png"), + 1: () => import("../assets/themes/underwater/1.png"), + 2: () => import("../assets/themes/underwater/2.png"), + 3: () => import("../assets/themes/underwater/3.png"), + 4: () => import("../assets/themes/underwater/4.png"), + 5: () => import("../assets/themes/underwater/5.png"), + 6: () => import("../assets/themes/underwater/6.png"), + 7: () => import("../assets/themes/underwater/7.png"), + 8: () => import("../assets/themes/underwater/8.png"), +}; diff --git a/src/views/collection/Collection.tsx b/src/views/collection/Collection.tsx new file mode 100644 index 0000000..92a5193 --- /dev/null +++ b/src/views/collection/Collection.tsx @@ -0,0 +1,97 @@ +import { Ellipsis } from "lucide-react"; +import { testBoard } from "../../../shared/testBoard"; +import Board from "../../components/Board"; +import { Button } from "../../components/Button"; +import { themes } from "../../themes"; +import { + DropdownMenuTrigger, + DropdownMenu, + DropdownMenuItem, + DropdownMenuContent, +} from "../../components/DropdownMenu"; +import { cn } from "../../lib/utils"; +import { useWSMutation, useWSQuery } from "../../hooks"; + +const Collection = () => { + const { data: collection, refetch } = useWSQuery( + "user.getOwnCollection", + null, + ); + const mutateSelected = useWSMutation("user.selectCollectionEntry"); + const mutateShuffle = useWSMutation("user.addCollectionEntryToShuffle"); + + return ( +
+

Collection

+
+ {themes.map((theme) => { + const selected = collection?.entries.some( + (e) => e.id === theme.id && e.selected, + ); + const owned = collection?.entries.some((e) => e.id === theme.id); + const times = + collection?.entries.filter((e) => e.id === theme.id).length || 0; + if (!owned) return null; + return ( +
+
+

+ {theme.name} + {owned && ( + + {" "} + (Owned{times > 1 && ` ${times}x`}) + + )} +

+ {owned && ( + + + + + + { + mutateSelected + .mutateAsync({ id: theme.id }) + .then(() => refetch()); + }} + > + Select + + + mutateShuffle + .mutateAsync({ id: theme.id }) + .then(() => refetch()) + } + > + {" "} + Add to shuffle + + + + )} +
+ {}} + restartGame={() => {}} + onRightClick={() => {}} + width={11 * 32} + height={4 * 32} + className={cn( + selected && "outline-primary outline-4 rounded-md", + )} + /> +
+ ); + })} +
+
+ ); +}; + +export default Collection; diff --git a/src/views/endless/Endless.tsx b/src/views/endless/Endless.tsx new file mode 100644 index 0000000..d61ebcb --- /dev/null +++ b/src/views/endless/Endless.tsx @@ -0,0 +1,118 @@ +import Board from "../../components/Board"; +import { useWSMutation, useWSQuery } from "../../hooks"; +import { useAtom } from "jotai"; +import { gameIdAtom } from "../../atoms"; +import { Button } from "../../components/Button"; +import LeaderboardButton from "../../components/LeaderboardButton"; +import { Fragment, useEffect } from "react"; + +interface EndlessProps { + gameId?: string; +} + +const Endless: React.FC = (props) => { + const [gameId, setGameId] = useAtom(gameIdAtom); + const { data: game } = useWSQuery("game.getGameState", gameId!, !!gameId); + const { data: settings } = useWSQuery("user.getSettings", null); + const startGame = useWSMutation("game.createGame"); + const { data: leaderboard } = useWSQuery("scoreboard.getScoreBoard", 100); + const reveal = useWSMutation("game.reveal"); + const placeFlag = useWSMutation("game.placeFlag"); + const placeQuestionMark = useWSMutation("game.placeQuestionMark"); + const clearTile = useWSMutation("game.clearTile"); + + useEffect(() => { + if (props.gameId) { + setGameId(props.gameId); + } + return () => { + setGameId(undefined); + }; + }, [props.gameId, setGameId]); + + return game ? ( + <> +
+
+ {game.minesCount - game.isFlagged.flat().filter((f) => f).length} + {" | "} + Stage {game.stage} +
+
+ +
+ { + const gameId = await startGame.mutateAsync(null); + setGameId(gameId.uuid); + }} + onLeftClick={(x, y) => { + reveal.mutateAsync({ x, y }); + }} + onRightClick={(x, y) => { + const isFlagged = game.isFlagged[x][y]; + const isQuestionMark = game.isQuestionMark[x][y]; + if (!isFlagged && !isQuestionMark) { + placeFlag.mutateAsync({ x, y }); + return; + } + if (isFlagged && settings?.placeQuestionMark) { + placeQuestionMark.mutateAsync({ x, y }); + return; + } else { + clearTile.mutateAsync({ x, y }); + return; + } + }} + /> + + ) : ( +
+
+

Minesweeper Endless

+ +

How to play

+

+ Endless minesweeper is just like regular minesweeper but you + can't win. Every time you clear the field you just proceed to the + next stage. Try to get as far as possible. You might be rewarded for + great performance! +
+
+ Good luck! +

+
+
+

+ Leaderboard +

+
+ {new Array(10).fill(0).map((_, i) => ( + +
{i + 1}.
+
+ {leaderboard?.[i]?.user ?? "No User"} +
+
+ Stage {leaderboard?.[i]?.stage ?? 0} +
+
+ ))} +
+ +
+
+ ); +}; + +export default Endless; diff --git a/src/views/home/Home.tsx b/src/views/home/Home.tsx new file mode 100644 index 0000000..d444161 --- /dev/null +++ b/src/views/home/Home.tsx @@ -0,0 +1,84 @@ +import { animate, motion, useMotionValue, useTransform } from "framer-motion"; +import { useEffect } from "react"; +import { useWSQuery } from "../../hooks"; +import { Tag } from "../../components/Tag"; +import RegisterButton from "../../components/Auth/RegisterButton"; +import { Button } from "../../components/Button"; +import defusing from "../../assets/illustrations/defusing.png?aspect=4:3&w=100;200;300;400&format=webp&quality=100&as=metadata"; +import lootbox1 from "../../assets/illustrations/lootbox1.png?aspect=1:1&w=100;200;300;400&format=webp&quality=100&as=metadata"; +import mine from "../../assets/illustrations/mine.png?aspect=1:1&w=100;200;300;400&format=webp&quality=100&as=metadata"; +import Section from "./Section"; +import Hr from "../../components/Hr"; +import { Link } from "wouter"; + +const Home = () => { + const { data: userCount } = useWSQuery("user.getUserCount", null); + const { data: gameCount } = useWSQuery("game.getTotalGamesPlayed", {}); + const { data: username } = useWSQuery("user.getSelf", null); + const usersFrom = (userCount ?? 0) / 2; + const usersTo = userCount ?? 0; + const gamesFrom = (gameCount ?? 0) / 2; + const gamesTo = gameCount ?? 0; + + const usersCount = useMotionValue(usersFrom); + const roundedUsers = useTransform(usersCount, (latest) => Math.round(latest)); + const gamesCount = useMotionValue(gamesFrom); + const roundedGames = useTransform(gamesCount, (latest) => Math.round(latest)); + + useEffect(() => { + const controls = animate(usersCount, usersTo, { duration: 1.5 }); + return controls.stop; + }, [usersCount, usersTo]); + useEffect(() => { + const controls = animate(gamesCount, gamesTo, { duration: 1.5 }); + return controls.stop; + }, [gamesCount, gamesTo]); + + return ( +
+
+ + {roundedUsers} Users Played{" "} + {roundedGames} Games + +

+ Business Minesweeper +
+ + is the greatest experience + +

+ +

+ Start now +

+ {username ? ( + // @ts-expect-error We dont care since this is internal api + + ) : ( + + )} +
+
+
+
+
+
+
+
+ ); +}; + +export default Home; diff --git a/src/views/home/Section.tsx b/src/views/home/Section.tsx new file mode 100644 index 0000000..ad0cd5e --- /dev/null +++ b/src/views/home/Section.tsx @@ -0,0 +1,88 @@ +import { + easeInOut, + motion, + useMotionTemplate, + useScroll, + useTransform, +} from "framer-motion"; +import { useEffect, useRef, useState } from "react"; +import { cn } from "../../lib/utils"; + +interface SectionProps { + text: string; + image: OutputMetadata[]; + left?: boolean; +} + +const Section = ({ text, image, left }: SectionProps) => { + const ref = useRef(null); + const wrapperRef = useRef(null); + const [width, setWidth] = useState(0); + useEffect(() => { + const resizeObserver = new ResizeObserver(() => { + if (wrapperRef.current) { + setWidth(wrapperRef.current.clientWidth); + } + }); + if (wrapperRef.current) { + resizeObserver.observe(wrapperRef.current); + } + return () => resizeObserver.disconnect(); + }, []); + const { scrollYProgress } = useScroll({ + target: ref, + }); + const transform = useTransform(scrollYProgress, [0, 1], [-50, 50], { + ease: easeInOut, + }); + const translateY = useMotionTemplate`${transform}px`; + return ( +
+

{text}

+ +
+ `${i.src} ${i.width}w`).join(", ")} + sizes={`${width}px`} + loading="lazy" + className="h-[80%]" + /> +
+
+
+ ); +}; + +export default Section; diff --git a/src/views/match-history/MatchHistory.tsx b/src/views/match-history/MatchHistory.tsx new file mode 100644 index 0000000..ab475eb --- /dev/null +++ b/src/views/match-history/MatchHistory.tsx @@ -0,0 +1,46 @@ +import { useInfiniteGames, useWSQuery } from "../../hooks"; +import PastMatch from "../../components/PastMatch"; +import { useEffect, useRef } from "react"; + +const MatchHistory = () => { + const { data: user } = useWSQuery("user.getSelf", null); + const { data, hasNextPage, fetchNextPage, isFetchingNextPage } = + useInfiniteGames(user); + const loadMoreRef = useRef(null); + useEffect(() => { + const intersectionObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && !isFetchingNextPage) { + fetchNextPage(); + } + }); + }); + const target = loadMoreRef.current; + if (target) { + intersectionObserver.observe(target); + } + return () => { + if (target) { + intersectionObserver.unobserve(target); + } + }; + }, [fetchNextPage, isFetchingNextPage, hasNextPage]); + + return ( +
+ {data?.pages.map((page) => + page.data.map((game) => ), + )} + {hasNextPage && ( +
+ Loading... +
+ )} +
+ ); +}; + +export default MatchHistory; diff --git a/src/views/settings/Settings.tsx b/src/views/settings/Settings.tsx new file mode 100644 index 0000000..878073f --- /dev/null +++ b/src/views/settings/Settings.tsx @@ -0,0 +1,71 @@ +import { ReactNode } from "react"; +import { Switch } from "../../components/Switch"; +import { useWSMutation, useWSQuery } from "../../hooks"; + +interface BoolSettingProps { + label: string; + description: ReactNode; + value: boolean; + onChange: (value: boolean) => void; +} + +const BoolSetting: React.FC = ({ + label, + description, + value, + onChange, +}) => ( +
+
+ +

{description}

+
+ +
+); + +const Settings = () => { + const { data: settings, refetch } = useWSQuery("user.getSettings", null); + const updateSettings = useWSMutation("user.updateSettings"); + + return ( +
+
+

Settings

+
+ + You can place a question mark on a tile after placing a flag. +
+ Just right click again on the tile. + + } + value={settings?.placeQuestionMark ?? false} + onChange={async (value) => { + await updateSettings.mutateAsync({ placeQuestionMark: value }); + refetch(); + }} + /> + + You can long press on a tile to reveal it. This is useful for + touch devices. + + } + value={settings?.longPressOnDesktop ?? false} + onChange={async (value) => { + updateSettings.mutateAsync({ longPressOnDesktop: value }); + refetch(); + }} + /> +
+
+
+ ); +}; + +export default Settings; diff --git a/src/views/store/Store.tsx b/src/views/store/Store.tsx new file mode 100644 index 0000000..49a156d --- /dev/null +++ b/src/views/store/Store.tsx @@ -0,0 +1,204 @@ +import { Fragment } from "react/jsx-runtime"; +import { lootboxes } from "../../../shared/lootboxes"; +import { Button } from "../../components/Button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../../components/Dialog"; +import GemsIcon from "../../components/GemIcon"; +import { themes } from "../../themes"; +import { useWSMutation, useWSQuery } from "../../hooks"; +import { Rarity } from "../../components/Rarity"; +import { lootboxResultAtom } from "../../atoms"; +import { useAtom } from "jotai"; +import { useEffect } from "react"; +import Particles, { initParticlesEngine } from "@tsparticles/react"; +import { loadSlim } from "@tsparticles/slim"; +import { loadSeaAnemonePreset } from "@tsparticles/preset-sea-anemone"; +import { motion } from "framer-motion"; +import BounceImg from "../../components/BounceImg"; + +const Store = () => { + const openLootbox = useWSMutation("user.openLootbox"); + const [lootboxResult, setLootboxResult] = useAtom(lootboxResultAtom); + const currentLootbox = lootboxes.find((l) => l.id === lootboxResult?.lootbox); + const { refetch } = useWSQuery("user.getOwnGems", null); + + // this should be run only once per application lifetime + useEffect(() => { + initParticlesEngine(async (engine) => { + // you can initiate the tsParticles instance (engine) here, adding custom shapes or presets + // this loads the tsparticles package bundle, it's the easiest method for getting everything ready + // starting from v2 you can add only the features you need reducing the bundle size + //await loadAll(engine); + //await loadFull(engine); + await loadSlim(engine); + await loadSeaAnemonePreset(engine); + + //await loadBasic(engine); + }); + }, []); + + return ( + <> + setLootboxResult(undefined)} + > + + + + Opening {currentLootbox?.name} + + +
+ + + i.id == lootboxResult?.result, + )?.rarity + } + > + {themes.find((t) => t.id === lootboxResult?.result)?.name} + + +
+
+
+
+

Store

+
+
+ {lootboxes.map((lootbox) => ( +
+
+

{lootbox.name}

+ + + + + + + {lootbox.name} + +
+
+ +
+ Introducing {lootbox.name}, the first-ever + lootbox for Minesweeper, packed with a variety of + stylish skins to customize your game like never + before! With {lootbox.name}, every click + becomes a statement, as you explore new looks for + your mines, tiles, and flags. Transform your + Minesweeper grid with these visually captivating + designs and make each puzzle feel fresh and + exciting. +
+

+ What's Inside? +

+
    +
  • + Mine Skins: Swap out the classic mine icons + for creative alternatives like skulls, treasure + chests, or high-tech drones. +
  • +
  • + Tile Skins: Replace the plain tiles with + vibrant patterns such as tropical beaches, + medieval bricks, or sleek metallic plates. +
  • +
  • + Flag Skins: Mark suspected mines in style + with custom flag designs including pirate flags, + futuristic beacons, or glowing crystals. +
  • +
  • + Backgrounds: Enhance your gameplay + experience with unique backgrounds, from serene + mountain landscapes to bustling cityscapes or + outer space vistas. +
  • +
    + Step up your Minesweeper game and express your + personality with {lootbox.name}. Unlock new + looks and turn every game into a visual + masterpiece! +
    +
+
+
+
+
Skin
+
Rarity
+ {lootbox.items.map((item) => ( + +
+ {themes.find((t) => t.id === item.id)?.name} +
+ +
+ ))} +
+
+
+
+
+
+ + +
+ ))} +
+
+
+ + ); +}; + +export default Store; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..4c81fea 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,37 @@ /// + +interface OutputMetadata { + src: string; // URL of the generated image + width: number; // Width of the image + height: number; // Height of the image + format: string; // Format of the generated image + + // The following options are the same as sharps input options + space: string; // Name of colour space interpretation + channels: number; // Number of bands e.g. 3 for sRGB, 4 for CMYK + density: number; // Number of pixels per inch + depth: string; // Name of pixel depth format + hasAlpha: boolean; // presence of an alpha transparency channel + hasProfile: boolean; // presence of an embedded ICC profile + isProgressive: boolean; // indicating whether the image is interlaced using a progressive scan +} + +declare module "*&as=metadata" { + const outputs: OutputMetadata[]; + export default outputs; +} + +declare module "*&inline" { + const outputs: string; + export default outputs; +} + +declare module "*.png" { + const outputs: string; + export default outputs; +} + +declare module "*?as=metadata" { + const outputs: OutputMetadata[]; + export default outputs; +} diff --git a/src/ws.ts b/src/ws.ts deleted file mode 100644 index 3ec0d1b..0000000 --- a/src/ws.ts +++ /dev/null @@ -1,53 +0,0 @@ -import toast from "react-hot-toast"; -import useGameStore from "./GameState"; - -let ws: WebSocket; - -export const connectWS = () => { - ws = new WebSocket("wss://mb.gordon.business/ws"); - ws.onmessage = (event) => { - const data = JSON.parse(event.data); - const name = localStorage.getItem("name"); - console.log(data); - if (data.user === name) { - return; - } - if (!useGameStore.getState().showFeed) return; - switch (data.type) { - case "new": - toast(data.user + " started a new game", { - icon: "🚀", - style: { - borderRadius: "10px", - background: "#333", - color: "#fff", - }, - }); - break; - case "loss": - toast("Game over by " + data.user + " stage " + data.stage, { - icon: "😢", - style: { - borderRadius: "10px", - background: "#333", - color: "#fff", - }, - }); - break; - } - }; -}; - -export const newGame = (user: string) => { - if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type: "new", user })); - } else { - setTimeout(() => { - newGame(user); - }, 100); - } -}; - -export const loseGame = (user: string, stage: number) => { - ws.send(JSON.stringify({ type: "loss", user, stage })); -}; diff --git a/src/wsClient.ts b/src/wsClient.ts new file mode 100644 index 0000000..ce4293e --- /dev/null +++ b/src/wsClient.ts @@ -0,0 +1,137 @@ +import type { Routes } from "../backend/router"; +import type { Events } from "../shared/events"; +import { queryClient } from "./queryClient"; + +const connectionString = import.meta.env.DEV + ? "ws://localhost:8072/ws" + : "wss://mbv2.gordon.business/ws"; + +const messageListeners = new Set<(event: MessageEvent) => void>(); +export const addMessageListener = (listener: (event: MessageEvent) => void) => { + messageListeners.add(listener); +}; +export const removeMessageListener = ( + listener: (event: MessageEvent) => void, +) => { + messageListeners.delete(listener); +}; + +const emitMessage = (event: MessageEvent) => { + messageListeners.forEach((listener) => listener(event)); +}; + +const createWSClient = () => { + let ws = new WebSocket(connectionString); + let reconnectAttempts = 0; + const maxReconnectAttempts = 5; + let isAuthenticated = false; + + const connect = () => { + ws = new WebSocket(connectionString); + + ws.onopen = async () => { + reconnectAttempts = 0; + const token = localStorage.getItem("loginToken"); + if (token) { + try { + await dispatch("user.loginWithToken", { token: JSON.parse(token) }); + isAuthenticated = true; + } catch (e) { + console.error("Re-authentication failed", e); + } + } + }; + + ws.onmessage = emitMessage; + + ws.onclose = () => { + if (reconnectAttempts < maxReconnectAttempts) { + setTimeout(() => { + reconnectAttempts++; + connect(); + }, 1000 * reconnectAttempts); + } else { + console.error("Max reconnect attempts reached"); + } + }; + + ws.onerror = (err) => { + console.error("WebSocket error", err); + ws.close(); + }; + }; + + connect(); + + addMessageListener((event: MessageEvent) => { + const data = JSON.parse(event.data) as Events; + if (data.type === "updateGame") { + queryClient.refetchQueries({ + queryKey: ["game.getGameState", data.game], + }); + } + if (data.type === "loss") { + queryClient.invalidateQueries({ + queryKey: ["scoreboard.getScoreBoard", 10], + }); + } + if (data.type === "gemsRewarded") { + queryClient.invalidateQueries({ + queryKey: ["user.getOwnGems", null], + }); + } + if (import.meta.env.DEV) { + console.log("Received message", data); + } + }); + + const dispatch = async < + TController extends keyof Routes, + TAction extends keyof Routes[TController] & string, + >( + action: `${TController}.${TAction}`, + // @ts-expect-error We dont care since this is internal api + payload: Routes[TController][TAction]["validate"]["_input"], + // @ts-expect-error We dont care since this is internal api + ): Promise>> => { + if (ws.readyState !== WebSocket.OPEN) { + await new Promise((res) => { + const onOpen = () => { + ws.removeEventListener("open", onOpen); + res(); + }; + ws.addEventListener("open", onOpen); + }); + } + const requestId = crypto.randomUUID(); + ws.send( + JSON.stringify({ + type: action, + payload, + id: requestId, + }), + ); + return new Promise< + // @ts-expect-error We dont care since this is internal api + Awaited> + >((res, rej) => { + const listener = (event: MessageEvent) => { + const data = JSON.parse(event.data); + if (data.id === requestId) { + removeMessageListener(listener); + if (data.error) { + rej(data.error); + } else { + res(data.payload); + } + } + }; + addMessageListener(listener); + }); + }; + return { + dispatch, + }; +}; + +export const wsClient = createWSClient(); diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.node.json b/tsconfig.node.json index e1c9f11..595c2e8 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -24,5 +24,5 @@ "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false }, - "include": ["vite.config.ts", "backend/**/*.ts"] + "include": ["vite.config.ts", "backend/**/*.ts", "src", "backend"] } diff --git a/vite.config.ts b/vite.config.ts index 861b04b..9ffaf19 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,12 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +import tailwindcss from "@tailwindcss/vite"; +import { imagetools } from "vite-imagetools"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], -}) + server: { + port: 3003, + }, + plugins: [react(), tailwindcss(), imagetools()], +});