feat: lots of design improvements and bugfixes
This commit is contained in:
parent
15d3d71715
commit
a2a6ea7bc3
17 changed files with 160 additions and 45 deletions
|
@ -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"]
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<main id="root"></main>
|
||||
|
||||
<script src="/src-web/index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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> = (props) => {
|
||||
const [board, setBoard] = createSignal<BoardData>(Array(64).fill(null));
|
||||
const [possibleMoves, setPossibleMoves] = createSignal<Move[]>([]);
|
||||
const [side, setSide] = createSignal<Side | null>(null);
|
||||
|
||||
function handleEvent(e: MessageEvent<string>) {
|
||||
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> = (props) => {
|
|||
}));
|
||||
}
|
||||
|
||||
return <Switch fallback={<><h1>Hello, World!</h1><Spinner /></>}>
|
||||
return <Switch fallback={<Spinner />}>
|
||||
<Match when={state() === WebSocket.CLOSED}>
|
||||
<Welcome gameId={props.gameId} joinGame={() => connect()} />
|
||||
<Welcome gameId={props.gameId} joinGame={() => joinGame()} />
|
||||
</Match>
|
||||
<Match when={state() === WebSocket.CONNECTING}>
|
||||
<Spinner />
|
||||
</Match>
|
||||
<Match when={state() === WebSocket.OPEN}>
|
||||
<Show when={side() !== null} fallback={<>
|
||||
<h1>Waiting for opponent...</h1>
|
||||
<Spinner />
|
||||
</>}>
|
||||
<Board board={board} moves={possibleMoves} makeMove={makeMove} />
|
||||
</Show>
|
||||
</Match>
|
||||
</Switch>
|
||||
};
|
||||
|
|
|
@ -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%;
|
||||
}
|
||||
|
|
|
@ -14,3 +14,11 @@ const Button: Component<Props> = (props) => {
|
|||
}
|
||||
|
||||
export default Button;
|
||||
|
||||
export const ButtonContainer: Component<{ children: JSX.Element }> = (props) => {
|
||||
const c = children(() => props.children);
|
||||
|
||||
return <div class="button-container">
|
||||
{c()}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ interface Props {
|
|||
const Spinner: Component<Props> = (props) => {
|
||||
return <div class="spinner" style={{
|
||||
'--spinner-size': props.size ?? '32px',
|
||||
'--spinner-colour': props.colour ?? '#f9027a',
|
||||
'--spinner-colour': props.colour ?? 'var(--accent)',
|
||||
}} />
|
||||
}
|
||||
|
||||
|
|
|
@ -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> = (props) => {
|
||||
console.log(props.gameId);
|
||||
return <main>
|
||||
function shareInvite() {
|
||||
navigator.share({
|
||||
url: window.location.href,
|
||||
});
|
||||
}
|
||||
|
||||
return <>
|
||||
<h1>Welcome</h1>
|
||||
<div>
|
||||
Game ID: <pre>{props.gameId}</pre>
|
||||
</div>
|
||||
<section>
|
||||
<h2>Game ID:</h2>
|
||||
<pre>{props.gameId}</pre>
|
||||
</section>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button onClick={() => shareInvite()}>
|
||||
Share invite
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => props.joinGame()}>
|
||||
Join
|
||||
</Button>
|
||||
</main>;
|
||||
</ButtonContainer>
|
||||
</>;
|
||||
}
|
||||
|
||||
export default Welcome;
|
||||
|
|
|
@ -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://";
|
||||
|
|
|
@ -51,9 +51,16 @@ export const PossibleMovesEvent = z.object({
|
|||
|
||||
export type PossibleMovesEvent = z.infer<typeof PossibleMovesEvent>;
|
||||
|
||||
export const StartGameEvent = z.object({
|
||||
side: Side,
|
||||
});
|
||||
|
||||
export type StartGameEvent = z.infer<typeof StartGameEvent>;
|
||||
|
||||
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<typeof ServerChessEvent>;
|
||||
|
|
|
@ -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(() => <App gameId={search.get('game_id')!} />, 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(() => <App gameId={search.get('game_id')!} />, root);
|
||||
} else {
|
||||
render(() => <Button onClick={newGame}>New game</Button>, root);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
29
src/game.rs
29
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<JoinGame> 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()),
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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<Option<Piece>> },
|
||||
PossibleMoves { moves: Vec<Move> },
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#[cfg(not(debug_assertions))]
|
||||
use axum::http::uri::Uri;
|
||||
use axum::{
|
||||
extract::{
|
||||
ws::{Message, WebSocket},
|
||||
|
|
Loading…
Reference in a new issue