diff --git a/Makefile.toml b/Makefile.toml index c7089b8..b253ed9 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -6,3 +6,8 @@ args = ["build"] command = "cargo" args = ["build", "--release"] dependencies = ["build-ui"] + +[tasks.run-release] +command = "cargo" +args = ["run", "--release"] +dependencies = ["build-ui"] diff --git a/index.html b/index.html index b7820db..e9c08c5 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@ -
+
diff --git a/package.json b/package.json index b511b78..bd5a994 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,18 @@ "license": "ISC", "devDependencies": { "@types/node": "^18.11.7", + "@types/uuid": "^8.3.4", "typescript": "^4.8.4", "vite": "^3.2.1", "vite-plugin-solid": "^2.3.10" }, "dependencies": { "@fontsource/noto-sans-symbols-2": "^4.5.10", + "@fontsource/ubuntu": "^4.5.11", "@solid-primitives/websocket": "^0.3.3", "solid-devtools": "^0.20.1", "solid-js": "^1.6.0", + "uuid": "^9.0.0", "zod": "^3.19.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5d191b..fa7ff1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,24 +2,30 @@ lockfileVersion: 5.4 specifiers: '@fontsource/noto-sans-symbols-2': ^4.5.10 + '@fontsource/ubuntu': ^4.5.11 '@solid-primitives/websocket': ^0.3.3 '@types/node': ^18.11.7 + '@types/uuid': ^8.3.4 solid-devtools: ^0.20.1 solid-js: ^1.6.0 typescript: ^4.8.4 + uuid: ^9.0.0 vite: ^3.2.1 vite-plugin-solid: ^2.3.10 zod: ^3.19.1 dependencies: '@fontsource/noto-sans-symbols-2': 4.5.10 + '@fontsource/ubuntu': 4.5.11 '@solid-primitives/websocket': 0.3.3_solid-js@1.6.0 solid-devtools: 0.20.1_solid-js@1.6.0+vite@3.2.1 solid-js: 1.6.0 + uuid: 9.0.0 zod: 3.19.1 devDependencies: '@types/node': 18.11.7 + '@types/uuid': 8.3.4 typescript: 4.8.4 vite: 3.2.1 vite-plugin-solid: 2.3.10_solid-js@1.6.0+vite@3.2.1 @@ -335,6 +341,10 @@ packages: resolution: {integrity: sha512-3sDgCDm3zNijKoQ4UVSg5PgGk2Drp9QIF73RKZzfiZ4Ksgsu6h4XXng2i+f6cXHpSaP2CEOLgTu7EwH298djMg==} dev: false + /@fontsource/ubuntu/4.5.11: + resolution: {integrity: sha512-c+B8A8r31hJLk8Ek7Svoz4GwjtInhc60LFM6XTr5TKtZbcct8TPNxJuKX4+hnqoWgcvy8s0Gj4mde4TcK/SgDQ==} + dev: false + /@jridgewell/gen-mapping/0.1.1: resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -566,6 +576,10 @@ packages: resolution: {integrity: sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ==} dev: true + /@types/uuid/8.3.4: + resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + dev: true + /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1025,6 +1039,11 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 + /uuid/9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + hasBin: true + dev: false + /vite-plugin-solid/2.3.10_solid-js@1.6.0+vite@3.2.1: resolution: {integrity: sha512-5jMF+QFk2TQaPLlDl7LvZZ99D4kO1X+rD9LR78p9sx9O+XisVSQaHFPLrCsyW/lXuBwub+ox/pNaZdCUZJwd3Q==} peerDependencies: diff --git a/src-web/App.tsx b/src-web/App.tsx index 102129e..7eeca2f 100644 --- a/src-web/App.tsx +++ b/src-web/App.tsx @@ -1,9 +1,9 @@ -import { Component, createEffect, createSignal, Match, Switch } from 'solid-js'; +import { Component, createSignal, Match, Show, Switch } from 'solid-js'; import createWebsocket from '@solid-primitives/websocket'; import { wsUrl } from './constants'; import Spinner from './components/Spinner'; import Welcome from './components/Welcome'; -import { Board as BoardData, Move, ServerChessEvent } from './events'; +import { Board as BoardData, Move, ServerChessEvent, Side } from './events'; import Board from './components/Board'; interface Props { @@ -13,29 +13,29 @@ interface Props { const App: Component = (props) => { const [board, setBoard] = createSignal(Array(64).fill(null)); const [possibleMoves, setPossibleMoves] = createSignal([]); + const [side, setSide] = createSignal(null); function handleEvent(e: MessageEvent) { const data = JSON.parse(e.data); const event = ServerChessEvent.parse(data); if (event.event === 'BoardUpdate') { - console.log(event.data.board); setBoard(event.data.board); } else if (event.event === 'PossibleMoves') { - console.log(event.data.moves); setPossibleMoves(event.data.moves); + } else if (event.event === 'StartGame') { + setSide(event.data.side); } } - createEffect(() => { - console.log('board is now', board()); - }); - - createEffect(() => { - console.log('moves is now', possibleMoves()); - }); - const [connect, disconnect, send, state, socket] = createWebsocket(wsUrl(props.gameId), handleEvent, console.error); + function joinGame() { + connect(); + setSide(null); + setBoard(Array(64).fill(null)); + setPossibleMoves([]); + } + function makeMove(move: Move) { send(JSON.stringify({ event: 'MakeMove', @@ -45,15 +45,20 @@ const App: Component = (props) => { })); } - return

Hello, World!

}> + return }> - connect()} /> + joinGame()} /> - + +

Waiting for opponent...

+ + }> + +
}; diff --git a/src-web/components/Button.css b/src-web/components/Button.css index 478dfdd..5818d3d 100644 --- a/src-web/components/Button.css +++ b/src-web/components/Button.css @@ -1,10 +1,12 @@ .button { width: 100%; + font-family: Ubuntu, sans-serif; font-size: 1.35rem; padding: 8px; border-radius: 8px; - border: 1px solid black; - background-color: #ddd; + border: 2px solid var(--accent); + background-color: var(--accent-dim); + color: white; } .button:hover { @@ -14,3 +16,10 @@ .button:active { filter: brightness(.8); } + +.button-container { + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; +} diff --git a/src-web/components/Button.tsx b/src-web/components/Button.tsx index b84a833..71081d0 100644 --- a/src-web/components/Button.tsx +++ b/src-web/components/Button.tsx @@ -14,3 +14,11 @@ const Button: Component = (props) => { } export default Button; + +export const ButtonContainer: Component<{ children: JSX.Element }> = (props) => { + const c = children(() => props.children); + + return
+ {c()} +
+} diff --git a/src-web/components/Spinner.tsx b/src-web/components/Spinner.tsx index bbdcdda..e34f1e8 100644 --- a/src-web/components/Spinner.tsx +++ b/src-web/components/Spinner.tsx @@ -9,7 +9,7 @@ interface Props { const Spinner: Component = (props) => { return
} diff --git a/src-web/components/Welcome.tsx b/src-web/components/Welcome.tsx index c21a443..f797116 100644 --- a/src-web/components/Welcome.tsx +++ b/src-web/components/Welcome.tsx @@ -1,5 +1,5 @@ import { Component } from "solid-js"; -import Button from "./Button"; +import Button, { ButtonContainer } from "./Button"; interface Props { gameId: string; @@ -7,16 +7,29 @@ interface Props { } const Welcome: Component = (props) => { - console.log(props.gameId); - return
+ function shareInvite() { + navigator.share({ + url: window.location.href, + }); + } + + return <>

Welcome

-
- Game ID:
{props.gameId}
-
- -
; +
+

Game ID:

+
{props.gameId}
+
+ + + + + + + ; } export default Welcome; diff --git a/src-web/constants.ts b/src-web/constants.ts index 2bd22b5..12d6e8d 100644 --- a/src-web/constants.ts +++ b/src-web/constants.ts @@ -1,8 +1,8 @@ // const WS_BASE = 'ws://localhost:3000/ws/'; export function wsUrl(id: string) { - if (import.meta.env.WS_BASE) { - return import.meta.env.WS_BASE + id; + if (import.meta.env.VITE_WS_BASE) { + return import.meta.env.VITE_WS_BASE + id; } else { const loc = window.location; let newUri = loc.protocol === "https:" ? "wss://" : "ws://"; diff --git a/src-web/events.ts b/src-web/events.ts index 4159bff..b939545 100644 --- a/src-web/events.ts +++ b/src-web/events.ts @@ -51,9 +51,16 @@ export const PossibleMovesEvent = z.object({ export type PossibleMovesEvent = z.infer; +export const StartGameEvent = z.object({ + side: Side, +}); + +export type StartGameEvent = z.infer; + export const ServerChessEvent = z.discriminatedUnion("event", [ z.object({ event: z.literal('BoardUpdate'), data: BoardUpdateEvent }), z.object({ event: z.literal('PossibleMoves'), data: PossibleMovesEvent }), + z.object({ event: z.literal('StartGame'), data: StartGameEvent }) ]); export type ServerChessEvent = z.infer; diff --git a/src-web/index.tsx b/src-web/index.tsx index e602e6b..71db148 100644 --- a/src-web/index.tsx +++ b/src-web/index.tsx @@ -4,11 +4,20 @@ import 'solid-devtools'; import App from './App'; import './main.css'; +import '@fontsource/ubuntu'; import "@fontsource/noto-sans-symbols-2"; +import { v4 as uuidv4 } from 'uuid'; +import Button from './components/Button'; const search = new URLSearchParams(window.location.search); -if (search.has('game_id')) { - render(() => , document.getElementById('root') as HTMLElement); -} else { - console.error('no game id'); +const root = document.getElementById('root')!; + +function newGame() { + window.location.search = '?game_id=' + uuidv4(); +} + +if (search.has('game_id')) { + render(() => , root); +} else { + render(() => , root); } diff --git a/src-web/main.css b/src-web/main.css index a620c0d..34a6f31 100644 --- a/src-web/main.css +++ b/src-web/main.css @@ -1,3 +1,14 @@ +:root { + --background: #13092b; + --background-transparent: #13092baa; + --background-2: #090f2b; + --foreground: #ddd; + --foreground-bright: #fff; + --foreground-dim: #aaa; + --accent: #f9027a; + --accent-dim: hsl(331, 50%, 49%); +} + html { box-sizing: border-box; } @@ -5,6 +16,10 @@ html { html, body { padding: 0; margin: 0; + font-family: Ubuntu, sans-serif; + font-size: 16px; + background-color: var(--background); + color: var(--foreground); } * { @@ -17,3 +32,10 @@ body { width: 100%; min-height: 100vh; } + +#root { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} diff --git a/src/game.rs b/src/game.rs index 37ad9f9..c60dae6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -4,10 +4,10 @@ use uuid::Uuid; use xtra::{prelude::*, WeakAddress}; use crate::{ - chess::{mv::generate_legal, Side}, + chess::{mv::generate_legal}, constants::START_FEN, player::{IncomingPlayerEvent, OutgoingPlayerEvent, Player}, - prelude::Board, + prelude::*, }; #[derive(Actor, Default)] @@ -80,6 +80,7 @@ impl Handler for ChessGame { None } else { self.black = Some(join_game.player); + self.send_game_start(); self.broadcast_new_board(); self.send_possible_moves(); Some(Side::Black) @@ -128,18 +129,28 @@ impl ChessGame { } } - fn broadcast_new_board(&self) { + fn send_game_start(&self) { // TODO: Handle players disconnecting - tokio::spawn(self.white.send(OutgoingPlayerEvent::BoardUpdate { - board: self.board.board.to_vec(), - })); + tokio::spawn(self.white.send(OutgoingPlayerEvent::StartGame { side: White })); if let Some(black) = &self.black { - tokio::spawn(black.send(OutgoingPlayerEvent::BoardUpdate { - board: self.board.board.to_vec(), - })); + tokio::spawn(black.send(OutgoingPlayerEvent::StartGame { side: Black })); } } + fn broadcast(&self, message: OutgoingPlayerEvent) { + // TODO: Handle players disconnecting + tokio::spawn(self.white.send(message.clone())); + if let Some(black) = &self.black { + tokio::spawn(black.send(message)); + } + } + + fn broadcast_new_board(&self) { + self.broadcast(OutgoingPlayerEvent::BoardUpdate { + board: self.board.board.to_vec(), + }); + } + fn send_possible_moves(&self) { let (current, other) = match &self.board.to_move { Side::Black => (self.black.clone().unwrap(), self.white.clone()), diff --git a/src/lib.rs b/src/lib.rs index 99122a9..877b5c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod prelude { pub use crate::chess::Board; pub use crate::chess::Piece; + pub use crate::chess::Side; pub use crate::chess::PieceType::*; pub use crate::chess::Side::*; diff --git a/src/player.rs b/src/player.rs index cbe6b42..6a02ce1 100644 --- a/src/player.rs +++ b/src/player.rs @@ -25,9 +25,10 @@ impl Player { } } -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] #[serde(tag = "event", content = "data")] pub enum OutgoingPlayerEvent { + StartGame { side: Side }, BoardUpdate { board: Vec> }, PossibleMoves { moves: Vec }, } diff --git a/src/routes.rs b/src/routes.rs index 04709c1..5071f91 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,3 +1,5 @@ +#[cfg(not(debug_assertions))] +use axum::http::uri::Uri; use axum::{ extract::{ ws::{Message, WebSocket},