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);
}}
>
@@ -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);
}}
>
@@ -455,8 +453,13 @@