chore: run prettier
This commit is contained in:
parent
2a7c87605c
commit
a302992c6a
16 changed files with 397 additions and 265 deletions
|
@ -4,5 +4,6 @@
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"singleQuote": true
|
"singleQuote": true,
|
||||||
|
"plugins": ["prettier-plugin-svelte"]
|
||||||
}
|
}
|
||||||
|
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"recommendations": ["svelte.svelte-vscode"]
|
"recommendations": ["svelte.svelte-vscode"]
|
||||||
}
|
}
|
||||||
|
|
30
index.html
30
index.html
|
@ -1,16 +1,20 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="Draw with friends">
|
<meta name="description" content="Draw with friends" />
|
||||||
<title>Doodly</title>
|
<title>Doodly</title>
|
||||||
<link rel="icon" href="/favicon.png">
|
<link rel="icon" href="/favicon.png" />
|
||||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
|
<link
|
||||||
<meta name="theme-color" content="#ffffff">
|
rel="apple-touch-icon"
|
||||||
</head>
|
href="/apple-touch-icon.png"
|
||||||
<body>
|
sizes="180x180"
|
||||||
<div id="app"></div>
|
/>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<meta name="theme-color" content="#ffffff" />
|
||||||
</body>
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
67
package.json
67
package.json
|
@ -1,34 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "doodly",
|
"name": "doodly",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
"dev:server": "node --loader ts-node/esm src/server/main.ts",
|
"dev:server": "node --loader ts-node/esm src/server/main.ts",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check . ./**/*.svelte",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write . ./**/*.svelte"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"svelte": "^3.49.0",
|
"prettier-plugin-svelte": "^2.7.0",
|
||||||
"svelte-check": "^2.8.0",
|
"svelte": "^3.49.0",
|
||||||
"svelte-preprocess": "^4.10.7",
|
"svelte-check": "^2.8.0",
|
||||||
"ts-node": "^10.9.1",
|
"svelte-preprocess": "^4.10.7",
|
||||||
"tslib": "^2.4.0",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.6.4",
|
"tslib": "^2.4.0",
|
||||||
"vite": "^3.0.6",
|
"typescript": "^4.6.4",
|
||||||
"vite-plugin-pwa": "^0.12.3"
|
"vite": "^3.0.6",
|
||||||
},
|
"vite-plugin-pwa": "^0.12.3"
|
||||||
"dependencies": {
|
},
|
||||||
"@fontsource/noto-sans": "^4.5.11",
|
"dependencies": {
|
||||||
"bulma": "^0.9.4",
|
"@fontsource/noto-sans": "^4.5.11",
|
||||||
"socket.io": "^4.5.1",
|
"bulma": "^0.9.4",
|
||||||
"socket.io-client": "^4.5.1"
|
"socket.io": "^4.5.1",
|
||||||
}
|
"socket.io-client": "^4.5.1"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ specifiers:
|
||||||
'@tsconfig/svelte': ^3.0.0
|
'@tsconfig/svelte': ^3.0.0
|
||||||
bulma: ^0.9.4
|
bulma: ^0.9.4
|
||||||
prettier: ^2.7.1
|
prettier: ^2.7.1
|
||||||
|
prettier-plugin-svelte: ^2.7.0
|
||||||
socket.io: ^4.5.1
|
socket.io: ^4.5.1
|
||||||
socket.io-client: ^4.5.1
|
socket.io-client: ^4.5.1
|
||||||
svelte: ^3.49.0
|
svelte: ^3.49.0
|
||||||
|
@ -27,6 +28,7 @@ devDependencies:
|
||||||
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.6
|
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.6
|
||||||
'@tsconfig/svelte': 3.0.0
|
'@tsconfig/svelte': 3.0.0
|
||||||
prettier: 2.7.1
|
prettier: 2.7.1
|
||||||
|
prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi
|
||||||
svelte: 3.49.0
|
svelte: 3.49.0
|
||||||
svelte-check: 2.8.0_svelte@3.49.0
|
svelte-check: 2.8.0_svelte@3.49.0
|
||||||
svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji
|
svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji
|
||||||
|
@ -2667,6 +2669,16 @@ packages:
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
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:
|
/prettier/2.7.1:
|
||||||
resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
|
resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
283
src/App.svelte
283
src/App.svelte
|
@ -1,171 +1,190 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { io, type Socket } from "socket.io-client";
|
import { io, type Socket } from 'socket.io-client';
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
import WaitingLobby from "./client/WaitingLobby.svelte";
|
import WaitingLobby from './client/WaitingLobby.svelte';
|
||||||
import JoinPage from "./client/JoinPage.svelte";
|
import JoinPage from './client/JoinPage.svelte';
|
||||||
import type { ChatMessage, ClientToServerEvents, GameState, Player, ServerToClientEvents, GameOver as GameOverData } from "./lib/socket";
|
import type {
|
||||||
import SelectWord from "./client/SelectWord.svelte";
|
ChatMessage,
|
||||||
import Card from "./components/Card.svelte";
|
ClientToServerEvents,
|
||||||
import ChatWindow from "./client/ChatWindow.svelte";
|
GameState,
|
||||||
import GuessPage from "./client/GuessPage.svelte";
|
Player,
|
||||||
import GameOver from "./client/GameOver.svelte";
|
ServerToClientEvents,
|
||||||
|
GameOver as GameOverData,
|
||||||
|
} from './lib/socket';
|
||||||
|
import SelectWord from './client/SelectWord.svelte';
|
||||||
|
import Card from './components/Card.svelte';
|
||||||
|
import ChatWindow from './client/ChatWindow.svelte';
|
||||||
|
import GuessPage from './client/GuessPage.svelte';
|
||||||
|
import GameOver from './client/GameOver.svelte';
|
||||||
|
|
||||||
let socket: Socket<ServerToClientEvents, ClientToServerEvents> | null = null;
|
let socket: Socket<ServerToClientEvents, ClientToServerEvents> | null =
|
||||||
let connected = false;
|
null;
|
||||||
|
let connected = false;
|
||||||
|
|
||||||
let username = '';
|
let username = '';
|
||||||
let gameId = '';
|
let gameId = '';
|
||||||
|
|
||||||
let error = '';
|
let error = '';
|
||||||
|
|
||||||
let host = false;
|
let host = false;
|
||||||
|
|
||||||
let players: Player[] = [];
|
let players: Player[] = [];
|
||||||
|
|
||||||
let gameState: GameState = 'waiting';
|
let gameState: GameState = 'waiting';
|
||||||
let wordOptions: string[] = null;
|
let wordOptions: string[] = null;
|
||||||
|
|
||||||
let chat: ChatMessage[] = [];
|
let chat: ChatMessage[] = [];
|
||||||
let gameOver: GameOverData = null;
|
let gameOver: GameOverData = null;
|
||||||
|
|
||||||
function joinGame(uname: string, gameId: string) {
|
function joinGame(uname: string, gameId: string) {
|
||||||
players = [];
|
players = [];
|
||||||
socket = io(import.meta.env.GAMESERVER_URL || `http://${window.location.hostname}:3000/`);
|
socket = io(
|
||||||
socket.on('connect', () => {
|
import.meta.env.GAMESERVER_URL ||
|
||||||
socket.emit('joinGame', gameId, uname, (result) => {
|
`http://${window.location.hostname}:3000/`
|
||||||
if (result.error === false) {
|
);
|
||||||
const { id, isHost, players: p } = result.data;
|
socket.on('connect', () => {
|
||||||
players = p;
|
socket.emit('joinGame', gameId, uname, (result) => {
|
||||||
host = isHost;
|
if (result.error === false) {
|
||||||
connected = true;
|
const { id, isHost, players: p } = result.data;
|
||||||
username = uname;
|
players = p;
|
||||||
gameState = 'waiting';
|
host = isHost;
|
||||||
chat = [];
|
connected = true;
|
||||||
gameOver = null;
|
username = uname;
|
||||||
} else {
|
gameState = 'waiting';
|
||||||
console.log('failed to join', result.message);
|
chat = [];
|
||||||
socket.disconnect();
|
gameOver = null;
|
||||||
socket = null;
|
} else {
|
||||||
connected = false;
|
console.log('failed to join', result.message);
|
||||||
error = result.message;
|
socket.disconnect();
|
||||||
}
|
socket = null;
|
||||||
|
connected = false;
|
||||||
|
error = result.message;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', (reason) => {
|
socket.on('disconnect', (reason) => {
|
||||||
connected = false;
|
connected = false;
|
||||||
error = 'lost connection: ' + reason;
|
error = 'lost connection: ' + reason;
|
||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
socket = null;
|
socket = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('playerJoined', (username) => {
|
socket.on('playerJoined', (username) => {
|
||||||
players.push({
|
players.push({
|
||||||
username,
|
username,
|
||||||
isHost: false,
|
isHost: false,
|
||||||
});
|
});
|
||||||
players = players;
|
players = players;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('playerLeft', (username) => {
|
socket.on('playerLeft', (username) => {
|
||||||
players = players.filter(p => p.username !== username);
|
players = players.filter((p) => p.username !== username);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('becameHost', (newHost) => {
|
socket.on('becameHost', (newHost) => {
|
||||||
if (newHost === username) {
|
if (newHost === username) {
|
||||||
host = true;
|
host = true;
|
||||||
}
|
}
|
||||||
players = players.map(player => {
|
players = players.map((player) => {
|
||||||
return {
|
return {
|
||||||
...player,
|
...player,
|
||||||
isHost: player.username === newHost,
|
isHost: player.username === newHost,
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('waitForWordPick', () => {
|
socket.on('waitForWordPick', () => {
|
||||||
gameState = 'waitingForWord';
|
gameState = 'waitingForWord';
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('pickWord', (words) => {
|
socket.on('pickWord', (words) => {
|
||||||
console.log('pickWord', gameState, words);
|
console.log('pickWord', gameState, words);
|
||||||
gameState = 'selectingWord';
|
gameState = 'selectingWord';
|
||||||
wordOptions = words;
|
wordOptions = words;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('startDrawing', () => {
|
socket.on('startDrawing', () => {
|
||||||
gameState = 'drawing';
|
gameState = 'drawing';
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('startGuessing', () => {
|
socket.on('startGuessing', () => {
|
||||||
gameState = 'guessing';
|
gameState = 'guessing';
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('chatMessage', (author, message) => {
|
socket.on('chatMessage', (author, message) => {
|
||||||
chat.reverse();
|
chat.reverse();
|
||||||
chat.push({ author, message });
|
chat.push({ author, message });
|
||||||
chat.reverse();
|
chat.reverse();
|
||||||
chat = chat;
|
chat = chat;
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('gameOver', (result) => {
|
socket.on('gameOver', (result) => {
|
||||||
gameState = 'gameOver';
|
gameState = 'gameOver';
|
||||||
gameOver = result;
|
gameOver = result;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
if (socket) {
|
|
||||||
socket.disconnect();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
function startGame() {
|
onDestroy(() => {
|
||||||
socket.emit('startGame');
|
if (socket) {
|
||||||
chat = [];
|
socket.disconnect();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function startGame() {
|
||||||
|
socket.emit('startGame');
|
||||||
|
chat = [];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
{#if !connected}
|
{#if !connected}
|
||||||
<JoinPage joinGame={joinGame} error={error} loading={socket !== null} on:dismiss-error={() => error = ''}></JoinPage>
|
<JoinPage
|
||||||
{:else}
|
{joinGame}
|
||||||
{#if gameState === 'waiting'}
|
{error}
|
||||||
<WaitingLobby username={username} gameId={gameId} host={host} socket={socket} players={players}></WaitingLobby>
|
loading={socket !== null}
|
||||||
|
on:dismiss-error={() => (error = '')}
|
||||||
|
/>
|
||||||
|
{:else if gameState === 'waiting'}
|
||||||
|
<WaitingLobby {username} {gameId} {host} {socket} {players} />
|
||||||
{:else if gameState === 'waitingForWord'}
|
{:else if gameState === 'waitingForWord'}
|
||||||
<Card>
|
<Card>
|
||||||
<p>
|
<p>Waiting for a word to be picked...</p>
|
||||||
Waiting for a word to be picked...
|
</Card>
|
||||||
</p>
|
|
||||||
</Card>
|
|
||||||
{:else if gameState === 'selectingWord'}
|
{:else if gameState === 'selectingWord'}
|
||||||
<SelectWord words={wordOptions} on:pick-word={(event) => socket.emit('pickWord', event.detail)} />
|
<SelectWord
|
||||||
|
words={wordOptions}
|
||||||
|
on:pick-word={(event) => socket.emit('pickWord', event.detail)}
|
||||||
|
/>
|
||||||
{:else if gameState === 'guessing'}
|
{:else if gameState === 'guessing'}
|
||||||
<GuessPage on:guess={(e) => socket.emit('guessWord', e.detail, (correct) => {
|
<GuessPage
|
||||||
if (correct) {
|
on:guess={(e) =>
|
||||||
gameState = 'guessedCorrectly';
|
socket.emit('guessWord', e.detail, (correct) => {
|
||||||
}
|
if (correct) {
|
||||||
})} />
|
gameState = 'guessedCorrectly';
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
<ChatWindow messages={chat} />
|
<ChatWindow messages={chat} />
|
||||||
{:else if gameState === 'guessedCorrectly'}
|
{:else if gameState === 'guessedCorrectly'}
|
||||||
<Card>
|
<Card>
|
||||||
<h1>Correct!</h1>
|
<h1>Correct!</h1>
|
||||||
<p>
|
<p>Waiting for the other players to finish...</p>
|
||||||
Waiting for the other players to finish...
|
|
||||||
</p>
|
|
||||||
</Card>
|
</Card>
|
||||||
<ChatWindow messages={chat} />
|
<ChatWindow messages={chat} />
|
||||||
{:else if gameState === 'drawing'}
|
{:else if gameState === 'drawing'}
|
||||||
<Card>
|
<Card>
|
||||||
<h1>
|
<h1>Start drawing!</h1>
|
||||||
Start drawing!
|
</Card>
|
||||||
</h1>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<ChatWindow messages={chat} />
|
<ChatWindow messages={chat} />
|
||||||
{:else if gameState === 'gameOver'}
|
{:else if gameState === 'gameOver'}
|
||||||
<GameOver gameOver={gameOver} isHost={host} {username} on:play-again={startGame} />
|
<GameOver
|
||||||
|
{gameOver}
|
||||||
|
isHost={host}
|
||||||
|
{username}
|
||||||
|
on:play-again={startGame}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
body {
|
body {
|
||||||
font-family: 'Noto Sans', sans-serif;
|
font-family: 'Noto Sans', sans-serif;
|
||||||
/* background-color: #212;
|
/* background-color: #212;
|
||||||
color: #ded;
|
color: #ded;
|
||||||
|
|
||||||
box-sizing: border-box; */
|
box-sizing: border-box; */
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* input, button {
|
/* input, button {
|
||||||
|
|
|
@ -19,7 +19,13 @@ export interface GameOver {
|
||||||
type Result<T> = { error: false; data: T } | { error: true; message: string };
|
type Result<T> = { error: false; data: T } | { error: true; message: string };
|
||||||
|
|
||||||
export interface ClientToServerEvents {
|
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;
|
startGame: () => void;
|
||||||
|
|
||||||
pickWord: (word: string) => void;
|
pickWord: (word: string) => void;
|
||||||
|
@ -35,7 +41,14 @@ export interface Player {
|
||||||
isHost: boolean;
|
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 {
|
export interface ChatMessage {
|
||||||
author: string;
|
author: string;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import '@fontsource/noto-sans';
|
||||||
import 'bulma/css/bulma.css';
|
import 'bulma/css/bulma.css';
|
||||||
|
|
||||||
const app = new App({
|
const app = new App({
|
||||||
target: document.getElementById('app'),
|
target: document.getElementById('app'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import type { Server, Socket } from "socket.io";
|
import type { Server, Socket } from 'socket.io';
|
||||||
import type { ClientToServerEvents, Player, ServerToClientEvents, SocketData } from "src/lib/socket";
|
import type {
|
||||||
import { levenshteinDistance } from "../lib/util.js";
|
ClientToServerEvents,
|
||||||
import randomWords from "./words.js";
|
Player,
|
||||||
|
ServerToClientEvents,
|
||||||
|
SocketData,
|
||||||
|
} from 'src/lib/socket';
|
||||||
|
import { levenshteinDistance } from '../lib/util.js';
|
||||||
|
import randomWords from './words.js';
|
||||||
|
|
||||||
export class Game {
|
export class Game {
|
||||||
io: Server<ClientToServerEvents, ServerToClientEvents, {}, SocketData>;
|
io: Server<ClientToServerEvents, ServerToClientEvents, {}, SocketData>;
|
||||||
|
@ -14,13 +19,24 @@ export class Game {
|
||||||
currentWord: string | null;
|
currentWord: string | null;
|
||||||
successfulPlayers: [string, number][];
|
successfulPlayers: [string, number][];
|
||||||
|
|
||||||
constructor(gameId: string, io: Server<ClientToServerEvents, ServerToClientEvents, {}, SocketData>) {
|
constructor(
|
||||||
|
gameId: string,
|
||||||
|
io: Server<ClientToServerEvents, ServerToClientEvents, {}, SocketData>
|
||||||
|
) {
|
||||||
this.io = io;
|
this.io = io;
|
||||||
this.gameId = gameId;
|
this.gameId = gameId;
|
||||||
this.players = new Map();
|
this.players = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
playerJoin(socket: Socket<ClientToServerEvents, ServerToClientEvents, {}, SocketData>, username: string): string | Player {
|
playerJoin(
|
||||||
|
socket: Socket<
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
{},
|
||||||
|
SocketData
|
||||||
|
>,
|
||||||
|
username: string
|
||||||
|
): string | Player {
|
||||||
for (const player of this.players.values()) {
|
for (const player of this.players.values()) {
|
||||||
if (player.username === username) {
|
if (player.username === username) {
|
||||||
return 'username taken';
|
return 'username taken';
|
||||||
|
@ -44,18 +60,26 @@ export class Game {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a client socket disconnects.
|
* Called when a client socket disconnects.
|
||||||
*
|
*
|
||||||
* @param socket Socket that has just disconnected
|
* @param socket Socket that has just disconnected
|
||||||
* @returns true if the game is empty, false otherwise
|
* @returns true if the game is empty, false otherwise
|
||||||
*/
|
*/
|
||||||
playerLeft(socket: Socket<ClientToServerEvents, ServerToClientEvents, {}, SocketData>): boolean {
|
playerLeft(
|
||||||
|
socket: Socket<
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
{},
|
||||||
|
SocketData
|
||||||
|
>
|
||||||
|
): boolean {
|
||||||
const player = this.players.get(socket.id);
|
const player = this.players.get(socket.id);
|
||||||
this.players.delete(socket.id);
|
this.players.delete(socket.id);
|
||||||
if (this.players.size === 0) return true;
|
if (this.players.size === 0) return true;
|
||||||
if (player.isHost) {
|
if (player.isHost) {
|
||||||
// we need to assign a new host for this room
|
// we need to assign a new host for this room
|
||||||
const playerIds = Array.from(this.players.keys());
|
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);
|
const newHost = this.players.get(newHostId);
|
||||||
newHost.isHost = true;
|
newHost.isHost = true;
|
||||||
this.io.to(this.gameId).emit('becameHost', newHost.username);
|
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
|
* Will pick a random player to choose a word, and then
|
||||||
* broadcast the start to the other players.
|
* broadcast the start to the other players.
|
||||||
*/
|
*/
|
||||||
gameStart(socket: Socket<ClientToServerEvents, ServerToClientEvents, {}, SocketData>) {
|
gameStart(
|
||||||
|
socket: Socket<
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
{},
|
||||||
|
SocketData
|
||||||
|
>
|
||||||
|
) {
|
||||||
if (!this.players.get(socket.id).isHost) return;
|
if (!this.players.get(socket.id).isHost) return;
|
||||||
|
|
||||||
const playerIds = Array.from(this.players.keys());
|
const playerIds = Array.from(this.players.keys());
|
||||||
|
@ -82,7 +113,15 @@ export class Game {
|
||||||
this.io.to(player).emit('pickWord', words);
|
this.io.to(player).emit('pickWord', words);
|
||||||
}
|
}
|
||||||
|
|
||||||
pickWord(socket: Socket<ClientToServerEvents, ServerToClientEvents, {}, SocketData>, word: string) {
|
pickWord(
|
||||||
|
socket: Socket<
|
||||||
|
ClientToServerEvents,
|
||||||
|
ServerToClientEvents,
|
||||||
|
{},
|
||||||
|
SocketData
|
||||||
|
>,
|
||||||
|
word: string
|
||||||
|
) {
|
||||||
if (socket.id !== this.currentPlayer) return;
|
if (socket.id !== this.currentPlayer) return;
|
||||||
this.currentWord = word;
|
this.currentWord = word;
|
||||||
this.io.to(this.gameId).except(socket.id).emit('startGuessing');
|
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.
|
* Called when a player makes a guess during a game.
|
||||||
*
|
*
|
||||||
* @param socket The socket of the player that made the guess
|
* @param socket The socket of the player that made the guess
|
||||||
* @param guess The word that the player guessed
|
* @param guess The word that the player guessed
|
||||||
* @returns true if the word was correct, false otherwise
|
* @returns true if the word was correct, false otherwise
|
||||||
*/
|
*/
|
||||||
guessWord(socket: Socket<ClientToServerEvents, ServerToClientEvents, {}, SocketData>, guess: string): boolean {
|
guessWord(
|
||||||
if (!this.currentWord || this.successfulPlayers.find(([id, _]) => id === socket.id) !== undefined) return;
|
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);
|
const player = this.players.get(socket.id);
|
||||||
|
|
||||||
if (guess.toLocaleLowerCase() === this.currentWord.toLocaleLowerCase()) {
|
if (
|
||||||
|
guess.toLocaleLowerCase() === this.currentWord.toLocaleLowerCase()
|
||||||
|
) {
|
||||||
// Correct answer!
|
// Correct answer!
|
||||||
this.io.to(this.gameId).except(socket.id).emit('chatMessage', 'game', `${player.username} guessed the correct word`);
|
this.io
|
||||||
this.successfulPlayers.push([socket.id, new Date().getTime() - this.startTime]);
|
.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;
|
return true;
|
||||||
} else if (levenshteinDistance(guess.toLocaleLowerCase(), this.currentWord.toLocaleLowerCase()) <= 2) {
|
} else if (
|
||||||
|
levenshteinDistance(
|
||||||
|
guess.toLocaleLowerCase(),
|
||||||
|
this.currentWord.toLocaleLowerCase()
|
||||||
|
) <= 2
|
||||||
|
) {
|
||||||
// close guess
|
// 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
|
// Broadcast incorrect guesses to all players
|
||||||
|
@ -138,7 +209,10 @@ export class Game {
|
||||||
|
|
||||||
private isGameOver(): boolean {
|
private isGameOver(): boolean {
|
||||||
for (const id of this.players.keys()) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,9 @@ io.on('connection', (socket) => {
|
||||||
io.to(gameId).emit('playerJoined', username);
|
io.to(gameId).emit('playerJoined', username);
|
||||||
|
|
||||||
socket.data.gameId = gameId;
|
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);
|
socket.join(gameId);
|
||||||
|
|
||||||
const players = Array.from(room.players.values());
|
const players = Array.from(room.players.values());
|
||||||
|
@ -82,14 +84,14 @@ io.on('connection', (socket) => {
|
||||||
socket.on('pickWord', (word) => {
|
socket.on('pickWord', (word) => {
|
||||||
if (!socket.data.gameId) return;
|
if (!socket.data.gameId) return;
|
||||||
const game = games.get(socket.data.gameId);
|
const game = games.get(socket.data.gameId);
|
||||||
|
|
||||||
game.pickWord(socket, word);
|
game.pickWord(socket, word);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('guessWord', (word, callback) => {
|
socket.on('guessWord', (word, callback) => {
|
||||||
if (!socket.data.gameId) return;
|
if (!socket.data.gameId) return;
|
||||||
const game = games.get(socket.data.gameId);
|
const game = games.get(socket.data.gameId);
|
||||||
|
|
||||||
const result = game.guessWord(socket, word);
|
const result = game.guessWord(socket, word);
|
||||||
callback(result);
|
callback(result);
|
||||||
game.checkGameOver();
|
game.checkGameOver();
|
||||||
|
|
|
@ -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[] {
|
export default function randomWords(count = 3): string[] {
|
||||||
const randomWords: string[] = [];
|
const randomWords: string[] = [];
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import sveltePreprocess from 'svelte-preprocess'
|
import sveltePreprocess from 'svelte-preprocess';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: sveltePreprocess()
|
preprocess: sveltePreprocess(),
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
{
|
{
|
||||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
/**
|
/**
|
||||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||||
* Note that setting allowJs false does not prevent the use
|
* Note that setting allowJs false does not prevent the use
|
||||||
* of JS in `.svelte` files.
|
* of JS in `.svelte` files.
|
||||||
*/
|
*/
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"isolatedModules": true
|
"isolatedModules": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
"include": [
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.js",
|
||||||
|
"src/**/*.svelte"
|
||||||
|
],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node"
|
"moduleResolution": "Node"
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite';
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte(),
|
svelte(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
includeAssets: ['favicon.png', 'apple-touch-icon.png'],
|
includeAssets: ['favicon.png', 'apple-touch-icon.png'],
|
||||||
manifest: {
|
manifest: {
|
||||||
name: 'Doodly',
|
name: 'Doodly',
|
||||||
short_name: 'Doodly',
|
short_name: 'Doodly',
|
||||||
description: 'Drawing game with friends',
|
description: 'Drawing game with friends',
|
||||||
theme_color: '#ffffff',
|
theme_color: '#ffffff',
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: 'favicon.png',
|
src: 'favicon.png',
|
||||||
sizes: '256x256',
|
sizes: '256x256',
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
},{
|
},
|
||||||
src: 'favicon-192.png',
|
{
|
||||||
sizes: '192x192',
|
src: 'favicon-192.png',
|
||||||
type: 'image/png',
|
sizes: '192x192',
|
||||||
},
|
type: 'image/png',
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
}),
|
},
|
||||||
],
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue