diff --git a/.prettierrc b/.prettierrc
index d7a5a9c..ec9a813 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -4,5 +4,6 @@
"tabWidth": 4,
"useTabs": false,
"trailingComma": "es5",
- "singleQuote": true
+ "singleQuote": true,
+ "plugins": ["prettier-plugin-svelte"]
}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index bdef820..fd2f1b8 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,3 +1,3 @@
{
- "recommendations": ["svelte.svelte-vscode"]
+ "recommendations": ["svelte.svelte-vscode"]
}
diff --git a/index.html b/index.html
index af8c531..29abb86 100644
--- a/index.html
+++ b/index.html
@@ -1,16 +1,20 @@
-
-
-
-
- Doodly
-
-
-
-
-
-
-
-
+
+
+
+
+ Doodly
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 753105a..7bf24bf 100644
--- a/package.json
+++ b/package.json
@@ -1,34 +1,35 @@
{
- "name": "doodly",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite --host 0.0.0.0",
- "build": "vite build",
- "preview": "vite preview",
- "check": "svelte-check --tsconfig ./tsconfig.json",
- "dev:server": "node --loader ts-node/esm src/server/main.ts",
- "format:check": "prettier --check .",
- "format": "prettier --write ."
- },
- "devDependencies": {
- "@sveltejs/vite-plugin-svelte": "^1.0.1",
- "@tsconfig/svelte": "^3.0.0",
- "prettier": "^2.7.1",
- "svelte": "^3.49.0",
- "svelte-check": "^2.8.0",
- "svelte-preprocess": "^4.10.7",
- "ts-node": "^10.9.1",
- "tslib": "^2.4.0",
- "typescript": "^4.6.4",
- "vite": "^3.0.6",
- "vite-plugin-pwa": "^0.12.3"
- },
- "dependencies": {
- "@fontsource/noto-sans": "^4.5.11",
- "bulma": "^0.9.4",
- "socket.io": "^4.5.1",
- "socket.io-client": "^4.5.1"
- }
-}
\ No newline at end of file
+ "name": "doodly",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "build": "vite build",
+ "preview": "vite preview",
+ "check": "svelte-check --tsconfig ./tsconfig.json",
+ "dev:server": "node --loader ts-node/esm src/server/main.ts",
+ "format:check": "prettier --check . ./**/*.svelte",
+ "format": "prettier --write . ./**/*.svelte"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^1.0.1",
+ "@tsconfig/svelte": "^3.0.0",
+ "prettier": "^2.7.1",
+ "prettier-plugin-svelte": "^2.7.0",
+ "svelte": "^3.49.0",
+ "svelte-check": "^2.8.0",
+ "svelte-preprocess": "^4.10.7",
+ "ts-node": "^10.9.1",
+ "tslib": "^2.4.0",
+ "typescript": "^4.6.4",
+ "vite": "^3.0.6",
+ "vite-plugin-pwa": "^0.12.3"
+ },
+ "dependencies": {
+ "@fontsource/noto-sans": "^4.5.11",
+ "bulma": "^0.9.4",
+ "socket.io": "^4.5.1",
+ "socket.io-client": "^4.5.1"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 066787c..a6a0075 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -6,6 +6,7 @@ specifiers:
'@tsconfig/svelte': ^3.0.0
bulma: ^0.9.4
prettier: ^2.7.1
+ prettier-plugin-svelte: ^2.7.0
socket.io: ^4.5.1
socket.io-client: ^4.5.1
svelte: ^3.49.0
@@ -27,6 +28,7 @@ devDependencies:
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.6
'@tsconfig/svelte': 3.0.0
prettier: 2.7.1
+ prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi
svelte: 3.49.0
svelte-check: 2.8.0_svelte@3.49.0
svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji
@@ -2667,6 +2669,16 @@ packages:
source-map-js: 1.0.2
dev: true
+ /prettier-plugin-svelte/2.7.0_o3ioganyptcsrh6x4hnxvjkpqi:
+ resolution: {integrity: sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==}
+ peerDependencies:
+ prettier: ^1.16.4 || ^2.0.0
+ svelte: ^3.2.0
+ dependencies:
+ prettier: 2.7.1
+ svelte: 3.49.0
+ dev: true
+
/prettier/2.7.1:
resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
engines: {node: '>=10.13.0'}
diff --git a/src/App.svelte b/src/App.svelte
index 241ec93..ab0ead2 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -1,171 +1,190 @@
- {#if !connected}
- error = ''}>
- {:else}
- {#if gameState === 'waiting'}
-
+ {#if !connected}
+ (error = '')}
+ />
+ {:else if gameState === 'waiting'}
+
{:else if gameState === 'waitingForWord'}
-
-
- Waiting for a word to be picked...
-
-
+
+ Waiting for a word to be picked...
+
{:else if gameState === 'selectingWord'}
- socket.emit('pickWord', event.detail)} />
+ socket.emit('pickWord', event.detail)}
+ />
{:else if gameState === 'guessing'}
- socket.emit('guessWord', e.detail, (correct) => {
- if (correct) {
- gameState = 'guessedCorrectly';
- }
- })} />
+
+ socket.emit('guessWord', e.detail, (correct) => {
+ if (correct) {
+ gameState = 'guessedCorrectly';
+ }
+ })}
+ />
{:else if gameState === 'guessedCorrectly'}
- Correct!
-
- Waiting for the other players to finish...
-
+ Correct!
+ Waiting for the other players to finish...
{:else if gameState === 'drawing'}
-
-
- Start drawing!
-
-
+
+ Start drawing!
+
-
+
{:else if gameState === 'gameOver'}
-
+
{/if}
- {/if}
diff --git a/src/app.css b/src/app.css
index 0e92e3d..0cafcea 100644
--- a/src/app.css
+++ b/src/app.css
@@ -1,13 +1,13 @@
body {
- font-family: 'Noto Sans', sans-serif;
- /* background-color: #212;
+ font-family: 'Noto Sans', sans-serif;
+ /* background-color: #212;
color: #ded;
box-sizing: border-box; */
}
* {
- box-sizing: inherit;
+ box-sizing: inherit;
}
/* input, button {
diff --git a/src/lib/socket.ts b/src/lib/socket.ts
index 382448b..2838bf5 100644
--- a/src/lib/socket.ts
+++ b/src/lib/socket.ts
@@ -19,7 +19,13 @@ export interface GameOver {
type Result = { error: false; data: T } | { error: true; message: string };
export interface ClientToServerEvents {
- joinGame: (id: string, username: string, callback: (result: Result<{id: string, isHost: boolean, players: Player[]}>) => void) => void;
+ joinGame: (
+ id: string,
+ username: string,
+ callback: (
+ result: Result<{ id: string; isHost: boolean; players: Player[] }>
+ ) => void
+ ) => void;
startGame: () => void;
pickWord: (word: string) => void;
@@ -35,7 +41,14 @@ export interface Player {
isHost: boolean;
}
-export type GameState = 'waiting' | 'selectingWord' | 'waitingForWord' | 'drawing' | 'guessing' | 'guessedCorrectly' | 'gameOver';
+export type GameState =
+ | 'waiting'
+ | 'selectingWord'
+ | 'waitingForWord'
+ | 'drawing'
+ | 'guessing'
+ | 'guessedCorrectly'
+ | 'gameOver';
export interface ChatMessage {
author: string;
diff --git a/src/main.ts b/src/main.ts
index ae18da5..f24b60b 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -4,7 +4,7 @@ import '@fontsource/noto-sans';
import 'bulma/css/bulma.css';
const app = new App({
- target: document.getElementById('app'),
+ target: document.getElementById('app'),
});
export default app;
diff --git a/src/server/game.ts b/src/server/game.ts
index c6591c6..15ef118 100644
--- a/src/server/game.ts
+++ b/src/server/game.ts
@@ -1,7 +1,12 @@
-import type { Server, Socket } from "socket.io";
-import type { ClientToServerEvents, Player, ServerToClientEvents, SocketData } from "src/lib/socket";
-import { levenshteinDistance } from "../lib/util.js";
-import randomWords from "./words.js";
+import type { Server, Socket } from 'socket.io';
+import type {
+ ClientToServerEvents,
+ Player,
+ ServerToClientEvents,
+ SocketData,
+} from 'src/lib/socket';
+import { levenshteinDistance } from '../lib/util.js';
+import randomWords from './words.js';
export class Game {
io: Server;
@@ -14,13 +19,24 @@ export class Game {
currentWord: string | null;
successfulPlayers: [string, number][];
- constructor(gameId: string, io: Server) {
+ constructor(
+ gameId: string,
+ io: Server
+ ) {
this.io = io;
this.gameId = gameId;
this.players = new Map();
}
- playerJoin(socket: Socket, username: string): string | Player {
+ playerJoin(
+ socket: Socket<
+ ClientToServerEvents,
+ ServerToClientEvents,
+ {},
+ SocketData
+ >,
+ username: string
+ ): string | Player {
for (const player of this.players.values()) {
if (player.username === username) {
return 'username taken';
@@ -44,18 +60,26 @@ export class Game {
/**
* Called when a client socket disconnects.
- *
+ *
* @param socket Socket that has just disconnected
* @returns true if the game is empty, false otherwise
*/
- playerLeft(socket: Socket): boolean {
+ playerLeft(
+ socket: Socket<
+ ClientToServerEvents,
+ ServerToClientEvents,
+ {},
+ SocketData
+ >
+ ): boolean {
const player = this.players.get(socket.id);
this.players.delete(socket.id);
if (this.players.size === 0) return true;
if (player.isHost) {
// we need to assign a new host for this room
const playerIds = Array.from(this.players.keys());
- const newHostId = playerIds[Math.floor(Math.random() * playerIds.length)];
+ const newHostId =
+ playerIds[Math.floor(Math.random() * playerIds.length)];
const newHost = this.players.get(newHostId);
newHost.isHost = true;
this.io.to(this.gameId).emit('becameHost', newHost.username);
@@ -70,7 +94,14 @@ export class Game {
* Will pick a random player to choose a word, and then
* broadcast the start to the other players.
*/
- gameStart(socket: Socket) {
+ gameStart(
+ socket: Socket<
+ ClientToServerEvents,
+ ServerToClientEvents,
+ {},
+ SocketData
+ >
+ ) {
if (!this.players.get(socket.id).isHost) return;
const playerIds = Array.from(this.players.keys());
@@ -82,7 +113,15 @@ export class Game {
this.io.to(player).emit('pickWord', words);
}
- pickWord(socket: Socket, word: string) {
+ pickWord(
+ socket: Socket<
+ ClientToServerEvents,
+ ServerToClientEvents,
+ {},
+ SocketData
+ >,
+ word: string
+ ) {
if (socket.id !== this.currentPlayer) return;
this.currentWord = word;
this.io.to(this.gameId).except(socket.id).emit('startGuessing');
@@ -92,25 +131,57 @@ export class Game {
/**
* Called when a player makes a guess during a game.
- *
+ *
* @param socket The socket of the player that made the guess
* @param guess The word that the player guessed
* @returns true if the word was correct, false otherwise
*/
- guessWord(socket: Socket, guess: string): boolean {
- if (!this.currentWord || this.successfulPlayers.find(([id, _]) => id === socket.id) !== undefined) return;
+ guessWord(
+ socket: Socket<
+ ClientToServerEvents,
+ ServerToClientEvents,
+ {},
+ SocketData
+ >,
+ guess: string
+ ): boolean {
+ if (
+ !this.currentWord ||
+ this.successfulPlayers.find(([id, _]) => id === socket.id) !==
+ undefined
+ )
+ return;
const player = this.players.get(socket.id);
- if (guess.toLocaleLowerCase() === this.currentWord.toLocaleLowerCase()) {
+ if (
+ guess.toLocaleLowerCase() === this.currentWord.toLocaleLowerCase()
+ ) {
// Correct answer!
- this.io.to(this.gameId).except(socket.id).emit('chatMessage', 'game', `${player.username} guessed the correct word`);
- this.successfulPlayers.push([socket.id, new Date().getTime() - this.startTime]);
+ this.io
+ .to(this.gameId)
+ .except(socket.id)
+ .emit(
+ 'chatMessage',
+ 'game',
+ `${player.username} guessed the correct word`
+ );
+ this.successfulPlayers.push([
+ socket.id,
+ new Date().getTime() - this.startTime,
+ ]);
return true;
- } else if (levenshteinDistance(guess.toLocaleLowerCase(), this.currentWord.toLocaleLowerCase()) <= 2) {
+ } else if (
+ levenshteinDistance(
+ guess.toLocaleLowerCase(),
+ this.currentWord.toLocaleLowerCase()
+ ) <= 2
+ ) {
// close guess
- this.io.to(socket.id).emit('chatMessage', 'game', `${guess} is close!`);
+ this.io
+ .to(socket.id)
+ .emit('chatMessage', 'game', `${guess} is close!`);
}
// Broadcast incorrect guesses to all players
@@ -138,7 +209,10 @@ export class Game {
private isGameOver(): boolean {
for (const id of this.players.keys()) {
- if (id !== this.currentPlayer && this.successfulPlayers.find(([i, _]) => id === i) === undefined) {
+ if (
+ id !== this.currentPlayer &&
+ this.successfulPlayers.find(([i, _]) => id === i) === undefined
+ ) {
return false;
}
}
diff --git a/src/server/main.ts b/src/server/main.ts
index 72d389a..556039f 100644
--- a/src/server/main.ts
+++ b/src/server/main.ts
@@ -55,7 +55,9 @@ io.on('connection', (socket) => {
io.to(gameId).emit('playerJoined', username);
socket.data.gameId = gameId;
- socket.rooms.forEach(room => room !== socket.id && socket.leave(room));
+ socket.rooms.forEach(
+ (room) => room !== socket.id && socket.leave(room)
+ );
socket.join(gameId);
const players = Array.from(room.players.values());
@@ -82,14 +84,14 @@ io.on('connection', (socket) => {
socket.on('pickWord', (word) => {
if (!socket.data.gameId) return;
const game = games.get(socket.data.gameId);
-
+
game.pickWord(socket, word);
});
socket.on('guessWord', (word, callback) => {
if (!socket.data.gameId) return;
const game = games.get(socket.data.gameId);
-
+
const result = game.guessWord(socket, word);
callback(result);
game.checkGameOver();
diff --git a/src/server/words.ts b/src/server/words.ts
index 148bcf5..765e976 100644
--- a/src/server/words.ts
+++ b/src/server/words.ts
@@ -1,4 +1,4 @@
-import words from '../assets/wordlist.json' assert {type: 'json'};
+import words from '../assets/wordlist.json' assert { type: 'json' };
export default function randomWords(count = 3): string[] {
const randomWords: string[] = [];
diff --git a/svelte.config.js b/svelte.config.js
index 3630bb3..f135239 100644
--- a/svelte.config.js
+++ b/svelte.config.js
@@ -1,7 +1,7 @@
-import sveltePreprocess from 'svelte-preprocess'
+import sveltePreprocess from 'svelte-preprocess';
export default {
- // Consult https://github.com/sveltejs/svelte-preprocess
- // for more information about preprocessors
- preprocess: sveltePreprocess()
-}
+ // Consult https://github.com/sveltejs/svelte-preprocess
+ // for more information about preprocessors
+ preprocess: sveltePreprocess(),
+};
diff --git a/tsconfig.json b/tsconfig.json
index d383031..e680bce 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,21 +1,26 @@
{
- "extends": "@tsconfig/svelte/tsconfig.json",
- "compilerOptions": {
- "target": "ESNext",
- "useDefineForClassFields": true,
- "module": "ESNext",
- "resolveJsonModule": true,
- "baseUrl": ".",
- /**
- * Typecheck JS in `.svelte` and `.js` files by default.
- * Disable checkJs if you'd like to use dynamic types in JS.
- * Note that setting allowJs false does not prevent the use
- * of JS in `.svelte` files.
- */
- "allowJs": true,
- "checkJs": true,
- "isolatedModules": true
- },
- "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
- "references": [{ "path": "./tsconfig.node.json" }]
+ "extends": "@tsconfig/svelte/tsconfig.json",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "resolveJsonModule": true,
+ "baseUrl": ".",
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable checkJs if you'd like to use dynamic types in JS.
+ * Note that setting allowJs false does not prevent the use
+ * of JS in `.svelte` files.
+ */
+ "allowJs": true,
+ "checkJs": true,
+ "isolatedModules": true
+ },
+ "include": [
+ "src/**/*.d.ts",
+ "src/**/*.ts",
+ "src/**/*.js",
+ "src/**/*.svelte"
+ ],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 65dbdb9..bda3d9d 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -1,8 +1,8 @@
{
- "compilerOptions": {
- "composite": true,
- "module": "ESNext",
- "moduleResolution": "Node"
- },
- "include": ["vite.config.ts"]
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node"
+ },
+ "include": ["vite.config.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 001cd8b..272ea19 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,30 +1,31 @@
-import { defineConfig } from 'vite'
-import { svelte } from '@sveltejs/vite-plugin-svelte'
+import { defineConfig } from 'vite';
+import { svelte } from '@sveltejs/vite-plugin-svelte';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [
- svelte(),
- VitePWA({
- includeAssets: ['favicon.png', 'apple-touch-icon.png'],
- manifest: {
- name: 'Doodly',
- short_name: 'Doodly',
- description: 'Drawing game with friends',
- theme_color: '#ffffff',
- icons: [
- {
- src: 'favicon.png',
- sizes: '256x256',
- type: 'image/png',
- },{
- src: 'favicon-192.png',
- sizes: '192x192',
- type: 'image/png',
- },
- ],
- },
- }),
- ],
+ plugins: [
+ svelte(),
+ VitePWA({
+ includeAssets: ['favicon.png', 'apple-touch-icon.png'],
+ manifest: {
+ name: 'Doodly',
+ short_name: 'Doodly',
+ description: 'Drawing game with friends',
+ theme_color: '#ffffff',
+ icons: [
+ {
+ src: 'favicon.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ {
+ src: 'favicon-192.png',
+ sizes: '192x192',
+ type: 'image/png',
+ },
+ ],
+ },
+ }),
+ ],
});