// Bun: bun sudoku.ts http://localhost:3000 // Import { newGame } to use this as a client. No packages required. // x = column, y = row (0–8); n = value (1–9); board[y][x]. export class ApiError extends Error { constructor(public readonly status: number, public readonly details: unknown) { super(`HTTP ${status}: ${JSON.stringify(details)}`); } } async function request(baseUrl: string, path: string, method = "GET", body?: unknown) { const response = await fetch(baseUrl.replace(/\/$/, "") + path, { method, headers: { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), signal: AbortSignal.timeout(10_000), }); const data = await response.json(); if (!response.ok) throw new ApiError(response.status, data); return data; } export class Game { constructor( public readonly id: string, public board: number[][], private readonly baseUrl: string, ) {} async move(x: number, y: number, n: number): Promise { if (![x, y, n].every(Number.isInteger) || x < 0 || x > 8 || y < 0 || y > 8 || n < 1 || n > 9) { throw new RangeError("x and y must be 0–8; n must be 1–9"); } const data = await request(this.baseUrl, `/board/${encodeURIComponent(this.id)}/turn`, "PATCH", { row: y, col: x, value: n, }); this.board = data.board; return this; } } export async function newGame(baseUrl = "http://localhost:3000"): Promise { const { id } = await request(baseUrl, "/board", "POST"); const { board } = await request(baseUrl, `/board/${encodeURIComponent(id)}`); return new Game(id, board, baseUrl); } if (import.meta.main) { let game = await newGame(process.argv[2]); game = await game.move(2, 1, 5); console.log(JSON.stringify(game.board)); }