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 b45dc43..85178da 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -6,6 +6,7 @@ import { VoiceInputSuccessResponseSchema, VoiceInputErrorResponseSchema, ResolveActionInputSchema, + initGameSchema, UserCreatedInputSchema, } from "@repo/schema"; import { createGeminiClient } from "./lib/gemini"; @@ -13,6 +14,7 @@ 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 { createGame } from "./lib/create-game"; type Bindings = { ASSETS: Fetcher; @@ -32,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) => { @@ -81,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..d9ed4b9 --- /dev/null +++ b/apps/api/src/lib/create-game.ts @@ -0,0 +1,51 @@ +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 { + const result = await db + .insert(games) + .values({ + player_id: gameData.player_id, + enemy_id: gameData.enemy_id, + first_player: gameData.first_player, + }) + .returning(); + + // game ID のみ取得 + const game_id = result[0].id; + + // 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({ + game_id: game_id, + personalitys: 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 6c7b091..3af9bca 100644 --- a/apps/frontend/src/features/chess/compomemt/Board.tsx +++ b/apps/frontend/src/features/chess/compomemt/Board.tsx @@ -92,10 +92,7 @@ const Board: React.FC = ({ className }) => { /> )} - + diff --git a/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx b/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx index 58cffd3..9edf141 100644 --- a/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx +++ b/apps/frontend/src/features/chess/compomemt/ChessPieces.tsx @@ -192,7 +192,6 @@ function MoveCommand( }); } - const ChessPieces = ({ command }: { command: VoiceInput | null }) => { const [pieces, setPieces] = useState(createInitialPieces()); const { startAnimation } = useAnimationStore(); 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;