From 25b8da601f7a2d365b17c9235873481a1d0dbeb5 Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:23:27 +0900 Subject: [PATCH 1/3] feat: add color.js to rules --- src/lib/assets/life-game-rules/color.js | 397 ++++++++++++++++++++++++ src/lib/icons/index.ts | 1 + src/lib/icons/paint-brush.svg | 3 + src/lib/rules-explanation.ts | 12 + src/routes/+page.svelte | 56 +++- 5 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 src/lib/assets/life-game-rules/color.js create mode 100644 src/lib/icons/paint-brush.svg diff --git a/src/lib/assets/life-game-rules/color.js b/src/lib/assets/life-game-rules/color.js new file mode 100644 index 0000000..3bf9300 --- /dev/null +++ b/src/lib/assets/life-game-rules/color.js @@ -0,0 +1,397 @@ +"use strict"; + +let timer = "stop"; +let generationFigure = 0; +let isDragging = false; +let dragMode = 0; // 1: 指定した色にする, 0: 白にする +let isPlacingTemplate = false; +let patternShape = []; +let patternHeight = 0; +let patternWidth = 0; +let previewCells = []; + +//変数設定 +let boardSize = 20; //盤面の大きさ(20x20) +const cellSize = 600 / boardSize; //セルの大きさ(px) + +const WHITE = { r: 255, g: 255, b: 255 }; +const BLACK = { r: 0, g: 0, b: 0 }; +let currentColor = { ...BLACK }; +let isColorMode = false; + +function isAlive(cell) { + return !(cell.r === WHITE.r && cell.g === WHITE.g && cell.b === WHITE.b); +} + +// around: 周囲の生きたセル数 self: 自身が生きているかどうか +function isNextAlive(around, self) { + // 自身が生きている & 周囲が 2 か 3 で生存 + if (self && 2 <= around && around <= 3) { + return self; + } + // 自身が死んでいる & 周囲が 3 で誕生 + if (!self && around === 3) { + return 1; + } + return 0; +} + +// cellの状態に応じた色を返す関数 +function getStyle(cell) { + return "rgb(" + cell.r + ", " + cell.g + ", " + cell.b + ")"; +} + +// 周囲の色の平均を取得する関数 +function getAverageColor(i, j) { + let totalR = 0, + totalG = 0, + totalB = 0, + count = 0; + + for (let di = -1; di <= 1; di++) { + for (let dj = -1; dj <= 1; dj++) { + if (di === 0 && dj === 0) continue; + const ni = i + di; + const nj = j + dj; + if (ni >= 0 && ni < boardSize && nj >= 0 && nj < boardSize && isAlive(board[ni][nj])) { + totalR += board[ni][nj].r; + totalG += board[ni][nj].g; + totalB += board[ni][nj].b; + count++; + } + } + } + + if (count > 0) { + return { + r: Math.round(totalR / count), + g: Math.round(totalG / count), + b: Math.round(totalB / count), + }; + } + + return isColorMode ? { ...currentColor } : { ...BLACK }; +} + +//Boardの初期化 +let board = Array.from({ length: boardSize }, () => + Array.from({ length: boardSize }, () => ({ ...WHITE })), +); +const table = document.getElementById("game-board"); + +//盤面をBoardに従って変更する関数達(Boardを変更したら実行する) +function renderBoard() { + // bodyを中央配置に設定 + document.body.style.display = "flex"; + document.body.style.justifyContent = "center"; + document.body.style.alignItems = "center"; + document.body.style.minHeight = "100vh"; + document.body.style.margin = "0"; + document.body.style.padding = "0"; + + // 初回の盤面生成 + table.innerHTML = ""; + for (let i = 0; i < boardSize; i++) { + const tr = document.createElement("tr"); + tr.style.padding = "0"; + for (let j = 0; j < boardSize; j++) { + const td = document.createElement("td"); + td.style.padding = "0"; + const button = document.createElement("button"); + button.style.backgroundColor = getStyle(board[i][j]); //Boardの対応する値によって色を変更 + + // ボードが大きいときは border をつけない + if (boardSize >= 50) { + button.style.border = "none"; + table.style.border = "1px solid black"; + } else { + button.style.border = "0.5px solid black"; + } + button.style.width = `${cellSize}px`; + button.style.height = `${cellSize}px`; + button.style.padding = "0"; //cellSizeが小さいとき、セルが横長になることを防ぐ + button.style.display = "block"; //cellSizeが小さいとき、行間が空きすぎるのを防ぐ + button.onclick = () => { + if (isPlacingTemplate) { + clearPreview(); + isPlacingTemplate = false; + if (i + patternHeight < boardSize + 1 && j + patternWidth < boardSize + 1) { + for (let r = 0; r < patternHeight; r++) { + for (let c = 0; c < patternWidth; c++) { + const boardRow = i + r; + const boardCol = j + c; + board[boardRow][boardCol] = + patternShape[r][c] === 1 ? { ...currentColor } : { ...WHITE }; + } + } + rerender(); + stop(); + } else { + window.parent.postMessage( + { + type: "Size shortage", + data: {}, + }, + "*", + ); + } + } else if (isColorMode && isAlive(board[i][j])) { + // 色選択モード:生きているセルに色を適用 + board[i][j] = { ...currentColor }; + button.style.backgroundColor = getStyle(board[i][j]); + } + }; + button.onmousedown = (e) => { + e.preventDefault(); + if (timer === "stop" && !isPlacingTemplate) { + isDragging = true; + if (isAlive(board[i][j])) { + board[i][j] = { ...WHITE }; + dragMode = 0; // 白にする + } else { + board[i][j] = { ...currentColor }; + dragMode = 1; // 指定した色にする + } + button.style.backgroundColor = getStyle(board[i][j]); + } + }; + button.onmouseenter = () => { + if (isDragging && timer === "stop" && !isPlacingTemplate) { + if (dragMode === 1 && !isAlive(board[i][j])) { + board[i][j] = { ...currentColor }; + } else if (dragMode === 0 && isAlive(board[i][j])) { + board[i][j] = { ...WHITE }; + } + button.style.backgroundColor = getStyle(board[i][j]); + } + if (isPlacingTemplate) { + drawPreview(i, j); + } + }; + td.appendChild(button); + tr.appendChild(td); + } + table.appendChild(tr); + } +} + +table.onmouseleave = () => { + if (isPlacingTemplate) { + clearPreview(); + } +}; + +function drawPreview(row, col) { + clearPreview(); + for (let r = 0; r < patternHeight; r++) { + for (let c = 0; c < patternWidth; c++) { + if (patternShape[r][c] === 1) { + const boardRow = row + r; + const boardCol = col + c; + if (boardRow < boardSize && boardCol < boardSize) { + const cell = table.rows[boardRow].cells[boardCol].firstChild; + cell.style.backgroundColor = "gray"; + previewCells.push({ row: boardRow, col: boardCol }); + } + } + } + } +} + +function clearPreview() { + previewCells.forEach((cellPos) => { + const cell = table.rows[cellPos.row].cells[cellPos.col].firstChild; + cell.style.backgroundColor = getStyle(board[cellPos.row][cellPos.col]); + }); + previewCells = []; +} + +function rerender() { + // 2回目以降の盤面生成 + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + const button = table.children[i].children[j].children[0]; + + // 色の更新 + const currentCellColor = button.style.backgroundColor; + const expectedCellColor = getStyle(board[i][j]); + if (currentCellColor !== expectedCellColor) { + button.style.backgroundColor = expectedCellColor; + } + + // セルサイズの更新 + const currentCellsize = button.style.width; + const expectedCellsize = `${cellSize}px`; + if (currentCellsize !== expectedCellsize) { + button.style.width = expectedCellsize; + button.style.height = expectedCellsize; + } + } + } +} + +document.addEventListener("mouseup", () => { + isDragging = false; +}); + +renderBoard(); + +function generationChange(num) { + //現在の世代を表すgenerationFigureを変更し、文章も変更 + generationFigure = num; + window.parent.postMessage( + { + type: "generation_change", + data: generationFigure, + }, + "*", + ); +} + +function progressBoard() { + const newBoard = board.map((row) => row.map((cell) => ({ ...cell }))); + for (let i = 0; i < boardSize; i++) { + for (let j = 0; j < boardSize; j++) { + //周囲のマスに黒マスが何個あるかを計算(aroundに格納)↓ + let around = 0; + let tate, yoko; + if (i === 0) { + tate = [0, 1]; + } else if (i === boardSize - 1) { + tate = [0, -1]; + } else { + tate = [-1, 0, 1]; + } + if (j === 0) { + yoko = [0, 1]; + } else if (j === boardSize - 1) { + yoko = [0, -1]; + } else { + yoko = [-1, 0, 1]; + } + for (let ii = 0; ii < tate.length; ii++) { + for (let jj = 0; jj < yoko.length; jj++) { + if (tate[ii] !== 0 || yoko[jj] !== 0) { + around += isAlive(board[i + tate[ii]][j + yoko[jj]]) ? 1 : 0; + } + } + } + //↑周囲のマスに黒マスが何個あるかを計算(aroundに格納) + + const cellIsAlive = isAlive(board[i][j]); + const nextAlive = isNextAlive(around, cellIsAlive ? 1 : 0); + + if (nextAlive && !cellIsAlive) { + // 新しく生まれるセル:周囲の色の平均 + newBoard[i][j] = getAverageColor(i, j); + } else if (nextAlive) { + // 生き続けるセル:色を保持 + newBoard[i][j] = { ...board[i][j] }; + } else { + // 死ぬセル:白にする + newBoard[i][j] = { ...WHITE }; + } + } + } + board = newBoard; + generationChange(generationFigure + 1); + rerender(); +} + +//イベント + +on.progress = () => { + progressBoard(); +}; + +on.play = () => { + timer = "start"; +}; + +on.pause = () => { + timer = "stop"; +}; + +on.board_reset = () => { + //すべて白にBoardを変更 + board = Array.from({ length: boardSize }, () => + Array.from({ length: boardSize }, () => ({ ...WHITE })), + ); + renderBoard(); + generationChange(0); + isColorMode = false; + table.style.cursor = "default"; +}; + +on.board_randomize = () => { + //ランダムな色にBoardを変更 + board = Array.from({ length: boardSize }, () => + Array.from({ length: boardSize }, () => { + if (Math.random() > 0.5) { + return { + r: Math.floor(Math.random() * 256), + g: Math.floor(Math.random() * 256), + b: Math.floor(Math.random() * 256), + }; + } else { + return { ...WHITE }; + } + }), + ); + renderBoard(); + generationChange(0); +}; + +on.request_sync = () => { + window.parent.postMessage( + { + type: "sync", + data: { + generationFigure: generationFigure, + boardSize: boardSize, + }, + }, + "*", + ); + console.log("generationFigure:", generationFigure, "boardSize:", boardSize); +}; + +on.place_template = (template) => { + patternShape = template; + patternHeight = patternShape.length; + patternWidth = patternShape[0].length; + isPlacingTemplate = true; + table.style.cursor = "crosshair"; + stop(); +}; + +on.save_board = async () => { + window.parent.postMessage({ type: "save_board", data: board }, "*"); +}; + +on.apply_board = (newBoard) => { + // newBoardが旧形式(number[][])の場合の変換 + board = newBoard.map((row) => + row.map((cell) => { + if (typeof cell === "number") { + // 旧形式:0 = 白(死), 1 = 黒(生) + return cell === 0 ? { ...WHITE } : { ...BLACK }; + } else { + // 新形式:そのまま使用 + return cell; + } + }), + ); + renderBoard(); + generationChange(0); + stop(); + isColorMode = false; + table.style.cursor = "default"; +}; + +on.apply_color = (color) => { + // colorはRGBオブジェクト形式で受け取る + currentColor = { ...color }; + isColorMode = true; + table.style.cursor = "crosshair"; +}; diff --git a/src/lib/icons/index.ts b/src/lib/icons/index.ts index e930bc2..525e10a 100644 --- a/src/lib/icons/index.ts +++ b/src/lib/icons/index.ts @@ -10,3 +10,4 @@ export { default as reset } from "./reset.svg"; export { default as accelerate } from "./accelerate.svg"; export { default as decelerate } from "./decelerate.svg"; export { default as questionmark } from "./questionmark.svg"; +export { default as paintBrush } from "./paint-brush.svg"; diff --git a/src/lib/icons/paint-brush.svg b/src/lib/icons/paint-brush.svg new file mode 100644 index 0000000..ec24047 --- /dev/null +++ b/src/lib/icons/paint-brush.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/lib/rules-explanation.ts b/src/lib/rules-explanation.ts index bf9dcd7..aafcca4 100644 --- a/src/lib/rules-explanation.ts +++ b/src/lib/rules-explanation.ts @@ -1,5 +1,6 @@ import lifespan from "$lib/assets/life-game-rules/lifespan.js?raw"; import probabilistics from "$lib/assets/life-game-rules/probabilistics.js?raw"; +import color from "$lib/assets/life-game-rules/color.js?raw"; export type RuleExplanation = { name: { @@ -35,4 +36,15 @@ export const rulesExplanation = { }, code: probabilistics, }, + color: { + name: { + ja: "カラフル", + en: "Colorful", + }, + description: { + ja: "セルに色を設定できます", + en: "Set colors to cells", + }, + code: color, + }, }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2abb85e..ff3dfa7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -16,8 +16,8 @@ import { oneDark } from "@codemirror/theme-one-dark"; import { EditorView } from "@codemirror/view"; - let editingCode = $state(lifeGameJS); - let appliedCode = $state(lifeGameJS); + let editingCode = $state(lifeGameJS || ""); + let appliedCode = $state(lifeGameJS || ""); const previewDoc = $derived(lifeGameHTML.replace('"@JAVASCRIPT@";', `${event}\n${appliedCode}`)); @@ -35,6 +35,9 @@ const boardManager = new BoardManager(); const codeManager = new CodeManager(); + let showColorPicker = $state(false); + let selectedColor = $state("#000000"); + let disabledTemplates: { [key: string]: boolean } = $derived.by(() => { const newDisabledState: { [key: string]: boolean } = {}; for (const key in patterns) { @@ -64,7 +67,8 @@ | "save_board" | "apply_board" | "request_sync" - | "progress"; + | "progress" + | "apply_color"; type IncomingEvent = "generation_change" | "sync" | "Size shortage" | "save_board"; @@ -145,6 +149,17 @@ editingCode = rule.code; appliedCode = rule.code; } + + function applyColor(color: string) { + // 16進数カラーコード(例: #ff0000)をRGBオブジェクトに変換 + const hex = color.startsWith("#") ? color.slice(1) : color; + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + + sendEvent("apply_color", { r, g, b }); + showColorPicker = false; + } + +{#if showColorPicker} +
+

+ {isJapanese + ? "色選択可能時のみ利用できます" + : "Available only when color selection is enabled"} +

+ +
+ + +
+
+{/if} From 592078a0a1e3728d307099eedc13ac024fe755db Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:12:35 +0900 Subject: [PATCH 2/3] fix(prisma): change Board.code from Json to String for hex color support --- .../migration.sql | 2 + prisma/schema.prisma | 2 +- src/lib/assets/life-game-rules/color.js | 94 +++++++++++-------- src/routes/+page.svelte | 8 +- src/routes/api/board/+server.ts | 60 ++++++++++-- 5 files changed, 110 insertions(+), 56 deletions(-) create mode 100644 prisma/migrations/20251121071021_change_code_to_string/migration.sql diff --git a/prisma/migrations/20251121071021_change_code_to_string/migration.sql b/prisma/migrations/20251121071021_change_code_to_string/migration.sql new file mode 100644 index 0000000..d0c5d0c --- /dev/null +++ b/prisma/migrations/20251121071021_change_code_to_string/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Board" ALTER COLUMN "code" SET DATA TYPE TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e93858e..447d506 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -21,7 +21,7 @@ model Board { board Json name String preview Json - code Json + code String @db.Text } model Code { diff --git a/src/lib/assets/life-game-rules/color.js b/src/lib/assets/life-game-rules/color.js index 3bf9300..a8fcbbd 100644 --- a/src/lib/assets/life-game-rules/color.js +++ b/src/lib/assets/life-game-rules/color.js @@ -14,13 +14,30 @@ let previewCells = []; let boardSize = 20; //盤面の大きさ(20x20) const cellSize = 600 / boardSize; //セルの大きさ(px) -const WHITE = { r: 255, g: 255, b: 255 }; -const BLACK = { r: 0, g: 0, b: 0 }; -let currentColor = { ...BLACK }; +const WHITE_HEX = 0xffffff; +const BLACK_HEX = 0x000000; +let currentColorHex = BLACK_HEX; let isColorMode = false; -function isAlive(cell) { - return !(cell.r === WHITE.r && cell.g === WHITE.g && cell.b === WHITE.b); +function hexToRgb(hex) { + return { + r: (hex >> 16) & 0xff, + g: (hex >> 8) & 0xff, + b: hex & 0xff, + }; +} + +function rgbToHex(r, g, b) { + return (r << 16) | (g << 8) | b; +} + +function getStyle(hex) { + const rgb = hexToRgb(hex); + return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`; +} + +function isAlive(hex) { + return hex !== WHITE_HEX; } // around: 周囲の生きたセル数 self: 自身が生きているかどうか @@ -36,11 +53,6 @@ function isNextAlive(around, self) { return 0; } -// cellの状態に応じた色を返す関数 -function getStyle(cell) { - return "rgb(" + cell.r + ", " + cell.g + ", " + cell.b + ")"; -} - // 周囲の色の平均を取得する関数 function getAverageColor(i, j) { let totalR = 0, @@ -54,28 +66,29 @@ function getAverageColor(i, j) { const ni = i + di; const nj = j + dj; if (ni >= 0 && ni < boardSize && nj >= 0 && nj < boardSize && isAlive(board[ni][nj])) { - totalR += board[ni][nj].r; - totalG += board[ni][nj].g; - totalB += board[ni][nj].b; + const rgb = hexToRgb(board[ni][nj]); + totalR += rgb.r; + totalG += rgb.g; + totalB += rgb.b; count++; } } } if (count > 0) { - return { - r: Math.round(totalR / count), - g: Math.round(totalG / count), - b: Math.round(totalB / count), - }; + return rgbToHex( + Math.round(totalR / count), + Math.round(totalG / count), + Math.round(totalB / count), + ); } - return isColorMode ? { ...currentColor } : { ...BLACK }; + return isColorMode ? currentColorHex : BLACK_HEX; } //Boardの初期化 let board = Array.from({ length: boardSize }, () => - Array.from({ length: boardSize }, () => ({ ...WHITE })), + Array.from({ length: boardSize }, () => WHITE_HEX), ); const table = document.getElementById("game-board"); @@ -120,8 +133,7 @@ function renderBoard() { for (let c = 0; c < patternWidth; c++) { const boardRow = i + r; const boardCol = j + c; - board[boardRow][boardCol] = - patternShape[r][c] === 1 ? { ...currentColor } : { ...WHITE }; + board[boardRow][boardCol] = patternShape[r][c] === 1 ? currentColorHex : WHITE_HEX; } } rerender(); @@ -137,7 +149,7 @@ function renderBoard() { } } else if (isColorMode && isAlive(board[i][j])) { // 色選択モード:生きているセルに色を適用 - board[i][j] = { ...currentColor }; + board[i][j] = currentColorHex; button.style.backgroundColor = getStyle(board[i][j]); } }; @@ -146,10 +158,10 @@ function renderBoard() { if (timer === "stop" && !isPlacingTemplate) { isDragging = true; if (isAlive(board[i][j])) { - board[i][j] = { ...WHITE }; + board[i][j] = WHITE_HEX; dragMode = 0; // 白にする } else { - board[i][j] = { ...currentColor }; + board[i][j] = currentColorHex; dragMode = 1; // 指定した色にする } button.style.backgroundColor = getStyle(board[i][j]); @@ -158,9 +170,9 @@ function renderBoard() { button.onmouseenter = () => { if (isDragging && timer === "stop" && !isPlacingTemplate) { if (dragMode === 1 && !isAlive(board[i][j])) { - board[i][j] = { ...currentColor }; + board[i][j] = currentColorHex; } else if (dragMode === 0 && isAlive(board[i][j])) { - board[i][j] = { ...WHITE }; + board[i][j] = WHITE_HEX; } button.style.backgroundColor = getStyle(board[i][j]); } @@ -249,7 +261,7 @@ function generationChange(num) { } function progressBoard() { - const newBoard = board.map((row) => row.map((cell) => ({ ...cell }))); + const newBoard = board.map((row) => [...row]); for (let i = 0; i < boardSize; i++) { for (let j = 0; j < boardSize; j++) { //周囲のマスに黒マスが何個あるかを計算(aroundに格納)↓ @@ -286,10 +298,10 @@ function progressBoard() { newBoard[i][j] = getAverageColor(i, j); } else if (nextAlive) { // 生き続けるセル:色を保持 - newBoard[i][j] = { ...board[i][j] }; + newBoard[i][j] = board[i][j]; } else { // 死ぬセル:白にする - newBoard[i][j] = { ...WHITE }; + newBoard[i][j] = WHITE_HEX; } } } @@ -315,7 +327,7 @@ on.pause = () => { on.board_reset = () => { //すべて白にBoardを変更 board = Array.from({ length: boardSize }, () => - Array.from({ length: boardSize }, () => ({ ...WHITE })), + Array.from({ length: boardSize }, () => WHITE_HEX), ); renderBoard(); generationChange(0); @@ -328,13 +340,13 @@ on.board_randomize = () => { board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => { if (Math.random() > 0.5) { - return { - r: Math.floor(Math.random() * 256), - g: Math.floor(Math.random() * 256), - b: Math.floor(Math.random() * 256), - }; + return hexToRgb( + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + Math.floor(Math.random() * 256), + ); } else { - return { ...WHITE }; + return WHITE_HEX; } }), ); @@ -375,7 +387,7 @@ on.apply_board = (newBoard) => { row.map((cell) => { if (typeof cell === "number") { // 旧形式:0 = 白(死), 1 = 黒(生) - return cell === 0 ? { ...WHITE } : { ...BLACK }; + return cell === 0 ? WHITE_HEX : BLACK_HEX; } else { // 新形式:そのまま使用 return cell; @@ -389,9 +401,9 @@ on.apply_board = (newBoard) => { table.style.cursor = "default"; }; -on.apply_color = (color) => { - // colorはRGBオブジェクト形式で受け取る - currentColor = { ...color }; +on.apply_color = (hexValue) => { + // 16進数の数値を直接受け取る + currentColorHex = hexValue; isColorMode = true; table.style.cursor = "crosshair"; }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index ff3dfa7..dec6c1a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -151,13 +151,11 @@ } function applyColor(color: string) { - // 16進数カラーコード(例: #ff0000)をRGBオブジェクトに変換 + // 16進数文字列を数値に変換して送信 const hex = color.startsWith("#") ? color.slice(1) : color; - const r = parseInt(hex.slice(0, 2), 16); - const g = parseInt(hex.slice(2, 4), 16); - const b = parseInt(hex.slice(4, 6), 16); + const hexValue = parseInt(hex, 16); - sendEvent("apply_color", { r, g, b }); + sendEvent("apply_color", hexValue); showColorPicker = false; } diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts index 6176f5e..729f7d0 100644 --- a/src/routes/api/board/+server.ts +++ b/src/routes/api/board/+server.ts @@ -18,25 +18,67 @@ export async function POST({ request }) { return json({ message: "無効なリクエスト形式です。" }, { status: 400 }); } + // ========== デバッグコード ここから ========== + console.log("=== DEBUG INFO ==="); + console.log("Request data keys:", Object.keys(requestData as object)); + console.log("Board type:", typeof (requestData as any).board); + console.log("Board is array:", Array.isArray((requestData as any).board)); + if (Array.isArray((requestData as any).board) && (requestData as any).board.length > 0) { + console.log("Board first row:", (requestData as any).board[0]); + console.log("Board sample cell:", (requestData as any).board[0]?.[0]); + console.log("Board sample cell type:", typeof (requestData as any).board[0]?.[0]); + } + console.log("Code type:", typeof (requestData as any).code); + console.log("Code length:", (requestData as any).code?.length); + console.log("Name:", (requestData as any).name); + console.log("=================="); + // ========== デバッグコード ここまで ========== + const result = v.safeParse(BoardSchema, requestData); if (!result.success) { console.error("Request validation failed:", result.issues); + // より詳細なエラーログ + result.issues.forEach((issue, idx) => { + console.error(`Issue ${idx}:`, { + path: issue.path, + message: issue.message, + expected: issue.expected, + received: issue.received, + }); + }); return json({ message: "無効なリクエストデータです。" }, { status: 400 }); } const { board, name, code } = result.output; const preview = createBoardPreview(board); - const newState = await prisma.board.create({ - data: { - board: board, - name: name, - preview: preview, - code: code, - }, - }); + // Prisma保存前のログ + console.log("Attempting to save to DB..."); + console.log("Board dimensions:", board.length, "x", board[0]?.length); + console.log("Preview dimensions:", preview.length, "x", preview[0]?.length); - return json({ id: newState.id }, { status: 201 }); + try { + const newState = await prisma.board.create({ + data: { + board: board, + name: name, + preview: preview, + code: code, + }, + }); + + console.log("✅ Save successful! ID:", newState.id); + return json({ id: newState.id }, { status: 201 }); + } catch (dbError) { + console.error("❌ Database save failed:", dbError); + // エラーの詳細を出力 + if (dbError instanceof Error) { + console.error("Error name:", dbError.name); + console.error("Error message:", dbError.message); + console.error("Error stack:", dbError.stack); + } + return json({ message: "データベースエラーが発生しました。" }, { status: 500 }); + } } export async function GET({ url }) { From 4afef80a851b85c52d392b882e147328620b1f53 Mon Sep 17 00:00:00 2001 From: kg0816 <211191696+kg0816@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:17:52 +0900 Subject: [PATCH 3/3] fix lint --- src/routes/api/board/+server.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts index 729f7d0..7bc73d4 100644 --- a/src/routes/api/board/+server.ts +++ b/src/routes/api/board/+server.ts @@ -18,22 +18,6 @@ export async function POST({ request }) { return json({ message: "無効なリクエスト形式です。" }, { status: 400 }); } - // ========== デバッグコード ここから ========== - console.log("=== DEBUG INFO ==="); - console.log("Request data keys:", Object.keys(requestData as object)); - console.log("Board type:", typeof (requestData as any).board); - console.log("Board is array:", Array.isArray((requestData as any).board)); - if (Array.isArray((requestData as any).board) && (requestData as any).board.length > 0) { - console.log("Board first row:", (requestData as any).board[0]); - console.log("Board sample cell:", (requestData as any).board[0]?.[0]); - console.log("Board sample cell type:", typeof (requestData as any).board[0]?.[0]); - } - console.log("Code type:", typeof (requestData as any).code); - console.log("Code length:", (requestData as any).code?.length); - console.log("Name:", (requestData as any).name); - console.log("=================="); - // ========== デバッグコード ここまで ========== - const result = v.safeParse(BoardSchema, requestData); if (!result.success) { console.error("Request validation failed:", result.issues);