From 201a5f583bbff1b37084e778d101a818b1e02f14 Mon Sep 17 00:00:00 2001 From: TK-613 Date: Sun, 21 Dec 2025 05:56:07 +0900 Subject: [PATCH 1/2] feature:add endpoint for create Game --- apps/api/seed/drizzle-test.sql | 4 +- apps/api/src/db/schema.ts | 36 ++++++++++- apps/api/src/index.ts | 26 ++++++-- apps/api/src/lib/create-game.ts | 40 ++++++++++++ .../src/features/chess/compomemt/Board.tsx | 15 ++--- .../features/chess/compomemt/ChessPieces.tsx | 7 +- .../src/features/chess/compomemt/Mark.tsx | 64 +++++++++---------- .../src/features/chess/compomemt/store.ts | 15 ++--- packages/schema/src/index.ts | 1 + packages/schema/src/initGame.ts | 16 +++++ packages/schema/src/user.ts | 6 +- 11 files changed, 165 insertions(+), 65 deletions(-) create mode 100644 apps/api/src/lib/create-game.ts create mode 100644 packages/schema/src/initGame.ts diff --git a/apps/api/seed/drizzle-test.sql b/apps/api/seed/drizzle-test.sql index 490e39e..5723852 100644 --- a/apps/api/seed/drizzle-test.sql +++ b/apps/api/seed/drizzle-test.sql @@ -34,4 +34,6 @@ CREATE TABLE IF NOT EXISTS personalitys ( randomness REAL NOT NULL ); -INSERT INTO users (name) VALUES ("hello"); \ No newline at end of file +INSERT INTO users (name) VALUES ("hello"); +INSERT INTO users (id, name) VALUES ('550e8400-e29b-41d4-a716-446655440000', 'Player'); +INSERT INTO users (id, name) VALUES ('550e8400-e29b-41d4-a716-446655440001', 'Enemy'); \ No newline at end of file diff --git a/apps/api/src/db/schema.ts b/apps/api/src/db/schema.ts index 8108391..43b21b9 100644 --- a/apps/api/src/db/schema.ts +++ b/apps/api/src/db/schema.ts @@ -1,6 +1,40 @@ -import { sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { sqliteTable, text, integer, check, real } from "drizzle-orm/sqlite-core"; +import { sql } from "drizzle-orm"; export const users = sqliteTable("users", { id: text().primaryKey(), name: text().notNull(), }); + +export const games = sqliteTable( + "games", + { + id: integer("id").primaryKey({ autoIncrement: true }), + player_id: text() + .notNull() + .references(() => users.id), + enemy_id: text() + .notNull() + .references(() => users.id), + first_player: text().notNull(), + winner: text("winner").default(sql`NULL`), + }, + (table) => [ + check( + "first_player_check", + sql`${table.first_player} IN (${table.player_id}, ${table.enemy_id})`, + ), + check( + "winner_check", + sql`${table.winner} IN ('player', 'enemy', 'draw') OR ${table.winner} IS NULL`, + ), + ], +); + +export const personalitys = sqliteTable("personalitys", { + id: integer().primaryKey(), + obedience: real().notNull(), + aggressiveness: real().notNull(), + fear: real().notNull(), + randomness: real().notNull(), +}); diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 1111d25..85178da 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -6,14 +6,15 @@ import { VoiceInputSuccessResponseSchema, VoiceInputErrorResponseSchema, ResolveActionInputSchema, + initGameSchema, UserCreatedInputSchema, } from "@repo/schema"; import { createGeminiClient } from "./lib/gemini"; import { transformVoiceInput } from "./lib/voice-input-transformer"; import { resolveAction } from "./lib/resolve-action"; -import { drizzle } from 'drizzle-orm/d1'; -import { users } from "./db/schema" - +import { drizzle } from "drizzle-orm/d1"; +import { users } from "./db/schema"; +import { createGame } from "./lib/create-game"; type Bindings = { ASSETS: Fetcher; @@ -21,12 +22,11 @@ type Bindings = { DB: D1Database; }; - // API routes const api = new Hono<{ Bindings: Bindings }>() .get("/", async (c) => { const db = drizzle(c.env.DB); - const result = await db.select().from(users).all() + const result = await db.select().from(users).all(); return Response.json(result); }) .get("/users", (c) => { @@ -34,10 +34,10 @@ const api = new Hono<{ Bindings: Bindings }>() return c.json(UsersResponseSchema.parse(response)); }) .post("/v1/users", zValidator("json", UserCreatedInputSchema), async (c) => { - const params = c.req.valid("json"); + const params = c.req.valid("json"); const db = drizzle(c.env.DB); const uuid = self.crypto.randomUUID(); - await db.insert(users).values({ id: uuid, name: params.name}); + await db.insert(users).values({ id: uuid, name: params.name }); return c.json({ userId: uuid, success: true }, 201); }) .post("/voice-input/transform", zValidator("json", VoiceInputRequestSchema), async (c) => { @@ -83,6 +83,18 @@ const api = new Hono<{ Bindings: Bindings }>() const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return c.json({ error: errorMessage }, 500); } + }) + .post("/v1/createGame", zValidator("json", initGameSchema), async (c) => { + try { + const db = drizzle(c.env.DB); + const gameData = c.req.valid("json"); + const result = await createGame(gameData, db); + + return c.json(result, 201); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; + return c.json({ error: errorMessage }, 500); + } }); // Main app diff --git a/apps/api/src/lib/create-game.ts b/apps/api/src/lib/create-game.ts new file mode 100644 index 0000000..05f1851 --- /dev/null +++ b/apps/api/src/lib/create-game.ts @@ -0,0 +1,40 @@ +import { type initGame, type initGameResponse, initGameResponseSchema } from "@repo/schema"; +import { DrizzleD1Database } from "drizzle-orm/d1"; +import { games, personalitys } from "../db/schema"; + +export async function createGame( + gameData: initGame, + db: DrizzleD1Database, +): Promise { + try { + await db + .insert(games) + .values({ + player_id: gameData.player_id, + enemy_id: gameData.enemy_id, + first_player: gameData.first_player, + }) + .returning(); + + // Generate random personalitys for response + const allPersonalitys = await db.select().from(personalitys).all(); + + if (allPersonalitys.length == 0) { + throw new Error("No personalitys found in database"); + } + + const randomPersonalitys = []; + for (let i = 0; i < 32; i++) { + randomPersonalitys[i] = allPersonalitys[Math.floor(Math.random() * allPersonalitys.length)]; + } + + const response = initGameResponseSchema.parse({ + response: randomPersonalitys, + }); + return response; + } catch (error) { + throw new Error( + `Failed to create game: ${error instanceof Error ? error.message : String(error)}`, + ); + } +} diff --git a/apps/frontend/src/features/chess/compomemt/Board.tsx b/apps/frontend/src/features/chess/compomemt/Board.tsx index 2d23b7b..eb3e266 100644 --- a/apps/frontend/src/features/chess/compomemt/Board.tsx +++ b/apps/frontend/src/features/chess/compomemt/Board.tsx @@ -16,13 +16,10 @@ interface BoardProps { className?: string; } - function ChessLine() { const squares = []; const squareSize = 0.6; // 1マスのサイズ - const borderColor = new THREE.Color( - "rgba(228, 221, 209, 1)" - ).convertSRGBToLinear(); + const borderColor = new THREE.Color("rgba(228, 221, 209, 1)").convertSRGBToLinear(); for (let row = 0; row < 8; row++) { for (let col = 0; col < 8; col++) { @@ -62,7 +59,7 @@ function ChessLine() { } const Board: React.FC = ({ className }) => { const [commanddata, setCommanddata] = useState(null); - const {turn, } = useTurnStore() + const { turn } = useTurnStore(); return ( @@ -71,7 +68,7 @@ const Board: React.FC = ({ className }) => { {turn == "white" && ( - console.log("認識結果:", text)} onTransformSuccess={(data) => { console.log("親で変換成功データを取得:", data); @@ -83,7 +80,7 @@ const Board: React.FC = ({ className }) => { /> )} {turn == "black" && ( - console.log("認識結果:", text)} onTransformSuccess={(data) => { console.log("親で変換成功データを取得:", data); @@ -94,12 +91,12 @@ const Board: React.FC = ({ className }) => { position={[0, 1.5, 5]} /> )} - + - + diff --git a/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx b/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx index 2a0142c..3a5c8db 100644 --- a/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx +++ b/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx @@ -198,16 +198,15 @@ function MoveCommand(pieces: Piece[], command: VoiceInput | null): Piece[] { }); } - const ChessPieces = ({ command }: { command: VoiceInput | null }) => { const [pieces, setPieces] = useState(createInitialPieces()); - const {change} = useTurnStore() - const [pieceID, ] = LinkVoiceAndId({ pieces, command }); + const { change } = useTurnStore(); + const [pieceID] = LinkVoiceAndId({ pieces, command }); useEffect(() => { if (command) { setPieces(MoveCommand(pieces, command)); - if (pieceID != -1){ + if (pieceID != -1) { setTimeout(change, 2000); } } diff --git a/apps/frontend/src/features/chess/compomemt/Mark.tsx b/apps/frontend/src/features/chess/compomemt/Mark.tsx index 7671d56..f203e62 100644 --- a/apps/frontend/src/features/chess/compomemt/Mark.tsx +++ b/apps/frontend/src/features/chess/compomemt/Mark.tsx @@ -7,146 +7,146 @@ export default function Mark() { position={[-2.8, -2.3, 0.03]} size={0.35} height={0.05} - font='https://threejs.org/examples/fonts/helvetiker_regular.typeface.json' + font="https://threejs.org/examples/fonts/helvetiker_regular.typeface.json" > 1 - + 2 - + 3 - + 4 - + 5 - + 6 - + 7 - + 8 - + A - + B - + C - + D - + E - + F - + G - + H - + ); diff --git a/apps/frontend/src/features/chess/compomemt/store.ts b/apps/frontend/src/features/chess/compomemt/store.ts index 2c4d878..7b80a43 100644 --- a/apps/frontend/src/features/chess/compomemt/store.ts +++ b/apps/frontend/src/features/chess/compomemt/store.ts @@ -1,14 +1,13 @@ import { create } from "zustand"; - interface TurnState { - turn: string; - - change: () => void; + turn: string; + + change: () => void; } const useTurnStore = create((set) => ({ - turn: "white", - change: () => set((state) => ({ turn: state.turn === "white" ? "black" : "white" })) + turn: "white", + change: () => set((state) => ({ turn: state.turn === "white" ? "black" : "white" })), })); - -export default useTurnStore; \ No newline at end of file + +export default useTurnStore; diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 93817ac..54b03e1 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -6,3 +6,4 @@ export * from "./aiResponse"; export * from "./personality"; export * from "./backend-chess"; export * from "./resolve-action"; +export * from "./initGame"; diff --git a/packages/schema/src/initGame.ts b/packages/schema/src/initGame.ts new file mode 100644 index 0000000..8ea7dc7 --- /dev/null +++ b/packages/schema/src/initGame.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; +import { personalitySchema } from "./personality"; + +export const initGameSchema = z.object({ + player_id: z.uuid(), + enemy_id: z.uuid(), + first_player: z.uuid(), +}); + +export type initGame = z.infer; + +export const initGameResponseSchema = z.object({ + response: z.array(personalitySchema).length(32), +}); + +export type initGameResponse = z.infer; diff --git a/packages/schema/src/user.ts b/packages/schema/src/user.ts index 278e565..f1afaeb 100644 --- a/packages/schema/src/user.ts +++ b/packages/schema/src/user.ts @@ -11,8 +11,8 @@ export const CreateUserSchema = UserSchema.omit({ id: true }); export type CreateUser = z.infer; export const UserCreatedInputSchema = z.object({ - name: z.string().min(2) -}) + name: z.string().min(2), +}); export type UserCreatedInput = z.infer; export const UsersResponseSchema = z.object({ @@ -21,7 +21,7 @@ export const UsersResponseSchema = z.object({ export const UserCreatedResponseSchema = z.object({ userId: z.uuid(), - success: z.boolean() + success: z.boolean(), }); export type UsersResponse = z.infer; From d5d97c6e26b20053635595f46b3495d550a1020c Mon Sep 17 00:00:00 2001 From: TK-613 Date: Sun, 21 Dec 2025 07:01:55 +0900 Subject: [PATCH 2/2] feature(schema):add game_id to initDataResponseSchema --- apps/api/src/lib/create-game.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/api/src/lib/create-game.ts b/apps/api/src/lib/create-game.ts index 05f1851..d9ed4b9 100644 --- a/apps/api/src/lib/create-game.ts +++ b/apps/api/src/lib/create-game.ts @@ -1,13 +1,17 @@ -import { type initGame, type initGameResponse, initGameResponseSchema } from "@repo/schema"; +import { + type initGame, + type initGameResponse, + initGameResponseSchema, +} from "@repo/schema"; import { DrizzleD1Database } from "drizzle-orm/d1"; import { games, personalitys } from "../db/schema"; export async function createGame( gameData: initGame, - db: DrizzleD1Database, + db: DrizzleD1Database ): Promise { try { - await db + const result = await db .insert(games) .values({ player_id: gameData.player_id, @@ -16,6 +20,9 @@ export async function createGame( }) .returning(); + // game ID のみ取得 + const game_id = result[0].id; + // Generate random personalitys for response const allPersonalitys = await db.select().from(personalitys).all(); @@ -25,16 +32,20 @@ export async function createGame( const randomPersonalitys = []; for (let i = 0; i < 32; i++) { - randomPersonalitys[i] = allPersonalitys[Math.floor(Math.random() * allPersonalitys.length)]; + randomPersonalitys[i] = + allPersonalitys[Math.floor(Math.random() * allPersonalitys.length)]; } const response = initGameResponseSchema.parse({ - response: randomPersonalitys, + game_id: game_id, + personalitys: randomPersonalitys, }); return response; } catch (error) { throw new Error( - `Failed to create game: ${error instanceof Error ? error.message : String(error)}`, + `Failed to create game: ${ + error instanceof Error ? error.message : String(error) + }` ); } }