closer
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import { type Api } from "../../server/src/api";
|
import { type Api } from "../../server/src/api";
|
||||||
import { treaty } from "@elysiajs/eden";
|
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;
|
export default api;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { Accessor, createContext, createResource, Show } from "solid-js";
|
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 api from "../api";
|
||||||
import Hand from "./Hand";
|
import Hand from "./Hand";
|
||||||
import Pile from "./Pile";
|
import Pile from "./Pile";
|
||||||
@@ -20,7 +24,7 @@ export default (props: { instanceId: string }) => {
|
|||||||
api
|
api
|
||||||
.simple(props)
|
.simple(props)
|
||||||
.post({ action })
|
.post({ action })
|
||||||
.then((res) => mutate(res.data as GameState));
|
.then((res) => res.status == 200 && mutate(res.data as vGameState));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GameContext.Provider value={{ gameState, submitAction }}>
|
<GameContext.Provider value={{ gameState, submitAction }}>
|
||||||
@@ -38,7 +42,7 @@ export default (props: { instanceId: string }) => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Hand hand={gameState.latest!.players[0]} />
|
<Hand hand={gameState.latest!.players["daniel"]} />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</GameContext.Provider>
|
</GameContext.Provider>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { A, useParams } from "@solidjs/router";
|
import { A, useParams } from "@solidjs/router";
|
||||||
|
|
||||||
import { createEffect, createResource, Show, Suspense } from "solid-js";
|
|
||||||
import Game from "../../components/Game";
|
import Game from "../../components/Game";
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
@@ -8,7 +7,7 @@ export default () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Game instanceId={Number.parseInt(params.instance)} />
|
<Game instanceId={params.instance} />
|
||||||
<A
|
<A
|
||||||
href={`/${params.game}`}
|
href={`/${params.game}`}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -15,7 +15,17 @@ export default () => {
|
|||||||
<Suspense>
|
<Suspense>
|
||||||
<div style={{ padding: "20px" }}>
|
<div style={{ padding: "20px" }}>
|
||||||
<h1 style={{ margin: 0 }}>{param.game}</h1>
|
<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>
|
<ul>
|
||||||
<For each={instances() ?? []}>
|
<For each={instances() ?? []}>
|
||||||
{(instance) => (
|
{(instance) => (
|
||||||
|
|||||||
@@ -16,9 +16,17 @@ model Game {
|
|||||||
instances Instance[]
|
instances Instance[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Instance {
|
model Human {
|
||||||
id Int @id @default(autoincrement())
|
key String @id
|
||||||
gameKey String
|
name String
|
||||||
game Game @relation(fields: [gameKey], references: [key])
|
Instance Instance[]
|
||||||
gameState Json
|
}
|
||||||
|
|
||||||
|
model Instance {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@prisma/client": "6.13.0",
|
"@prisma/client": "6.13.0",
|
||||||
"elysia": "^1.3.8",
|
"elysia": "^1.3.8",
|
||||||
"hono": "^4.8.12",
|
"hono": "^4.8.12",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
"zod": "^4.0.15"
|
"zod": "^4.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
shuffle,
|
shuffle,
|
||||||
vCard,
|
vCard,
|
||||||
} from "../../../shared/cards";
|
} from "../../../shared/cards";
|
||||||
import hash from "object-hash";
|
|
||||||
import { heq } from "../../../shared/utils";
|
import { heq } from "../../../shared/utils";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { prisma } from "../db/db";
|
import { prisma } from "../db/db";
|
||||||
@@ -14,7 +13,6 @@ import { prisma } from "../db/db";
|
|||||||
// omniscient game state
|
// omniscient game state
|
||||||
export type GameState = {
|
export type GameState = {
|
||||||
prev?: {
|
prev?: {
|
||||||
hash: string;
|
|
||||||
action: Action;
|
action: Action;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,14 +39,15 @@ export type PlayerView = {
|
|||||||
|
|
||||||
export type Action = { type: "draw" } | { type: "discard"; card: Card };
|
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()),
|
deck: shuffle(newDeck()),
|
||||||
players: Object.fromEntries(players.map((humanId) => [humanId, []])),
|
players: Object.fromEntries(players.map((humanId) => [humanId, []])),
|
||||||
} as GameState);
|
} as GameState;
|
||||||
|
};
|
||||||
|
|
||||||
export const getKnowledge = (state: GameState, humanId: string) => ({
|
export const getKnowledge = (state: GameState, humanId: string) => ({
|
||||||
hash: hash(state),
|
|
||||||
humanId,
|
humanId,
|
||||||
deck: state.deck.map((_) => null),
|
deck: state.deck.map((_) => null),
|
||||||
players: Object.fromEntries(
|
players: Object.fromEntries(
|
||||||
@@ -59,23 +58,27 @@ export const getKnowledge = (state: GameState, humanId: string) => ({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resolveAction = (state: GameState, action: Action): GameState => {
|
export const resolveAction = (
|
||||||
if (action.prevHash != hash(state)) {
|
state: GameState,
|
||||||
throw new Error(
|
humanId: string,
|
||||||
`action thinks it's applying to ${
|
action: Action
|
||||||
action.prevHash
|
): GameState => {
|
||||||
}, but we're checking it against ${hash(state)}`
|
// 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") {
|
if (action.type == "draw") {
|
||||||
const [drawn, ...rest] = state.deck;
|
const [drawn, ...rest] = state.deck;
|
||||||
return {
|
return {
|
||||||
deck: rest,
|
deck: rest,
|
||||||
players: {
|
players: {
|
||||||
...state.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],
|
deck: [action.card, ...state.deck],
|
||||||
players: {
|
players: {
|
||||||
...state.players,
|
...state.players,
|
||||||
[action.humanId]: playerHand
|
[humanId]: playerHand
|
||||||
.slice(0, index)
|
.slice(0, index)
|
||||||
.concat(playerHand.slice(index + 1)),
|
.concat(playerHand.slice(index + 1)),
|
||||||
},
|
},
|
||||||
@@ -94,10 +97,18 @@ export const resolveAction = (state: GameState, action: Action): GameState => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const simpleApi = new Elysia({ prefix: "/simple" })
|
export const simpleApi = new Elysia({ prefix: "/simple" })
|
||||||
|
// .guard({ headers: t.Object({ Human: t.String() }) })
|
||||||
.post(
|
.post(
|
||||||
"/newGame",
|
"/newGame",
|
||||||
({ body: { players } }: { body: { players: string[] } }) =>
|
(args: { body: { players: string[] }; headers: { human: string } }) => {
|
||||||
newGame(players)
|
return prisma.instance.create({
|
||||||
|
data: {
|
||||||
|
gameState: newGame(args.body.players),
|
||||||
|
gameKey: "simple",
|
||||||
|
createdByKey: args.headers.human,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.group("/:instanceId", (app) =>
|
.group("/:instanceId", (app) =>
|
||||||
app
|
app
|
||||||
@@ -105,33 +116,35 @@ export const simpleApi = new Elysia({ prefix: "/simple" })
|
|||||||
prisma.instance
|
prisma.instance
|
||||||
.findUnique({
|
.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(instanceId),
|
id: instanceId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((game) => game?.gameState)
|
.then((game) => game?.gameState)
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/",
|
"/",
|
||||||
({ params: { instanceId }, body: { action } }) =>
|
({
|
||||||
|
params: { instanceId },
|
||||||
|
body: { action },
|
||||||
|
headers: { Human: humanId },
|
||||||
|
}) =>
|
||||||
prisma.instance
|
prisma.instance
|
||||||
.findUniqueOrThrow({
|
.findUniqueOrThrow({
|
||||||
where: {
|
where: {
|
||||||
id: Number(instanceId),
|
id: instanceId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((game) => {
|
.then((game) => {
|
||||||
const newState = resolveAction(
|
const newState = resolveAction(
|
||||||
game.gameState as GameState,
|
game.gameState as GameState,
|
||||||
|
humanId!,
|
||||||
action
|
action
|
||||||
);
|
);
|
||||||
const knownState = getKnowledge(
|
const knownState = getKnowledge(newState, humanId!);
|
||||||
newState,
|
|
||||||
action.humanId
|
|
||||||
);
|
|
||||||
void prisma.instance.update({
|
void prisma.instance.update({
|
||||||
data: { gameState: newState },
|
data: { gameState: newState },
|
||||||
where: {
|
where: {
|
||||||
id: Number(instanceId),
|
id: instanceId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return knownState;
|
return knownState;
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ const app = new Elysia()
|
|||||||
.onRequest(({ request }) => {
|
.onRequest(({ request }) => {
|
||||||
console.log(request.method, request.url);
|
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);
|
console.error(code, error);
|
||||||
})
|
})
|
||||||
.get("/ping", () => "pong")
|
.get("/ping", () => "pong")
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -57,6 +57,9 @@ importers:
|
|||||||
hono:
|
hono:
|
||||||
specifier: ^4.8.12
|
specifier: ^4.8.12
|
||||||
version: 4.8.12
|
version: 4.8.12
|
||||||
|
object-hash:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0
|
||||||
zod:
|
zod:
|
||||||
specifier: ^4.0.15
|
specifier: ^4.0.15
|
||||||
version: 4.0.15
|
version: 4.0.15
|
||||||
@@ -810,6 +813,10 @@ packages:
|
|||||||
engines: {node: ^14.16.0 || >=16.10.0}
|
engines: {node: ^14.16.0 || >=16.10.0}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
object-hash@3.0.0:
|
||||||
|
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
ohash@2.0.11:
|
ohash@2.0.11:
|
||||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||||
|
|
||||||
@@ -1736,6 +1743,8 @@ snapshots:
|
|||||||
pkg-types: 2.2.0
|
pkg-types: 2.2.0
|
||||||
tinyexec: 1.0.1
|
tinyexec: 1.0.1
|
||||||
|
|
||||||
|
object-hash@3.0.0: {}
|
||||||
|
|
||||||
ohash@2.0.11: {}
|
ohash@2.0.11: {}
|
||||||
|
|
||||||
openapi-types@12.1.3:
|
openapi-types@12.1.3:
|
||||||
|
|||||||
Reference in New Issue
Block a user