This commit is contained in:
2025-08-09 15:48:14 -04:00
parent 2ff5d781fd
commit 5e8978c550
9 changed files with 90 additions and 40 deletions

View File

@@ -1,5 +1,9 @@
import { type Api } from "../../server/src/api";
import { treaty } from "@elysiajs/eden";
const { api } = treaty<Api>("http://localhost:5001");
const { api } = treaty<Api>("http://localhost:5001", {
headers: {
human: "daniel",
},
});
export default api;

View File

@@ -1,5 +1,9 @@
import { Accessor, createContext, createResource, Show } from "solid-js";
import { GameState, Action } from "../../../server/src/games/simple";
import {
GameState,
Action,
vGameState,
} from "../../../server/src/games/simple";
import api from "../api";
import Hand from "./Hand";
import Pile from "./Pile";
@@ -20,7 +24,7 @@ export default (props: { instanceId: string }) => {
api
.simple(props)
.post({ action })
.then((res) => mutate(res.data as GameState));
.then((res) => res.status == 200 && mutate(res.data as vGameState));
return (
<GameContext.Provider value={{ gameState, submitAction }}>
@@ -38,7 +42,7 @@ export default (props: { instanceId: string }) => {
}
/>
</div>
<Hand hand={gameState.latest!.players[0]} />
<Hand hand={gameState.latest!.players["daniel"]} />
</div>
</Show>
</GameContext.Provider>

View File

@@ -1,6 +1,5 @@
import { A, useParams } from "@solidjs/router";
import { createEffect, createResource, Show, Suspense } from "solid-js";
import Game from "../../components/Game";
export default () => {
@@ -8,7 +7,7 @@ export default () => {
return (
<>
<Game instanceId={Number.parseInt(params.instance)} />
<Game instanceId={params.instance} />
<A
href={`/${params.game}`}
style={{

View File

@@ -15,7 +15,17 @@ export default () => {
<Suspense>
<div style={{ padding: "20px" }}>
<h1 style={{ margin: 0 }}>{param.game}</h1>
<button onClick={() => null}>New Game</button>
<button
onClick={() =>
api.simple.newGame
.post({
players: ["daniel"],
})
.then(refetch)
}
>
New Game
</button>
<ul>
<For each={instances() ?? []}>
{(instance) => (

View File

@@ -16,8 +16,16 @@ model Game {
instances Instance[]
}
model Human {
key String @id
name String
Instance Instance[]
}
model Instance {
id Int @id @default(autoincrement())
id String @id @default(cuid(2))
createdByKey String
createdBy Human @relation(fields: [createdByKey], references: [key])
gameKey String
game Game @relation(fields: [gameKey], references: [key])
gameState Json

View File

@@ -17,6 +17,7 @@
"@prisma/client": "6.13.0",
"elysia": "^1.3.8",
"hono": "^4.8.12",
"object-hash": "^3.0.0",
"zod": "^4.0.15"
},
"devDependencies": {

View File

@@ -6,7 +6,6 @@ import {
shuffle,
vCard,
} from "../../../shared/cards";
import hash from "object-hash";
import { heq } from "../../../shared/utils";
import { Elysia, t } from "elysia";
import { prisma } from "../db/db";
@@ -14,7 +13,6 @@ import { prisma } from "../db/db";
// omniscient game state
export type GameState = {
prev?: {
hash: string;
action: Action;
};
@@ -41,14 +39,15 @@ export type PlayerView = {
export type Action = { type: "draw" } | { type: "discard"; card: Card };
export const newGame = (players: string[]) =>
({
export const newGame = (players: string[]) => {
console.log("new game called with", JSON.stringify(players));
return {
deck: shuffle(newDeck()),
players: Object.fromEntries(players.map((humanId) => [humanId, []])),
} as GameState);
} as GameState;
};
export const getKnowledge = (state: GameState, humanId: string) => ({
hash: hash(state),
humanId,
deck: state.deck.map((_) => null),
players: Object.fromEntries(
@@ -59,23 +58,27 @@ export const getKnowledge = (state: GameState, humanId: string) => ({
),
});
export const resolveAction = (state: GameState, action: Action): GameState => {
if (action.prevHash != hash(state)) {
throw new Error(
`action thinks it's applying to ${
action.prevHash
}, but we're checking it against ${hash(state)}`
);
}
export const resolveAction = (
state: GameState,
humanId: string,
action: Action
): GameState => {
// if (action.prevHash != hash(state)) {
// throw new Error(
// `action thinks it's applying to ${
// action.prevHash
// }, but we're checking it against ${hash(state)}`
// );
// }
const playerHand = state.players[action.humanId];
const playerHand = state.players[humanId];
if (action.type == "draw") {
const [drawn, ...rest] = state.deck;
return {
deck: rest,
players: {
...state.players,
[action.humanId]: [drawn, ...playerHand],
[humanId]: [drawn, ...playerHand],
},
};
}
@@ -86,7 +89,7 @@ export const resolveAction = (state: GameState, action: Action): GameState => {
deck: [action.card, ...state.deck],
players: {
...state.players,
[action.humanId]: playerHand
[humanId]: playerHand
.slice(0, index)
.concat(playerHand.slice(index + 1)),
},
@@ -94,10 +97,18 @@ export const resolveAction = (state: GameState, action: Action): GameState => {
};
export const simpleApi = new Elysia({ prefix: "/simple" })
// .guard({ headers: t.Object({ Human: t.String() }) })
.post(
"/newGame",
({ body: { players } }: { body: { players: string[] } }) =>
newGame(players)
(args: { body: { players: string[] }; headers: { human: string } }) => {
return prisma.instance.create({
data: {
gameState: newGame(args.body.players),
gameKey: "simple",
createdByKey: args.headers.human,
},
});
}
)
.group("/:instanceId", (app) =>
app
@@ -105,33 +116,35 @@ export const simpleApi = new Elysia({ prefix: "/simple" })
prisma.instance
.findUnique({
where: {
id: Number(instanceId),
id: instanceId,
},
})
.then((game) => game?.gameState)
)
.post(
"/",
({ params: { instanceId }, body: { action } }) =>
({
params: { instanceId },
body: { action },
headers: { Human: humanId },
}) =>
prisma.instance
.findUniqueOrThrow({
where: {
id: Number(instanceId),
id: instanceId,
},
})
.then((game) => {
const newState = resolveAction(
game.gameState as GameState,
humanId!,
action
);
const knownState = getKnowledge(
newState,
action.humanId
);
const knownState = getKnowledge(newState, humanId!);
void prisma.instance.update({
data: { gameState: newState },
where: {
id: Number(instanceId),
id: instanceId,
},
});
return knownState;

View File

@@ -10,7 +10,9 @@ const app = new Elysia()
.onRequest(({ request }) => {
console.log(request.method, request.url);
})
.onError(({ code, error }) => {
.onError(({ code, error, body, headers }) => {
console.error("headers", JSON.stringify(headers, null, 2));
console.error("body", JSON.stringify(body, null, 2));
console.error(code, error);
})
.get("/ping", () => "pong")

9
pnpm-lock.yaml generated
View File

@@ -57,6 +57,9 @@ importers:
hono:
specifier: ^4.8.12
version: 4.8.12
object-hash:
specifier: ^3.0.0
version: 3.0.0
zod:
specifier: ^4.0.15
version: 4.0.15
@@ -810,6 +813,10 @@ packages:
engines: {node: ^14.16.0 || >=16.10.0}
hasBin: true
object-hash@3.0.0:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
ohash@2.0.11:
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
@@ -1736,6 +1743,8 @@ snapshots:
pkg-types: 2.2.0
tinyexec: 1.0.1
object-hash@3.0.0: {}
ohash@2.0.11: {}
openapi-types@12.1.3: