diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 23e942b..a1cadfd 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -1,11 +1,14 @@ "use strict"; let timer = "stop"; -let timerId = 0; let generationFigure = 0; -let timerTime = 1000; let isDragging = false; let dragMode = false; // true: 黒にする, false: 白にする +let isPlacingTemplate = false; +let patternShape = []; +let patternHeight = 0; +let patternWidth = 0; +let previewCells = []; //変数設定 let boardSize = 20; //盤面の大きさ(20x20) @@ -51,9 +54,35 @@ function renderBoard() { 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; + } + } + rerender(); + generationChange(0); + stop(); + } else { + window.parent.postMessage( + { + type: "Size shortage", + data: {}, + }, + "*", + ); + } + } + }; button.onmousedown = (e) => { e.preventDefault(); - if (timer === "stop") { + if (timer === "stop" && !isPlacingTemplate) { isDragging = true; board[i][j] = !board[i][j]; dragMode = board[i][j]; @@ -61,10 +90,13 @@ function renderBoard() { } }; button.onmouseenter = () => { - if (isDragging && timer === "stop" && board[i][j] !== dragMode) { + if (isDragging && timer === "stop" && board[i][j] !== dragMode && !isPlacingTemplate) { board[i][j] = dragMode; button.style.backgroundColor = board[i][j] ? "black" : "white"; } + if (isPlacingTemplate) { + drawPreview(i, j); + } }; td.appendChild(button); tr.appendChild(td); @@ -73,6 +105,37 @@ function renderBoard() { } } +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 = board[cellPos.row][cellPos.col] ? "black" : "white"; + }); + previewCells = []; +} + function rerender() { // 2回目以降の盤面生成 for (let i = 0; i < boardSize; i++) { @@ -158,22 +221,18 @@ function progressBoard() { rerender(); } -function resetTimer() { - if (timer !== "stop") { - timer = "stop"; - clearInterval(timerId); - } -} - //イベント +on.progress = () => { + progressBoard(); +}; + on.play = () => { timer = "start"; - timerId = setInterval(progressBoard, timerTime); }; on.pause = () => { - resetTimer(); + timer = "stop"; }; on.board_reset = () => { @@ -181,7 +240,6 @@ on.board_reset = () => { board = Array.from({ length: boardSize }, () => Array.from({ length: boardSize }, () => false)); renderBoard(); generationChange(0); - resetTimer(); }; on.board_randomize = () => { @@ -191,15 +249,6 @@ on.board_randomize = () => { ); renderBoard(); generationChange(0); - resetTimer(); -}; - -on.timer_change = (ms) => { - timerTime = ms; - if (timer === "start") { - clearInterval(timerId); - timerId = setInterval(progressBoard, timerTime); - } }; on.request_sync = () => { @@ -216,11 +265,12 @@ on.request_sync = () => { console.log("generationFigure:", generationFigure, "boardSize:", boardSize); }; -on.place_template = (newBoard) => { - board = newBoard; - renderBoard(); - generationChange(0); - resetTimer(); +on.place_template = (template) => { + patternShape = template; + patternHeight = patternShape.length; + patternWidth = patternShape[0].length; + isPlacingTemplate = true; + table.style.cursor = "crosshair"; stop(); }; @@ -233,6 +283,5 @@ on.apply_board = (newBoard) => { board = newBoard; renderBoard(); generationChange(0); - resetTimer(); stop(); }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e8b90ee..d8931bb 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -20,10 +20,19 @@ let resetModalOpen = $state(false); let bottomDrawerOpen = $state(false); - let intervalMs = $state(1000); let generationFigure = $state(0); let sizeValue = $state(20); + let timer: "running" | "stopped" = $state("stopped"); + let intervalMs = $state(1000); + $effect(() => { + if (timer === "stopped") return; + const timerId = setInterval(() => { + sendEvent("progress"); + }, intervalMs); + return () => clearInterval(timerId); + }); + type SaveState = | { saving: false } | { saving: true; boardData: boolean[][]; boardName: string; boardPreview: boolean[][] }; @@ -39,15 +48,15 @@ | "play" | "pause" | "state_update" - | "timer_change" | "board_reset" | "board_randomize" | "place_template" | "save_board" | "apply_board" - | "request_sync"; + | "request_sync" + | "progress"; - type IncomingEvent = "generation_change" | "sync" | "save_board"; + type IncomingEvent = "generation_change" | "sync" | "Size shortage" | "save_board"; function handleMessage(event: MessageEvent<{ type: IncomingEvent; data: unknown }>) { switch (event.data.type) { @@ -61,6 +70,14 @@ sizeValue = data.boardSize; break; } + case "Size shortage": { + alert( + isJapanese + ? "盤面からはみ出してしまうため、キャンセルしました" + : "This action was canceled because it would have extended beyond the board.", + ); + break; + } case "save_board": { const board = event.data.data as boolean[][]; const preview = createPreview(board); @@ -168,13 +185,8 @@ onclick={() => { sendEvent("request_sync"); - const newBoard = Array.from({ length: sizeValue }, () => - Array.from({ length: sizeValue }, () => false), - ); const patternData = patterns[patternName]; const patternShape = patternData.shape; - const patternHeight = patternShape.length; - const patternWidth = patternShape[0].length; if (sizeValue < (patternData.minBoardSize || 0)) { if (isJapanese) { @@ -189,19 +201,8 @@ return; } - // パターンがボードの中央に来るよう、パターンの左上のセルの位置(startRow, startCol)を調整 - const startRow = Math.floor((sizeValue - patternHeight) / 2); - const startCol = Math.floor((sizeValue - patternWidth) / 2); - - for (let r = 0; r < patternHeight; r++) { - for (let c = 0; c < patternWidth; c++) { - const boardRow = startRow + r; - const boardCol = startCol + c; - newBoard[boardRow][boardCol] = patternShape[r][c] === 1; - } - } bottomDrawerOpen = false; - sendEvent("place_template", newBoard); + sendEvent("place_template", patternShape); }} > { intervalMs = intervalMs * 2; - sendEvent("timer_change", intervalMs); }} > decelerate @@ -425,7 +425,6 @@ class="btn btn-ghost btn-circle text-black hover:bg-[rgb(220,220,220)]" onclick={() => { intervalMs = 1000; - sendEvent("timer_change", intervalMs); }} > x1 @@ -435,7 +434,6 @@ class="btn btn-ghost btn-circle hover:bg-[rgb(220,220,220)]" onclick={() => { intervalMs = intervalMs / 2; - sendEvent("timer_change", intervalMs); }} > accelerate @@ -455,8 +453,13 @@