Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 58 additions & 2 deletions apps/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { randomInt } from "node:crypto";
import { text } from "node:stream/consumers";
import { PrismaPg } from "@prisma/adapter-pg";
import { Hono } from "hono";
import { getSignedCookie, setSignedCookie } from "hono/cookie";
import { cors } from "hono/cors";
import { createMiddleware } from "hono/factory";
import { Pool } from "pg";
import { PrismaClient } from "./generated/prisma";
import { PrismaClient } from "./generated/prisma/client";
import {
type GameState,
Magic,
Expand All @@ -14,9 +15,11 @@ import {
type Operation,
type Rule,
} from "./magic";
import { Matching } from "./matching";

type Bindings = {
MAGIC: DurableObjectNamespace;
MATCHING: DurableObjectNamespace;
DATABASE_URL: string;
};

Expand Down Expand Up @@ -233,6 +236,34 @@ const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
return c.json({ message: "Left room successfully" });
})

.post("/matching/create", authMiddleware, async (c) => {
const prisma = c.get("prisma");

const { members } = await c.req.json<{ members: string[] }>();
if (!members) {
return c.json({ error: "Member is requirecd" }, 400);
}
const roomName = generateRandomString(8);
const secret = randomInt(100000, 999999).toString();

const room = await prisma.room.create({
data: {
name: roomName,
hostId: members[0],
users: members,
},
});

await prisma.roomSecret.create({
data: {
roomId: room.id,
secret,
},
});

return c.json(room, 201);
})

.get("/games/:id/ws", authMiddleware, async (c) => {
const gameId = c.req.param("id");
const user = c.get("user");
Expand All @@ -243,12 +274,37 @@ const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
const url = new URL(c.req.url);
url.searchParams.set("playerId", user.id);

const request = new Request(url.toString(), c.req.raw);
return stub.fetch(request);
})

.get("/matching/ws", authMiddleware, async (c) => {
const user = c.get("user");

const id = c.env.MATCHING.idFromName("matching");
const stub = c.env.MATCHING.get(id);

const url = new URL(c.req.url);
url.searchParams.set("playerId", user.id);

const request = new Request(url.toString(), c.req.raw);
return stub.fetch(request);
});

function generateRandomString(length: number, charset?: string): string {
const defaultCharset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const chars = charset || defaultCharset;
let result = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * chars.length);
result += chars[randomIndex];
}
return result;
}

export type AppType = typeof app;
export default app;

export type { GameState, MoveAction, MessageType, Rule, Operation };
export { Magic };
export { Magic, Matching };
16 changes: 8 additions & 8 deletions apps/backend/src/magic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,9 +483,7 @@ export class Magic extends DurableObject {
this.gameState.board[j][this.gameState.rules.boardSize - j - 1],
);
} else {
nullinary.push(
this.gameState.board[this.gameState.rules.boardSize - j - 1][j],
);
nullinary.push(this.gameState.board[j][j]);
}
}
const diaary = nullinary.filter((value) => value !== null);
Expand All @@ -494,7 +492,7 @@ export class Magic extends DurableObject {
if (i === 0) {
matrix[j][this.gameState.rules.boardSize - j - 1] = true;
} else {
matrix[this.gameState.rules.boardSize - j - 1][j] = true;
matrix[j][j] = true;
}
}
}
Expand All @@ -518,8 +516,9 @@ export class Magic extends DurableObject {
}
if (hikaku > 3) {
for (let i = 0; i < nullinary.length; i++) {
matrix[Math.floor(i / mission.number)][i % mission.number] =
this.multi(nullinary[i], mission.number);
matrix[Math.floor(i / this.gameState.rules.boardSize)][
i % this.gameState.rules.boardSize
] = this.multi(nullinary[i], mission.number);
}
}
}
Expand All @@ -532,8 +531,9 @@ export class Magic extends DurableObject {
}
if (hikaku > 3) {
for (let i = 0; i < nullinary.length; i++) {
matrix[Math.floor(i / mission.number)][i % mission.number] =
this.prime(nullinary[i]);
matrix[Math.floor(i / this.gameState.rules.boardSize)][
i % this.gameState.rules.boardSize
] = this.prime(nullinary[i]);
}
}
}
Expand Down
130 changes: 130 additions & 0 deletions apps/backend/src/matching.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { DurableObject } from "cloudflare:workers";
import { randomInt } from "node:crypto";
import { PrismaClient } from "./generated/prisma/client";

const prisma = new PrismaClient();

function generateRandomString(length: number, charset?: string): string {
const defaultCharset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const chars = charset || defaultCharset;
let result = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * chars.length);
result += chars[randomIndex];
}
return result;
}

interface Session {
ws: WebSocket;
playerId: string;
}

export class Matching extends DurableObject {
waitingUser: string[] | undefined = [];
sessions: Session[] = [];

constructor(ctx: DurableObjectState, env: unknown) {
super(ctx, env);
this.ctx.blockConcurrencyWhile(async () => {
this.waitingUser = await this.ctx.storage.get<string[]>("waitingUser");
});
}

async fetch(request: Request) {
const url = new URL(request.url);
const playerId = url.searchParams.get("playerId");
const playerName = url.searchParams.get("playerName");
if (!playerId) {
return new Response("playerId is required", { status: 400 });
} else if (!playerName) {
return new Response("playerName is required", { status: 400 });
}

if (request.headers.get("Upgrade") !== "websocket") {
return new Response("Expected websocket", { status: 400 });
}

const { 0: client, 1: server } = new WebSocketPair();
await this.handleSession(server, playerId);

return new Response(null, {
status: 101,
webSocket: client,
});
}

async handleSession(ws: WebSocket, playerId: string) {
const session: Session = { ws, playerId };
this.sessions.push(session);

ws.accept();

// Add player to the game state if not already present
await this.addUser(playerId);

ws.addEventListener("message", async (msg) => {
try {
// TODO: 型をつける
const { type, payload } = JSON.parse(msg.data as string);

switch (type) {
}
} catch {
ws.send(JSON.stringify({ error: "Invalid message" }));
}
});

ws.send(JSON.stringify({ type: "addUser", payload: this.waitingUser }));

if (this.waitingUser?.length === 2) {
const roomName = generateRandomString(6);
const room = await prisma.room.create({
data: {
name: roomName,
hostId: this.waitingUser[0],
users: this.waitingUser,
},
});
const roomSecret = await prisma.roomSecret.create({
data: {
roomId: room.id,
secret: randomInt(100000, 999999).toString(),
},
});
ws.send(JSON.stringify({ type: "goRoom", payload: roomSecret }));
this.waitingUser = [];
ws.send(JSON.stringify({ type: "addUser", payload: this.waitingUser }));
}
}

async addUser(playerId: string) {
if (!this.waitingUser) {
this.waitingUser = [];
}
if (
this.waitingUser &&
this.waitingUser.length !== 2 &&
!this.waitingUser.includes(playerId)
) {
this.waitingUser.push(playerId);

await this.ctx.storage.put("waitingUser", this.waitingUser);
this.broadcast({ type: "addUser", payload: this.waitingUser });
}
if (this.waitingUser && this.waitingUser.length === 2) {
console.log("GO!");
}
}
broadcast(message: unknown) {
const serialized = JSON.stringify(message);
this.sessions.forEach((session) => {
try {
session.ws.send(serialized);
} catch {
this.sessions = this.sessions.filter((s) => s !== session);
}
});
}
}
4 changes: 4 additions & 0 deletions apps/backend/wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
{
"name": "MAGIC",
"class_name": "Magic"
},
{
"name": "MATCHING",
"class_name": "Matching"
}
]
},
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export default [
index("routes/home.tsx"),
route("logic-puzzle", "routes/logic-puzzle.tsx"),
route("logic-puzzle/lobby", "routes/logic-puzzle/lobby.tsx"),
route("logic-puzzle/matching", "routes/logic-puzzle/matching.tsx"),
route("logic-puzzle/room/:roomId", "routes/logic-puzzle/room.$roomId.tsx"),
] satisfies RouteConfig;
12 changes: 12 additions & 0 deletions apps/frontend/app/routes/logic-puzzle/lobby.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ export default function Lobby() {

<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<div className="space-y-8">
<div>
<h2 className="text-2xl font-bold mb-4">Random Match</h2>
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<div className="card-actions justify-end mt-4">
<a href="/logic-puzzle/matching" className="btn btn-primary">
Go!
</a>
</div>
</div>
</div>
</div>
<div>
<h2 className="text-2xl font-bold mb-4">Create a Room</h2>
<div className="card bg-base-100 shadow-xl">
Expand Down
Loading