From 539357ccb77b2e50744184d6fb4f8b0c8f5c7983 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Sat, 3 May 2025 21:53:58 +0900 Subject: [PATCH 01/17] =?UTF-8?q?=E5=88=9D=E6=9C=9F=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/constants.ts | 3 +++ src/context.ts | 4 +++ src/grid.ts | 11 ++++++-- src/main.ts | 6 +++-- src/player.ts | 6 ++--- src/stages.ts | 69 +++++++++++++++++++++++++++++------------------- 6 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index ec8c0b3..142e4f6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,3 +15,6 @@ export enum Facing { left = "left", right = "right", } +export type MovableBlock = { + block: Block.movable; +}; diff --git a/src/context.ts b/src/context.ts index b815403..3071e6b 100644 --- a/src/context.ts +++ b/src/context.ts @@ -10,6 +10,10 @@ export type Context = { grid: Grid; blockSize: number; + // about player + initialPlayerX: number; // initial player position in X direction + initialPlayerY: number; // initial player position in Y direction + // about time elapsed: number; }; diff --git a/src/grid.ts b/src/grid.ts index de03b0a..6121429 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -24,8 +24,8 @@ export class Grid { ) { this.stage = stage; const cells: GridCell[][] = []; - for (let y = 0; y < stageDefinition.length; y++) { - const rowDefinition = stageDefinition[y].split(""); + for (let y = 0; y < stageDefinition.stage.length; y++) { + const rowDefinition = stageDefinition.stage[y].split(""); const row: GridCell[] = []; for (let x = 0; x < rowDefinition.length; x++) { const cellDef = rowDefinition[x]; @@ -59,6 +59,13 @@ export class Grid { getBlock(x: number, y: number): Block | undefined { return this.cells[y]?.[x]?.block; } + getMovableBlock(x: number, y: number): Block.movable | undefined { + const cell = this.cells[y]?.[x]; + if (!cell) return undefined; + if (cell.block === Block.movable) { + return cell.block; + } + } setBlock(cx: Context, x: number, y: number, block: Block) { const prev = this.cells[y][x]; if (block === prev.block) return; diff --git a/src/main.ts b/src/main.ts index 7c81512..67caefb 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,8 +24,8 @@ export async function setup(el: HTMLElement, stageDefinition: StageDefinition) { const stage = new Container(); app.stage.addChild(stage); - const gridX = stageDefinition[0].length; - const gridY = stageDefinition.length; + const gridX = stageDefinition.stage[0].length; + const gridY = stageDefinition.stage.length; // Initialize the application await app.init({ background: "white", resizeTo: window }); @@ -41,6 +41,8 @@ export async function setup(el: HTMLElement, stageDefinition: StageDefinition) { gridX, gridY, blockSize, + initialPlayerX: stageDefinition.initialPlayerX, + initialPlayerY: stageDefinition.initialPlayerY, grid, elapsed: 0, }; diff --git a/src/player.ts b/src/player.ts index 7093bc0..a3f5017 100644 --- a/src/player.ts +++ b/src/player.ts @@ -30,9 +30,9 @@ export class Player { this.sprite = new Sprite(spriteOptions); // Center the sprite's anchor point this.sprite.anchor.set(0.5, 1); - // todo: 初期座標をフィールドとともにどこかで決定 - this.sprite.x = 2 * cx.blockSize; - this.sprite.y = 2 * cx.blockSize; + + this.sprite.x = cx.blockSize * cx.initialPlayerX; + this.sprite.y = cx.blockSize * (cx.gridY - cx.initialPlayerY); this.sprite.width = c.playerWidth * cx.blockSize; this.sprite.height = c.playerHeight * cx.blockSize; diff --git a/src/stages.ts b/src/stages.ts index 69473bf..c1582f9 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -1,38 +1,53 @@ -export type StageDefinition = string[]; +export type StageDefinition = { + stage: string[]; + initialPlayerX: number; + initialPlayerY: number; +}; export const stages = new Map([ [ "1", - [ - "bbbbbbbbbbbbbbbbbb", - ".........b........", - ".........b........", - ".........b...bbbbb", - ".........m...bbbbb", - "bbbbbbbbbbbbbbbbbb", - ], + { + stage: [ + "bbbbbbbbbbbbbbbbbb", + ".........b........", + ".........b........", + ".........b...bbbbb", + ".........m...bbbbb", + "bbbbbbbbbbbbbbbbbb", + ], + initialPlayerX: 1, + initialPlayerY: 1, + }, ], [ "2", - [ - "bbbbbbbbbbbbbbbbbb", - "..................", - "..................", - "m............bbbbb", - "bb...........bbbbb", - "bb.....m.....bbbbb", - "bbbbbbbbbbbbbbbbbb", - ], + { + stage: [ + "bbbbbbbbbbbbbbbbbb", + ".........b........", + ".........b........", + ".........b...bbbbb", + ".........m...bbbbb", + "bbbbbbbbbbbbbbbbbb", + ], + initialPlayerX: 1, + initialPlayerY: 1, + }, ], [ "3", - [ - "bbbbbbbbbbbbbbbbbb", - "...b..............", - "...b.....m.....bbb", - "...bm..........bbb", - "...bbb.........bbb", - "...mm..........bbb", - "bbbbbbbbbbb....bbb", - ], + { + stage: [ + "bbbbbbbbbbbbbbbbbb", + "...b..............", + "...b.....m.....bbb", + "...bm..........bbb", + "...bbb.........bbb", + "...mm..........bbb", + "bbbbbbbbbbb....bbb", + ], + initialPlayerX: 1, + initialPlayerY: 1, + }, ], ]); From 7732e4f6941043d7ed9ea6821a9b67c2ec048dbc Mon Sep 17 00:00:00 2001 From: nakomochi Date: Sun, 4 May 2025 12:54:31 +0900 Subject: [PATCH 02/17] =?UTF-8?q?=E8=A4=87=E6=95=B0=E3=83=96=E3=83=AD?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=81=8B=E3=82=89=E3=81=AA=E3=82=8B=E3=82=AA?= =?UTF-8?q?=E3=83=96=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=81=AEcut,paste?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 65 ++++++++++++++++++------ src/grid.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++++--- src/stages.ts | 75 ++++++++++++++++++++++++++-- 3 files changed, 243 insertions(+), 28 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index c7a698f..15ab343 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -1,5 +1,6 @@ -import { Block, Facing } from "./constants.ts"; +import { Block, Facing, type MovableBlock } from "./constants.ts"; import type { Context } from "./context.ts"; +import type { MovableBlocks, MovableObject } from "./grid.ts"; export type Coords = { x: number; @@ -15,17 +16,22 @@ export type AbilityEnableOptions = { }; type History = { at: { x: number; y: number }; - from: Block; - to: Block; + from: MovableObject | Block; + to: MovableObject | Block; inventory: { - before: Block | null; - after: Block | null; + before: MovableObject | null; + after: MovableObject | null; }; }; + +function isMovableObject(obj: MovableObject | Block): obj is MovableObject { + return (obj as MovableObject).objectId !== undefined; +} + export class AbilityControl { history: History[] = []; historyIndex = 0; - inventory: Block | null = null; + inventory: MovableObject | null = null; inventoryIsInfinite = false; enabled: AbilityEnableOptions; focused: Coords | undefined; @@ -56,17 +62,25 @@ export class AbilityControl { } copy(cx: Context) { if (!this.focused) return; - const target = cx.grid.getBlock(this.focused.x, this.focused.y); + const x = this.focused.x; + const y = this.focused.y; + const target = cx.grid.getBlock(x, y); if (!target || target !== Block.movable) return; - this.inventory = target; + const movableObject = cx.grid.getMovableObject(x, y); + if (!movableObject) return; + this.inventory = movableObject; } paste(cx: Context) { if (!this.focused) return; - if (!this.inventory || this.inventory === Block.air) return; + if (!this.inventory /*|| this.inventory === Block.air*/) return; + const x = this.focused.x; + const y = this.focused.y; const target = cx.grid.getBlock(this.focused.x, this.focused.y); if (!target || target !== Block.air) return; const prevInventory = this.inventory; - cx.grid.setBlock(cx, this.focused.x, this.focused.y, this.inventory); + + cx.grid.setMovableObject(cx, x, y, this.inventory); + if (!this.inventoryIsInfinite) { this.inventory = null; } @@ -83,12 +97,23 @@ export class AbilityControl { } cut(cx: Context) { if (!this.focused) return; - const target = cx.grid.getBlock(this.focused.x, this.focused.y); + const x = this.focused.x; + const y = this.focused.y; + const target = cx.grid.getBlock(x, y); // removable 以外はカットできない if (!target || target !== Block.movable) return; + const movableObject = cx.grid.getMovableObject(x, y); + if (!movableObject) return; const prevInventory = this.inventory; - this.inventory = target; - cx.grid.setBlock(cx, this.focused.x, this.focused.y, Block.air); + this.inventory = movableObject; + + for (const i of movableObject.relativePositions) { + const positionX = movableObject.x + i.x; + const positionY = movableObject.y + i.y; + cx.grid.setBlock(cx, positionX, positionY, Block.air); + } + + // cx.grid.setBlock(cx, this.focused.x, this.focused.y, Block.air); this.pushHistory({ at: { ...this.focused }, @@ -96,7 +121,7 @@ export class AbilityControl { to: Block.air, inventory: { before: prevInventory, - after: target, + after: movableObject, }, }); } @@ -112,7 +137,11 @@ export class AbilityControl { if (this.historyIndex <= 0) return; this.historyIndex--; // undo は、巻き戻し後の index で計算する const op = this.history[this.historyIndex]; - cx.grid.setBlock(cx, op.at.x, op.at.y, op.from); + if (!isMovableObject(op.from)) { + cx.grid.setBlock(cx, op.at.x, op.at.y, op.from); + } else { + cx.grid.setMovableObject(cx, op.at.x, op.at.y, op.from); + } this.inventory = op.inventory.before; console.log(`history: ${this.historyIndex} / ${this.history.length}`); } @@ -121,7 +150,11 @@ export class AbilityControl { const op = this.history[this.historyIndex]; this.historyIndex++; // redo は、巻き戻し前の index this.inventory = op.inventory.after; - cx.grid.setBlock(cx, op.at.x, op.at.y, op.to); + if (!isMovableObject(op.to)) { + cx.grid.setBlock(cx, op.at.x, op.at.y, op.to); + } else { + cx.grid.setMovableObject(cx, op.at.x, op.at.y, op.to); + } console.log(`history: ${this.historyIndex} / ${this.history.length}`); } handleKeyDown(cx: Context, e: KeyboardEvent, onGround: boolean) { diff --git a/src/grid.ts b/src/grid.ts index 6121429..5a9eb34 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -6,23 +6,53 @@ import type { StageDefinition } from "./stages.ts"; type GridCell = | { - block: Block.block | Block.movable; + block: Block.block; sprite: Sprite; } + | { + block: Block.movable; + sprite: Sprite; + objectId: number; + relativePosition: { + x: number; + y: number; + }; + } | { block: Block.air; sprite: null; }; +export type MovableBlocks = { + x: number; + y: number; + objectId: number; + // 基準ブロックからの相対位置 + relativeX: number; + relativeY: number; +}[]; + +export type MovableObject = { + objectId: number; + x: number; + y: number; + relativePositions: { + x: number; + y: number; + }[]; +}; + export class Grid { private stage: Container; cells: GridCell[][]; + movableBlocks: MovableBlocks; constructor( stage: Container, cellSize: number, stageDefinition: StageDefinition, ) { this.stage = stage; + this.movableBlocks = stageDefinition.movableBlocks; const cells: GridCell[][] = []; for (let y = 0; y < stageDefinition.stage.length; y++) { const rowDefinition = stageDefinition.stage[y].split(""); @@ -36,6 +66,23 @@ export class Grid { sprite: null, }; row.push(cell); + } else if (block === Block.movable) { + const sprite = createSprite(cellSize, block, x, y); + stage.addChild(sprite); + const movableBlock = stageDefinition.movableBlocks.filter( + (block) => block.x === x && block.y === y, + )[0]; + const cell: GridCell = { + block, + sprite, + objectId: movableBlock.objectId, + // 同グループの基準ブロックに対する相対位置 + relativePosition: { + x: movableBlock.relativeX, + y: movableBlock.relativeY, + }, + }; + row.push(cell); } else { const sprite = createSprite(cellSize, block, x, y); stage.addChild(sprite); @@ -59,13 +106,39 @@ export class Grid { getBlock(x: number, y: number): Block | undefined { return this.cells[y]?.[x]?.block; } - getMovableBlock(x: number, y: number): Block.movable | undefined { + getMovableObject(x: number, y: number): MovableObject | undefined { const cell = this.cells[y]?.[x]; if (!cell) return undefined; if (cell.block === Block.movable) { - return cell.block; + const objectId = cell.objectId; + const retrievedBlocks = this.movableBlocks.filter( + (block) => block.objectId === objectId, + ); + const retrievedObject: MovableObject = { + objectId, + x: retrievedBlocks.filter( + (block) => block.relativeX === 0 && block.relativeY === 0, + )[0].x, + y: retrievedBlocks.filter( + (block) => block.relativeX === 0 && block.relativeY === 0, + )[0].y, + relativePositions: retrievedBlocks.map((block) => ({ + x: block.relativeX, + y: block.relativeY, + })), + }; + + console.log(retrievedObject); + + return retrievedObject; } } + // setAirBlock(cx: Context, x: number, y: number) { + // this.cells[y][x] = { + // block: Block.air, + // sprite: null, + // } + // } setBlock(cx: Context, x: number, y: number, block: Block) { const prev = this.cells[y][x]; if (block === prev.block) return; @@ -77,14 +150,58 @@ export class Grid { block, sprite: null, }; - } else { - const sprite = createSprite(cx.blockSize, block, x, y); + } + // else { + // const sprite = createSprite(cx.blockSize, block, x, y); + // this.stage.addChild(sprite); + // this.cells[y][x] = { + // block, + // sprite, + // }; + // } + } + setMovableObject(cx: Context, x: number, y: number, object: MovableObject) { + console.log("1"); + console.log(object.relativePositions); + console.log(object, object); + const prev = this.cells[y][x]; + if (prev.block !== Block.air) { + this.stage.removeChild(prev.sprite); + } + for (const i of object.relativePositions) { + console.log(i); + const positionX = x + i.x; + const positionY = y + i.y; + const sprite = createSprite( + cx.blockSize, + Block.movable, + positionX, + positionY, + ); this.stage.addChild(sprite); - this.cells[y][x] = { - block, + this.cells[positionY][positionX] = { + block: Block.movable, sprite, + objectId: object.objectId, + relativePosition: { + x: i.x, + y: i.y, + }, }; } + // オブジェクトの座標を更新 + object.x = x; + object.y = y; + this.movableBlocks = this.movableBlocks.map((block) => { + if (block.objectId === object.objectId) { + return { + ...block, + x, + y, + }; + } + return block; + }); } } diff --git a/src/stages.ts b/src/stages.ts index c1582f9..4c4cf4a 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -2,7 +2,16 @@ export type StageDefinition = { stage: string[]; initialPlayerX: number; initialPlayerY: number; + movableBlocks: { + x: number; + y: number; + objectId: number; + // 基準ブロックからの相対位置 + relativeX: number; + relativeY: number; + }[]; }; + export const stages = new Map([ [ "1", @@ -17,6 +26,15 @@ export const stages = new Map([ ], initialPlayerX: 1, initialPlayerY: 1, + movableBlocks: [ + { + x: 9, + y: 4, + objectId: 1, + relativeX: 0, + relativeY: 0, + }, + ], }, ], [ @@ -24,14 +42,31 @@ export const stages = new Map([ { stage: [ "bbbbbbbbbbbbbbbbbb", - ".........b........", - ".........b........", - ".........b...bbbbb", - ".........m...bbbbb", + "..................", + "..................", + "m............bbbbb", + "bb...........bbbbb", + "bb.....m.....bbbbb", "bbbbbbbbbbbbbbbbbb", ], initialPlayerX: 1, initialPlayerY: 1, + movableBlocks: [ + { + x: 0, + y: 3, + objectId: 1, + relativeX: 0, + relativeY: 0, + }, + { + x: 7, + y: 5, + objectId: 2, + relativeX: 0, + relativeY: 0, + }, + ], }, ], [ @@ -40,7 +75,7 @@ export const stages = new Map([ stage: [ "bbbbbbbbbbbbbbbbbb", "...b..............", - "...b.....m.....bbb", + "...b....m......bbb", "...bm..........bbb", "...bbb.........bbb", "...mm..........bbb", @@ -48,6 +83,36 @@ export const stages = new Map([ ], initialPlayerX: 1, initialPlayerY: 1, + movableBlocks: [ + { + x: 3, + y: 5, + objectId: 1, + relativeX: 0, + relativeY: 0, + }, + { + x: 4, + y: 5, + objectId: 1, + relativeX: 1, + relativeY: 0, + }, + { + x: 4, + y: 3, + objectId: 2, + relativeX: 0, + relativeY: 0, + }, + { + x: 8, + y: 2, + objectId: 3, + relativeX: 0, + relativeY: 0, + }, + ], }, ], ]); From 2476c10dcad56ecba89913ddc75e7ae6dfce4a76 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Sun, 4 May 2025 13:38:11 +0900 Subject: [PATCH 03/17] =?UTF-8?q?=E5=B7=A6=E3=82=92=E5=90=91=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E8=B2=BC=E3=82=8A=E4=BB=98?= =?UTF-8?q?=E3=81=91=E4=BD=8D=E7=BD=AE=E3=82=92=E8=AA=BF=E6=95=B4=E3=83=BB?= =?UTF-8?q?=E8=B2=BC=E3=82=8A=E4=BB=98=E3=81=91=E5=8F=AF=E5=90=A6=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 38 ++++++++++++++++++++++++++++++++------ src/player.ts | 2 +- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 15ab343..22ff70d 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -70,13 +70,34 @@ export class AbilityControl { if (!movableObject) return; this.inventory = movableObject; } - paste(cx: Context) { + paste(cx: Context, facing: Facing) { if (!this.focused) return; if (!this.inventory /*|| this.inventory === Block.air*/) return; - const x = this.focused.x; + const width = + this.inventory.relativePositions.reduce( + (acc, i) => Math.max(acc, i.x), + 0, + ) - + this.inventory.relativePositions.reduce( + (acc, i) => Math.min(acc, i.x), + 1000, + ) + + 1; + + const x = this.focused.x - (facing === Facing.left ? width - 1 : 0); const y = this.focused.y; - const target = cx.grid.getBlock(this.focused.x, this.focused.y); - if (!target || target !== Block.air) return; + + for (const i of this.inventory.relativePositions) { + const positionX = x + i.x; + const positionY = y + i.y; + const target = cx.grid.getBlock(positionX, positionY); + if (target !== Block.air) { + // すでに何かある場合は、ペーストできない + return; + } + } + // const target = cx.grid.getBlock(this.focused.x, this.focused.y); + // if (!target || target !== Block.air) return; const prevInventory = this.inventory; cx.grid.setMovableObject(cx, x, y, this.inventory); @@ -157,11 +178,16 @@ export class AbilityControl { } console.log(`history: ${this.historyIndex} / ${this.history.length}`); } - handleKeyDown(cx: Context, e: KeyboardEvent, onGround: boolean) { + handleKeyDown( + cx: Context, + e: KeyboardEvent, + onGround: boolean, + facing: Facing, + ) { if (!(e.ctrlKey || e.metaKey)) return; if (this.enabled.paste && onGround && e.key === "v") { - this.paste(cx); + this.paste(cx, facing); } if (this.enabled.copy && onGround && e.key === "c") { this.copy(cx); diff --git a/src/player.ts b/src/player.ts index a3f5017..da7c03e 100644 --- a/src/player.ts +++ b/src/player.ts @@ -84,7 +84,7 @@ export class Player { } handleInput(cx: Context, event: KeyboardEvent, eventIsKeyDown: boolean) { if (eventIsKeyDown) { - this.ability.handleKeyDown(cx, event, this.onGround); + this.ability.handleKeyDown(cx, event, this.onGround, this.facing); } switch (event.key) { case "Control": From 8074e8079d95628a7b4a37dfe81be9ac66c6badc Mon Sep 17 00:00:00 2001 From: nakomochi Date: Sun, 4 May 2025 14:09:06 +0900 Subject: [PATCH 04/17] =?UTF-8?q?=E3=83=97=E3=83=AC=E3=82=A4=E3=83=A4?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E5=88=9D=E6=9C=9F=E4=BD=8D=E7=BD=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/grid.ts | 14 -------------- src/player.ts | 2 +- src/stages.ts | 8 ++++---- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/grid.ts b/src/grid.ts index 5a9eb34..fa2169f 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -133,12 +133,6 @@ export class Grid { return retrievedObject; } } - // setAirBlock(cx: Context, x: number, y: number) { - // this.cells[y][x] = { - // block: Block.air, - // sprite: null, - // } - // } setBlock(cx: Context, x: number, y: number, block: Block) { const prev = this.cells[y][x]; if (block === prev.block) return; @@ -151,14 +145,6 @@ export class Grid { sprite: null, }; } - // else { - // const sprite = createSprite(cx.blockSize, block, x, y); - // this.stage.addChild(sprite); - // this.cells[y][x] = { - // block, - // sprite, - // }; - // } } setMovableObject(cx: Context, x: number, y: number, object: MovableObject) { console.log("1"); diff --git a/src/player.ts b/src/player.ts index da7c03e..8c41fef 100644 --- a/src/player.ts +++ b/src/player.ts @@ -32,7 +32,7 @@ export class Player { this.sprite.anchor.set(0.5, 1); this.sprite.x = cx.blockSize * cx.initialPlayerX; - this.sprite.y = cx.blockSize * (cx.gridY - cx.initialPlayerY); + this.sprite.y = cx.blockSize * cx.initialPlayerY; this.sprite.width = c.playerWidth * cx.blockSize; this.sprite.height = c.playerHeight * cx.blockSize; diff --git a/src/stages.ts b/src/stages.ts index 4c4cf4a..54541a9 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -25,7 +25,7 @@ export const stages = new Map([ "bbbbbbbbbbbbbbbbbb", ], initialPlayerX: 1, - initialPlayerY: 1, + initialPlayerY: 5, movableBlocks: [ { x: 9, @@ -49,8 +49,8 @@ export const stages = new Map([ "bb.....m.....bbbbb", "bbbbbbbbbbbbbbbbbb", ], - initialPlayerX: 1, - initialPlayerY: 1, + initialPlayerX: 3, + initialPlayerY: 6, movableBlocks: [ { x: 0, @@ -82,7 +82,7 @@ export const stages = new Map([ "bbbbbbbbbbb....bbb", ], initialPlayerX: 1, - initialPlayerY: 1, + initialPlayerY: 6, movableBlocks: [ { x: 3, From 96fc8c1eae3cd76542c3f0f765b2be070fd6f1d3 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 08:37:05 +0900 Subject: [PATCH 05/17] =?UTF-8?q?history=E3=81=AF=E7=8A=B6=E6=85=8B?= =?UTF-8?q?=E3=82=92=E5=85=A8=E3=81=A6=E8=A8=98=E9=8C=B2=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 129 ++++++++++++++++++++++++++++++++----------------- src/grid.ts | 113 +++++++++++++++++++++++++++++-------------- src/player.ts | 26 +++++++++- src/stages.ts | 2 + 4 files changed, 187 insertions(+), 83 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 22ff70d..bcd94a0 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -15,13 +15,18 @@ export type AbilityEnableOptions = { cut: boolean; }; type History = { - at: { x: number; y: number }; - from: MovableObject | Block; - to: MovableObject | Block; - inventory: { - before: MovableObject | null; - after: MovableObject | null; - }; + playerX: number; + playerY: number; + playerFacing: Facing; + inventory: MovableObject | null; + movableBlocks: { + x: number; + y: number; + objectId: number; + // 基準ブロックからの相対位置 + relativeX: number; + relativeY: number; + }[]; }; function isMovableObject(obj: MovableObject | Block): obj is MovableObject { @@ -66,13 +71,28 @@ export class AbilityControl { const y = this.focused.y; const target = cx.grid.getBlock(x, y); if (!target || target !== Block.movable) return; - const movableObject = cx.grid.getMovableObject(x, y); + const movableObject = cx.grid.getMovableObject(x, y, cx); if (!movableObject) return; this.inventory = movableObject; + // cx.gridとinventryは重複しないように + cx.grid.movableBlocks = cx.grid.movableBlocks.filter( + (block) => block.objectId !== movableObject.objectId, + ); } - paste(cx: Context, facing: Facing) { + paste(cx: Context, facing: Facing, playerAt: Coords) { if (!this.focused) return; if (!this.inventory /*|| this.inventory === Block.air*/) return; + const objectId = this.inventory.objectId; + + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); + + // 左向きのときにブロックを配置する位置を変更するのに使用 const width = this.inventory.relativePositions.reduce( (acc, i) => Math.max(acc, i.x), @@ -107,50 +127,57 @@ export class AbilityControl { } this.pushHistory({ - at: { ...this.focused }, - from: Block.air, - to: prevInventory, - inventory: { - before: prevInventory, - after: this.inventory, - }, + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, }); } - cut(cx: Context) { + cut(cx: Context, facing: Facing, playerAt: Coords) { if (!this.focused) return; + + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); + console.log(this.history); + const x = this.focused.x; const y = this.focused.y; const target = cx.grid.getBlock(x, y); // removable 以外はカットできない if (!target || target !== Block.movable) return; - const movableObject = cx.grid.getMovableObject(x, y); + const movableObject = cx.grid.getMovableObject(x, y, cx); if (!movableObject) return; - const prevInventory = this.inventory; + // const prevInventory = this.inventory; this.inventory = movableObject; - + // cx.gridとinventryは重複しないように + cx.grid.movableBlocks = cx.grid.movableBlocks.filter( + (block) => block.objectId !== movableObject.objectId, + ); for (const i of movableObject.relativePositions) { const positionX = movableObject.x + i.x; const positionY = movableObject.y + i.y; - cx.grid.setBlock(cx, positionX, positionY, Block.air); + cx.grid.setBlock(positionX, positionY, Block.air); } - // cx.grid.setBlock(cx, this.focused.x, this.focused.y, Block.air); - this.pushHistory({ - at: { ...this.focused }, - from: target, - to: Block.air, - inventory: { - before: prevInventory, - after: movableObject, - }, + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, }); } // History については、 `docs/history-stack.png` を参照のこと pushHistory(h: History) { this.history = this.history.slice(0, this.historyIndex); - this.history.push(h); + this.history.push(JSON.parse(JSON.stringify(h))); this.historyIndex = this.history.length; console.log(`history: ${this.historyIndex} / ${this.history.length}`); } @@ -158,24 +185,34 @@ export class AbilityControl { if (this.historyIndex <= 0) return; this.historyIndex--; // undo は、巻き戻し後の index で計算する const op = this.history[this.historyIndex]; - if (!isMovableObject(op.from)) { - cx.grid.setBlock(cx, op.at.x, op.at.y, op.from); - } else { - cx.grid.setMovableObject(cx, op.at.x, op.at.y, op.from); - } - this.inventory = op.inventory.before; + + // すべてのオブジェクトを削除 + cx.grid.clearAllMovableBlocks(); + + // オブジェクトを配置 + this.inventory = op.inventory + ? JSON.parse(JSON.stringify(op.inventory)) + : null; + cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); + cx.grid.setAllMovableBlocks(cx); + console.log(`history: ${this.historyIndex} / ${this.history.length}`); } redo(cx: Context) { if (this.historyIndex >= this.history.length) return; const op = this.history[this.historyIndex]; this.historyIndex++; // redo は、巻き戻し前の index - this.inventory = op.inventory.after; - if (!isMovableObject(op.to)) { - cx.grid.setBlock(cx, op.at.x, op.at.y, op.to); - } else { - cx.grid.setMovableObject(cx, op.at.x, op.at.y, op.to); - } + + // すべてのオブジェクトを削除 + cx.grid.clearAllMovableBlocks(); + + // オブジェクトを配置 + this.inventory = op.inventory + ? JSON.parse(JSON.stringify(op.inventory)) + : null; + cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); + cx.grid.setAllMovableBlocks(cx); + console.log(`history: ${this.historyIndex} / ${this.history.length}`); } handleKeyDown( @@ -183,17 +220,19 @@ export class AbilityControl { e: KeyboardEvent, onGround: boolean, facing: Facing, + history: History[], + playerAt: Coords, ) { if (!(e.ctrlKey || e.metaKey)) return; if (this.enabled.paste && onGround && e.key === "v") { - this.paste(cx, facing); + this.paste(cx, facing, playerAt); } if (this.enabled.copy && onGround && e.key === "c") { this.copy(cx); } if (this.enabled.cut && onGround && e.key === "x") { - this.cut(cx); + this.cut(cx, facing, playerAt); } if (e.key === "z") { this.undo(cx); diff --git a/src/grid.ts b/src/grid.ts index fa2169f..2f0c6bd 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -76,7 +76,7 @@ export class Grid { block, sprite, objectId: movableBlock.objectId, - // 同グループの基準ブロックに対する相対位置 + // 同オブジェクトの基準ブロックに対する相対位置 relativePosition: { x: movableBlock.relativeX, y: movableBlock.relativeY, @@ -106,34 +106,35 @@ export class Grid { getBlock(x: number, y: number): Block | undefined { return this.cells[y]?.[x]?.block; } - getMovableObject(x: number, y: number): MovableObject | undefined { + getMovableObject( + x: number, + y: number, + cx: Context, + ): MovableObject | undefined { const cell = this.cells[y]?.[x]; if (!cell) return undefined; - if (cell.block === Block.movable) { - const objectId = cell.objectId; - const retrievedBlocks = this.movableBlocks.filter( - (block) => block.objectId === objectId, - ); - const retrievedObject: MovableObject = { - objectId, - x: retrievedBlocks.filter( - (block) => block.relativeX === 0 && block.relativeY === 0, - )[0].x, - y: retrievedBlocks.filter( - (block) => block.relativeX === 0 && block.relativeY === 0, - )[0].y, - relativePositions: retrievedBlocks.map((block) => ({ - x: block.relativeX, - y: block.relativeY, - })), - }; - - console.log(retrievedObject); + if (cell.block !== Block.movable) return undefined; + const objectId = cell.objectId; + const retrievedBlocks = this.movableBlocks.filter( + (block) => block.objectId === objectId, + ); + const retrievedObject: MovableObject = { + objectId, + x: retrievedBlocks.filter( + (block) => block.relativeX === 0 && block.relativeY === 0, + )[0].x, + y: retrievedBlocks.filter( + (block) => block.relativeX === 0 && block.relativeY === 0, + )[0].y, + relativePositions: retrievedBlocks.map((block) => ({ + x: block.relativeX, + y: block.relativeY, + })), + }; - return retrievedObject; - } + return retrievedObject; } - setBlock(cx: Context, x: number, y: number, block: Block) { + setBlock(x: number, y: number, block: Block) { const prev = this.cells[y][x]; if (block === prev.block) return; if (prev.block !== Block.air) { @@ -147,9 +148,6 @@ export class Grid { } } setMovableObject(cx: Context, x: number, y: number, object: MovableObject) { - console.log("1"); - console.log(object.relativePositions); - console.log(object, object); const prev = this.cells[y][x]; if (prev.block !== Block.air) { this.stage.removeChild(prev.sprite); @@ -174,20 +172,61 @@ export class Grid { y: i.y, }, }; + this.movableBlocks.push({ + x: positionX, + y: positionY, + objectId: object.objectId, + relativeX: i.x, + relativeY: i.y, + }); } + + // 座標基準で重複を削除 + this.movableBlocks = Array.from( + new Map(this.movableBlocks.map((b) => [`${b.x},${b.y}`, b])).values(), + ); + // オブジェクトの座標を更新 object.x = x; object.y = y; - this.movableBlocks = this.movableBlocks.map((block) => { - if (block.objectId === object.objectId) { - return { - ...block, - x, - y, - }; + } + clearAllMovableBlocks() { + for (const i of this.movableBlocks) { + const prev = this.cells[i.y][i.x]; + if (prev.block !== Block.air) { + this.stage.removeChild(prev.sprite); } - return block; - }); + this.setBlock(i.x, i.y, Block.air); + } + } + setAllMovableBlocks(cx: Context) { + const objectIds = Array.from( + new Set(this.movableBlocks.map((block) => block.objectId)), + ); + for (const objectId of objectIds) { + const retrievedBlocks = this.movableBlocks.filter( + (block) => block.objectId === objectId, + ); + const movableObject: MovableObject = { + objectId, + x: retrievedBlocks.filter( + (block) => block.relativeX === 0 && block.relativeY === 0, + )[0].x, + y: retrievedBlocks.filter( + (block) => block.relativeX === 0 && block.relativeY === 0, + )[0].y, + relativePositions: retrievedBlocks.map((block) => ({ + x: block.relativeX, + y: block.relativeY, + })), + }; + cx.grid.setMovableObject( + cx, + movableObject.x, + movableObject.y, + movableObject, + ); + } } } diff --git a/src/player.ts b/src/player.ts index 8c41fef..48acf03 100644 --- a/src/player.ts +++ b/src/player.ts @@ -3,8 +3,24 @@ import { AbilityControl, type AbilityInit } from "./ability.ts"; import * as c from "./constants.ts"; import { Block } from "./constants.ts"; import type { Context } from "./context.ts"; +import type { MovableObject } from "./grid.ts"; import { highlightHoldTexture, highlightTexture } from "./resources.ts"; +type History = { + playerX: number; + playerY: number; + playerFacing: c.Facing; + inventory: MovableObject | null; + movableBlocks: { + x: number; + y: number; + objectId: number; + // 基準ブロックからの相対位置 + relativeX: number; + relativeY: number; + }[]; +}; + enum Inputs { Left = 0, Right = 1, @@ -20,6 +36,7 @@ export class Player { jumpingBegin: number | null; facing: c.Facing = c.Facing.right; ability: AbilityControl; + history: History[] = []; constructor( cx: Context, spriteOptions?: SpriteOptions | Texture, @@ -84,7 +101,14 @@ export class Player { } handleInput(cx: Context, event: KeyboardEvent, eventIsKeyDown: boolean) { if (eventIsKeyDown) { - this.ability.handleKeyDown(cx, event, this.onGround, this.facing); + this.ability.handleKeyDown( + cx, + event, + this.onGround, + this.facing, + this.history, + { x: this.x, y: this.y }, + ); } switch (event.key) { case "Control": diff --git a/src/stages.ts b/src/stages.ts index 54541a9..7e2960a 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -7,6 +7,8 @@ export type StageDefinition = { y: number; objectId: number; // 基準ブロックからの相対位置 + // 基準ブロックは原則オブジェクトの左下で + // 右を向くときに目の前に来るブロック relativeX: number; relativeY: number; }[]; From c1cd0aafd12e2022edf2e59417ad3cc4802dbd04 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 08:45:55 +0900 Subject: [PATCH 06/17] =?UTF-8?q?getMovableOnject=E3=81=AE=E4=BE=8B?= =?UTF-8?q?=E5=A4=96=E6=BC=8F=E3=82=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/grid.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/grid.ts b/src/grid.ts index 2f0c6bd..2de764d 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -118,6 +118,7 @@ export class Grid { const retrievedBlocks = this.movableBlocks.filter( (block) => block.objectId === objectId, ); + if (!retrievedBlocks) return; const retrievedObject: MovableObject = { objectId, x: retrievedBlocks.filter( From e9d65e88bb68ac725a46e6026375a30ab990ffa4 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 09:31:20 +0900 Subject: [PATCH 07/17] =?UTF-8?q?copy=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 100 ++++++++++++++++++++++++++----------------------- src/grid.ts | 22 ++++++----- src/player.ts | 2 +- src/stages.ts | 16 ++++---- 4 files changed, 74 insertions(+), 66 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index bcd94a0..7f7f4cd 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -22,7 +22,7 @@ type History = { movableBlocks: { x: number; y: number; - objectId: number; + objectId: string; // 基準ブロックからの相対位置 relativeX: number; relativeY: number; @@ -71,27 +71,19 @@ export class AbilityControl { const y = this.focused.y; const target = cx.grid.getBlock(x, y); if (!target || target !== Block.movable) return; - const movableObject = cx.grid.getMovableObject(x, y, cx); + const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; this.inventory = movableObject; // cx.gridとinventryは重複しないように - cx.grid.movableBlocks = cx.grid.movableBlocks.filter( - (block) => block.objectId !== movableObject.objectId, - ); + // cx.grid.movableBlocks = cx.grid.movableBlocks.filter( + // (block) => block.objectId !== movableObject.objectId, + // ); } - paste(cx: Context, facing: Facing, playerAt: Coords) { + paste(cx: Context, facing: Facing) { if (!this.focused) return; if (!this.inventory /*|| this.inventory === Block.air*/) return; const objectId = this.inventory.objectId; - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); - // 左向きのときにブロックを配置する位置を変更するのに使用 const width = this.inventory.relativePositions.reduce( @@ -116,46 +108,26 @@ export class AbilityControl { return; } } - // const target = cx.grid.getBlock(this.focused.x, this.focused.y); - // if (!target || target !== Block.air) return; - const prevInventory = this.inventory; cx.grid.setMovableObject(cx, x, y, this.inventory); if (!this.inventoryIsInfinite) { this.inventory = null; } - - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); } - cut(cx: Context, facing: Facing, playerAt: Coords) { + cut(cx: Context) { if (!this.focused) return; - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); - console.log(this.history); - const x = this.focused.x; const y = this.focused.y; const target = cx.grid.getBlock(x, y); // removable 以外はカットできない if (!target || target !== Block.movable) return; - const movableObject = cx.grid.getMovableObject(x, y, cx); + const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; - // const prevInventory = this.inventory; this.inventory = movableObject; // cx.gridとinventryは重複しないように + // 取得したオブジェクトは削除する cx.grid.movableBlocks = cx.grid.movableBlocks.filter( (block) => block.objectId !== movableObject.objectId, ); @@ -164,14 +136,6 @@ export class AbilityControl { const positionY = movableObject.y + i.y; cx.grid.setBlock(positionX, positionY, Block.air); } - - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); } // History については、 `docs/history-stack.png` を参照のこと @@ -226,13 +190,55 @@ export class AbilityControl { if (!(e.ctrlKey || e.metaKey)) return; if (this.enabled.paste && onGround && e.key === "v") { - this.paste(cx, facing, playerAt); + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); + this.paste(cx, facing); + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); } if (this.enabled.copy && onGround && e.key === "c") { + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); this.copy(cx); + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); } if (this.enabled.cut && onGround && e.key === "x") { - this.cut(cx, facing, playerAt); + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); + this.cut(cx); + this.pushHistory({ + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }); } if (e.key === "z") { this.undo(cx); diff --git a/src/grid.ts b/src/grid.ts index 2de764d..9f2a548 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -12,7 +12,7 @@ type GridCell = | { block: Block.movable; sprite: Sprite; - objectId: number; + objectId: string; relativePosition: { x: number; y: number; @@ -26,14 +26,14 @@ type GridCell = export type MovableBlocks = { x: number; y: number; - objectId: number; + objectId: string; // 基準ブロックからの相対位置 relativeX: number; relativeY: number; }[]; export type MovableObject = { - objectId: number; + objectId: string; x: number; y: number; relativePositions: { @@ -106,11 +106,7 @@ export class Grid { getBlock(x: number, y: number): Block | undefined { return this.cells[y]?.[x]?.block; } - getMovableObject( - x: number, - y: number, - cx: Context, - ): MovableObject | undefined { + getMovableObject(x: number, y: number): MovableObject | undefined { const cell = this.cells[y]?.[x]; if (!cell) return undefined; if (cell.block !== Block.movable) return undefined; @@ -118,7 +114,7 @@ export class Grid { const retrievedBlocks = this.movableBlocks.filter( (block) => block.objectId === objectId, ); - if (!retrievedBlocks) return; + if (!retrievedBlocks) return undefined; const retrievedObject: MovableObject = { objectId, x: retrievedBlocks.filter( @@ -132,6 +128,7 @@ export class Grid { y: block.relativeY, })), }; + if (!retrievedObject.x || !retrievedObject.y) return undefined; return retrievedObject; } @@ -153,8 +150,13 @@ export class Grid { if (prev.block !== Block.air) { this.stage.removeChild(prev.sprite); } + // 既に同じobjectIdのオブジェクトが設置済みのとき、objectIdを変更して設置する + // copy使用時に対応 + if (this.movableBlocks.filter((b) => b.objectId === object.objectId)) { + object.objectId = self.crypto.randomUUID(); + } + for (const i of object.relativePositions) { - console.log(i); const positionX = x + i.x; const positionY = y + i.y; const sprite = createSprite( diff --git a/src/player.ts b/src/player.ts index 48acf03..9047782 100644 --- a/src/player.ts +++ b/src/player.ts @@ -14,7 +14,7 @@ type History = { movableBlocks: { x: number; y: number; - objectId: number; + objectId: string; // 基準ブロックからの相対位置 relativeX: number; relativeY: number; diff --git a/src/stages.ts b/src/stages.ts index 7e2960a..4d30702 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -5,7 +5,7 @@ export type StageDefinition = { movableBlocks: { x: number; y: number; - objectId: number; + objectId: string; // 基準ブロックからの相対位置 // 基準ブロックは原則オブジェクトの左下で // 右を向くときに目の前に来るブロック @@ -32,7 +32,7 @@ export const stages = new Map([ { x: 9, y: 4, - objectId: 1, + objectId: "1", relativeX: 0, relativeY: 0, }, @@ -57,14 +57,14 @@ export const stages = new Map([ { x: 0, y: 3, - objectId: 1, + objectId: "1", relativeX: 0, relativeY: 0, }, { x: 7, y: 5, - objectId: 2, + objectId: "2", relativeX: 0, relativeY: 0, }, @@ -89,28 +89,28 @@ export const stages = new Map([ { x: 3, y: 5, - objectId: 1, + objectId: "1", relativeX: 0, relativeY: 0, }, { x: 4, y: 5, - objectId: 1, + objectId: "1", relativeX: 1, relativeY: 0, }, { x: 4, y: 3, - objectId: 2, + objectId: "2", relativeX: 0, relativeY: 0, }, { x: 8, y: 2, - objectId: 3, + objectId: "3", relativeX: 0, relativeY: 0, }, From 38ebd620ac9798aa8bb6af37d864c075bec2c8e0 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 09:47:08 +0900 Subject: [PATCH 08/17] =?UTF-8?q?getMovableObject=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 5 ++++- src/grid.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 7f7f4cd..07ffeae 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -124,7 +124,10 @@ export class AbilityControl { // removable 以外はカットできない if (!target || target !== Block.movable) return; const movableObject = cx.grid.getMovableObject(x, y); - if (!movableObject) return; + if (!movableObject) { + console.log("aaaaaa"); + return; + } this.inventory = movableObject; // cx.gridとinventryは重複しないように // 取得したオブジェクトは削除する diff --git a/src/grid.ts b/src/grid.ts index 9f2a548..02b14b2 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -128,7 +128,7 @@ export class Grid { y: block.relativeY, })), }; - if (!retrievedObject.x || !retrievedObject.y) return undefined; + if (!retrievedObject) return undefined; return retrievedObject; } From e94a1c31ba5bc6bf0571996c01ec975a812d4927 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 10:46:41 +0900 Subject: [PATCH 09/17] =?UTF-8?q?History=E3=82=92Player=E4=BE=9D=E5=AD=98?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 168 +++++++++++++++++++++++++++++++------------------ src/player.ts | 5 +- 2 files changed, 110 insertions(+), 63 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 07ffeae..0881f99 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -34,8 +34,8 @@ function isMovableObject(obj: MovableObject | Block): obj is MovableObject { } export class AbilityControl { - history: History[] = []; - historyIndex = 0; + // history: History[] = []; + // historyIndex = 0; inventory: MovableObject | null = null; inventoryIsInfinite = false; enabled: AbilityEnableOptions; @@ -142,16 +142,32 @@ export class AbilityControl { } // History については、 `docs/history-stack.png` を参照のこと - pushHistory(h: History) { - this.history = this.history.slice(0, this.historyIndex); - this.history.push(JSON.parse(JSON.stringify(h))); - this.historyIndex = this.history.length; - console.log(`history: ${this.historyIndex} / ${this.history.length}`); + pushHistory( + h: History, + history: { + list: History[]; + index: number; + }, + ) { + history.list = history.list.slice(0, history.index); + history.list.push(JSON.parse(JSON.stringify(h))); + history.index = history.list.length; + console.log(`history: ${history.index} / ${history.list.length}`); + console.log(history); } - undo(cx: Context) { - if (this.historyIndex <= 0) return; - this.historyIndex--; // undo は、巻き戻し後の index で計算する - const op = this.history[this.historyIndex]; + undo( + cx: Context, + history: { + list: History[]; + index: number; + }, + ) { + console.log(history.index); + if (history.index <= 0) return; + history.index--; // undo は、巻き戻し後の index で計算する + const op = history.list[history.index]; + + console.log("bbb"); // すべてのオブジェクトを削除 cx.grid.clearAllMovableBlocks(); @@ -163,12 +179,18 @@ export class AbilityControl { cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); cx.grid.setAllMovableBlocks(cx); - console.log(`history: ${this.historyIndex} / ${this.history.length}`); + console.log(`history: ${history.index} / ${history.list.length}`); } - redo(cx: Context) { - if (this.historyIndex >= this.history.length) return; - const op = this.history[this.historyIndex]; - this.historyIndex++; // redo は、巻き戻し前の index + redo( + cx: Context, + history: { + list: History[]; + index: number; + }, + ) { + if (history.index >= history.list.length) return; + const op = history.list[history.index]; + history.index++; // redo は、巻き戻し前の index // すべてのオブジェクトを削除 cx.grid.clearAllMovableBlocks(); @@ -180,75 +202,97 @@ export class AbilityControl { cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); cx.grid.setAllMovableBlocks(cx); - console.log(`history: ${this.historyIndex} / ${this.history.length}`); + console.log(`history: ${history.index} / ${history.list.length}`); } handleKeyDown( cx: Context, e: KeyboardEvent, onGround: boolean, facing: Facing, - history: History[], + history: { + list: History[]; + index: number; + }, playerAt: Coords, ) { if (!(e.ctrlKey || e.metaKey)) return; if (this.enabled.paste && onGround && e.key === "v") { - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); + this.pushHistory( + { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }, + history, + ); this.paste(cx, facing); - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); + this.pushHistory( + { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }, + history, + ); } if (this.enabled.copy && onGround && e.key === "c") { - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); + this.pushHistory( + { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }, + history, + ); this.copy(cx); - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); + this.pushHistory( + { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }, + history, + ); } if (this.enabled.cut && onGround && e.key === "x") { - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); + this.pushHistory( + { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }, + history, + ); this.cut(cx); - this.pushHistory({ - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - }); + this.pushHistory( + { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + }, + history, + ); + console.log("x", history); } if (e.key === "z") { - this.undo(cx); + this.undo(cx, history); e.preventDefault(); } if (e.key === "y") { - this.redo(cx); + this.redo(cx, history); e.preventDefault(); } } diff --git a/src/player.ts b/src/player.ts index 9047782..7720437 100644 --- a/src/player.ts +++ b/src/player.ts @@ -36,7 +36,10 @@ export class Player { jumpingBegin: number | null; facing: c.Facing = c.Facing.right; ability: AbilityControl; - history: History[] = []; + history = { + list: [] as History[], + index: 0, + }; constructor( cx: Context, spriteOptions?: SpriteOptions | Texture, From a0f7b9d96fad6937a95567a4756f12c42f41ab82 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 10:51:59 +0900 Subject: [PATCH 10/17] =?UTF-8?q?history=E3=81=AE=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E3=82=92=E5=8F=8D=E6=98=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 11 ++++++++++- src/player.ts | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 0881f99..02950b6 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -215,7 +215,7 @@ export class AbilityControl { }, playerAt: Coords, ) { - if (!(e.ctrlKey || e.metaKey)) return; + if (!(e.ctrlKey || e.metaKey)) return undefined; if (this.enabled.paste && onGround && e.key === "v") { this.pushHistory( @@ -290,10 +290,19 @@ export class AbilityControl { if (e.key === "z") { this.undo(cx, history); e.preventDefault(); + return { + x: history.list[history.index].playerX, + y: history.list[history.index].playerY, + }; } if (e.key === "y") { this.redo(cx, history); e.preventDefault(); + if (history.index >= history.list.length) return; + return { + x: history.list[history.index].playerX, + y: history.list[history.index].playerY, + }; } } } diff --git a/src/player.ts b/src/player.ts index 7720437..623a5fc 100644 --- a/src/player.ts +++ b/src/player.ts @@ -104,7 +104,7 @@ export class Player { } handleInput(cx: Context, event: KeyboardEvent, eventIsKeyDown: boolean) { if (eventIsKeyDown) { - this.ability.handleKeyDown( + const playerPosition = this.ability.handleKeyDown( cx, event, this.onGround, @@ -112,6 +112,10 @@ export class Player { this.history, { x: this.x, y: this.y }, ); + if (playerPosition) { + this.x = playerPosition.x; + this.y = playerPosition.y; + } } switch (event.key) { case "Control": From 68c2f5f1b4d093878c92a88bcfa286f17bdbb181 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 13:38:19 +0900 Subject: [PATCH 11/17] =?UTF-8?q?history=E3=81=AE=E5=85=88=E9=A0=AD?= =?UTF-8?q?=E3=81=AB=E5=88=9D=E6=9C=9F=E7=8A=B6=E6=85=8B=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 24 ++++-------------------- src/player.ts | 7 +++++++ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 02950b6..05cec19 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -29,13 +29,7 @@ type History = { }[]; }; -function isMovableObject(obj: MovableObject | Block): obj is MovableObject { - return (obj as MovableObject).objectId !== undefined; -} - export class AbilityControl { - // history: History[] = []; - // historyIndex = 0; inventory: MovableObject | null = null; inventoryIsInfinite = false; enabled: AbilityEnableOptions; @@ -74,10 +68,6 @@ export class AbilityControl { const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; this.inventory = movableObject; - // cx.gridとinventryは重複しないように - // cx.grid.movableBlocks = cx.grid.movableBlocks.filter( - // (block) => block.objectId !== movableObject.objectId, - // ); } paste(cx: Context, facing: Facing) { if (!this.focused) return; @@ -124,10 +114,7 @@ export class AbilityControl { // removable 以外はカットできない if (!target || target !== Block.movable) return; const movableObject = cx.grid.getMovableObject(x, y); - if (!movableObject) { - console.log("aaaaaa"); - return; - } + if (!movableObject) return; this.inventory = movableObject; // cx.gridとinventryは重複しないように // 取得したオブジェクトは削除する @@ -142,6 +129,7 @@ export class AbilityControl { } // History については、 `docs/history-stack.png` を参照のこと + // すべての状態を保存 pushHistory( h: History, history: { @@ -149,11 +137,11 @@ export class AbilityControl { index: number; }, ) { - history.list = history.list.slice(0, history.index); + // history.listの先頭(初期状態)は残す + history.list = history.list.slice(0, Math.max(history.index, 1)); history.list.push(JSON.parse(JSON.stringify(h))); history.index = history.list.length; console.log(`history: ${history.index} / ${history.list.length}`); - console.log(history); } undo( cx: Context, @@ -162,13 +150,10 @@ export class AbilityControl { index: number; }, ) { - console.log(history.index); if (history.index <= 0) return; history.index--; // undo は、巻き戻し後の index で計算する const op = history.list[history.index]; - console.log("bbb"); - // すべてのオブジェクトを削除 cx.grid.clearAllMovableBlocks(); @@ -285,7 +270,6 @@ export class AbilityControl { }, history, ); - console.log("x", history); } if (e.key === "z") { this.undo(cx, history); diff --git a/src/player.ts b/src/player.ts index 623a5fc..ce61fb5 100644 --- a/src/player.ts +++ b/src/player.ts @@ -69,6 +69,13 @@ export class Player { this.onGround = false; this.jumpingBegin = null; this.holdingKeys = {}; + this.history.list.push({ + playerX: this.x, + playerY: this.y, + playerFacing: this.facing, + inventory: null, + movableBlocks: cx.grid.movableBlocks, + }); } get x() { return this.sprite.x; From eb7b3cef18a7b8e1bf15a96ad1dffd856e26bfc3 Mon Sep 17 00:00:00 2001 From: aster <137767097+aster-void@users.noreply.github.com> Date: Sun, 4 May 2025 18:04:28 +0900 Subject: [PATCH 12/17] fix build --- routes/game/+page.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/game/+page.svelte b/routes/game/+page.svelte index f2adc0e..38038ca 100644 --- a/routes/game/+page.svelte +++ b/routes/game/+page.svelte @@ -1,10 +1,11 @@ From b1bfe3456abf00ec56447f3b5523032e47b9dca8 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 17:28:24 +0900 Subject: [PATCH 13/17] =?UTF-8?q?=E5=8B=95=E3=81=8F=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 110 +++++++++++++++++++++++-------------------------- src/player.ts | 14 ++----- 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 5e4f179..5f35a95 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -1,6 +1,7 @@ import { Block, Facing, type MovableBlock } from "./constants.ts"; import type { Context } from "./context.ts"; import type { MovableBlocks, MovableObject } from "./grid.ts"; +import type { History } from "./player.ts"; export type Coords = { x: number; @@ -16,32 +17,14 @@ export type AbilityEnableOptions = { paste: number; cut: number; }; -type History = { - playerX: number; - playerY: number; - playerFacing: Facing; - inventory: MovableObject | null; - movableBlocks: { - x: number; - y: number; - objectId: string; - // 基準ブロックからの相対位置 - relativeX: number; - relativeY: number; - }[]; - enabled: { - before: AbilityEnableOptions; - after: AbilityEnableOptions; - }; -}; export class AbilityControl { inventory: MovableObject | null = null; inventoryIsInfinite = false; - enabled: AbilityEnableOptions; + enabledAbilities: AbilityEnableOptions; focused: Coords | undefined; constructor(cx: Context, options?: AbilityInit) { - this.enabled = options?.enabled ?? { + this.enabledAbilities = options?.enabled ?? { copy: Number.POSITIVE_INFINITY, paste: Number.POSITIVE_INFINITY, cut: Number.POSITIVE_INFINITY, @@ -51,8 +34,8 @@ export class AbilityControl { ...prev, inventory: this.inventory, inventoryIsInfinite: this.inventoryIsInfinite, - ...this.enabled, - undo: 0, + ...this.enabledAbilities, + undo: 1, redo: 0, })); // document.addEventListener("copy", (e) => { @@ -105,7 +88,7 @@ export class AbilityControl { this.setInventory(cx, movableObject); cx.uiContext.update((prev) => ({ ...prev, - copy: --this.enabled.copy, + copy: --this.enabledAbilities.copy, })); } paste(cx: Context, facing: Facing) { @@ -143,10 +126,10 @@ export class AbilityControl { if (!this.inventoryIsInfinite) { this.setInventory(cx, null); } - const prevEnabled = { ...this.enabled }; + const prevEnabled = { ...this.enabledAbilities }; cx.uiContext.update((prev) => ({ ...prev, - paste: --this.enabled.paste, + paste: --this.enabledAbilities.paste, })); // this.pushHistory(cx, { // at: { ...this.focused }, @@ -185,10 +168,10 @@ export class AbilityControl { const positionY = movableObject.y + i.y; cx.grid.setBlock(positionX, positionY, Block.air); } - const prevEnabled = { ...this.enabled }; + const prevEnabled = { ...this.enabledAbilities }; cx.uiContext.update((prev) => ({ ...prev, - cut: --this.enabled.cut, + cut: --this.enabledAbilities.cut, })); // this.pushHistory({ @@ -213,7 +196,16 @@ export class AbilityControl { ) { // history.listの先頭(初期状態)は残す history.list = history.list.slice(0, Math.max(history.index, 1)); - history.list.push(JSON.parse(JSON.stringify(h))); + // Infinityはnullにならないように置換してDeepCopy + history.list.push( + JSON.parse( + JSON.stringify(h, (k, v) => + v === Number.POSITIVE_INFINITY ? "Infinity" : v, + ), + (k, v) => (v === "Infinity" ? Number.POSITIVE_INFINITY : v), + ), + ); + history.index = history.list.length; console.log(`history: ${history.index} / ${history.list.length}`); cx.uiContext.update((prev) => ({ @@ -236,22 +228,25 @@ export class AbilityControl { // すべてのオブジェクトを削除 cx.grid.clearAllMovableBlocks(); - // オブジェクトを配置 - // this.inventory = op.inventory + // オブジェクトを配置 // this.inventory = op.inventory // ? JSON.parse(JSON.stringify(op.inventory)) // : null; this.setInventory(cx, op.inventory); cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); cx.grid.setAllMovableBlocks(cx); - this.enabled = op.enabled.before; + // console.log(op.enabledAbilities) + + this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ ...prev, - ...this.enabled, + ...this.enabledAbilities, undo: history.index, redo: history.list.length - history.index, })); + // console.log(this.enabledAbilities) + console.log(`history: ${history.index} / ${history.list.length}`); } redo( @@ -269,18 +264,15 @@ export class AbilityControl { cx.grid.clearAllMovableBlocks(); // オブジェクトを配置 - // this.inventory = op.inventory - // ? JSON.parse(JSON.stringify(op.inventory)) - // : null; this.setInventory(cx, op.inventory); cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); cx.grid.setAllMovableBlocks(cx); - this.enabled = op.enabled.after; + this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ ...prev, - ...this.enabled, + ...this.enabledAbilities, undo: history.index, redo: history.list.length - history.index, })); @@ -300,7 +292,7 @@ export class AbilityControl { ) { if (!(e.ctrlKey || e.metaKey)) return undefined; - if (this.enabled.paste && onGround && e.key === "v") { + if (this.enabledAbilities.paste > 0 && onGround && e.key === "v") { this.pushHistory( cx, { @@ -309,9 +301,9 @@ export class AbilityControl { inventory: this.inventory ? this.inventory : null, playerFacing: facing, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.enabled, - after: { ...this.enabled, paste: this.enabled.paste - 1 }, + enabledAbilities: { + ...this.enabledAbilities, + paste: this.enabledAbilities.paste - 1, }, }, history, @@ -325,15 +317,15 @@ export class AbilityControl { inventory: this.inventory ? this.inventory : null, playerFacing: facing, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.enabled, - after: { ...this.enabled, paste: this.enabled.paste - 1 }, + enabledAbilities: { + ...this.enabledAbilities, + paste: this.enabledAbilities.paste - 1, }, }, history, ); } - if (this.enabled.copy && onGround && e.key === "c") { + if (this.enabledAbilities.copy > 0 && onGround && e.key === "c") { this.pushHistory( cx, { @@ -342,9 +334,9 @@ export class AbilityControl { inventory: this.inventory ? this.inventory : null, playerFacing: facing, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.enabled, - after: { ...this.enabled, copy: this.enabled.copy - 1 }, + enabledAbilities: { + ...this.enabledAbilities, + copy: this.enabledAbilities.copy - 1, }, }, history, @@ -358,15 +350,15 @@ export class AbilityControl { inventory: this.inventory ? this.inventory : null, playerFacing: facing, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.enabled, - after: { ...this.enabled, copy: this.enabled.copy - 1 }, + enabledAbilities: { + ...this.enabledAbilities, + copy: this.enabledAbilities.copy - 1, }, }, history, ); } - if (this.enabled.cut && onGround && e.key === "x") { + if (this.enabledAbilities.cut > 0 && onGround && e.key === "x") { this.pushHistory( cx, { @@ -375,9 +367,9 @@ export class AbilityControl { inventory: this.inventory ? this.inventory : null, playerFacing: facing, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.enabled, - after: { ...this.enabled, cut: this.enabled.cut - 1 }, + enabledAbilities: { + ...this.enabledAbilities, + cut: this.enabledAbilities.cut - 1, }, }, history, @@ -391,15 +383,17 @@ export class AbilityControl { inventory: this.inventory ? this.inventory : null, playerFacing: facing, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.enabled, - after: { ...this.enabled, cut: this.enabled.cut - 1 }, + enabledAbilities: { + ...this.enabledAbilities, + cut: this.enabledAbilities.cut - 1, }, }, history, ); } if (e.key === "z") { + console.log(history); + console.log(this.enabledAbilities); this.undo(cx, history); e.preventDefault(); return { diff --git a/src/player.ts b/src/player.ts index e2e8be8..56d7ca1 100644 --- a/src/player.ts +++ b/src/player.ts @@ -7,7 +7,7 @@ import type { Context } from "./context.ts"; import type { MovableObject } from "./grid.ts"; import { highlightHoldTexture, highlightTexture } from "./resources.ts"; -type History = { +export type History = { playerX: number; playerY: number; playerFacing: c.Facing; @@ -20,10 +20,7 @@ type History = { relativeX: number; relativeY: number; }[]; - enabled: { - before: AbilityEnableOptions; - after: AbilityEnableOptions; - }; + enabledAbilities: AbilityEnableOptions; }; enum Inputs { @@ -43,7 +40,7 @@ export class Player { ability: AbilityControl; history = { list: [] as History[], - index: 0, + index: 1, }; constructor( cx: Context, @@ -80,10 +77,7 @@ export class Player { playerFacing: this.facing, inventory: null, movableBlocks: cx.grid.movableBlocks, - enabled: { - before: this.ability.enabled, - after: this.ability.enabled, - }, + enabledAbilities: this.ability.enabledAbilities, }); } get x() { From f1a341691e9a50c9013972570a6edaec7d239e22 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Mon, 5 May 2025 18:03:15 +0900 Subject: [PATCH 14/17] =?UTF-8?q?=E3=82=AA=E3=83=96=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=81=8C=E5=A4=89=E6=9B=B4=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=81=A8=E3=81=8D=E3=81=AF=E8=A8=98=E9=8C=B2?= =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 51 ++++++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 5f35a95..9af17b9 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -52,7 +52,7 @@ export class AbilityControl { // }); } setInventory(cx: Context, inventory: MovableObject | null) { - this.inventory = inventory; + this.inventory = JSON.parse(JSON.stringify(inventory)); cx.uiContext.update((prev) => ({ ...prev, inventory, @@ -131,19 +131,6 @@ export class AbilityControl { ...prev, paste: --this.enabledAbilities.paste, })); - // this.pushHistory(cx, { - // at: { ...this.focused }, - // from: Block.air, - // to: prevInventory, - // inventory: { - // before: prevInventory, - // after: this.inventory, - // }, - // enabled: { - // before: prevEnabled, - // after: this.enabled, - // }, - // }); } cut(cx: Context) { if (!this.focused) return; @@ -173,16 +160,6 @@ export class AbilityControl { ...prev, cut: --this.enabledAbilities.cut, })); - - // this.pushHistory({ - // at: { ...this.focused }, - // from: target, - // to: Block.air, - // inventory: { - // before: prevInventory, - // after: target, - // }, - // }); } // History については、 `docs/history-stack.png` を参照のこと @@ -197,15 +174,24 @@ export class AbilityControl { // history.listの先頭(初期状態)は残す history.list = history.list.slice(0, Math.max(history.index, 1)); // Infinityはnullにならないように置換してDeepCopy - history.list.push( - JSON.parse( - JSON.stringify(h, (k, v) => - v === Number.POSITIVE_INFINITY ? "Infinity" : v, - ), - (k, v) => (v === "Infinity" ? Number.POSITIVE_INFINITY : v), + const newHistory = JSON.parse( + JSON.stringify(h, (k, v) => + v === Number.POSITIVE_INFINITY ? "Infinity" : v, ), + (k, v) => (v === "Infinity" ? Number.POSITIVE_INFINITY : v), ); + // オブジェクトの状態について直前と一致するなら記録しない + if ( + history.list.length > 0 && + JSON.stringify(history.list[history.list.length - 1].movableBlocks) === + JSON.stringify(newHistory.movableBlocks) + ) { + return; + } + + history.list.push(newHistory); + history.index = history.list.length; console.log(`history: ${history.index} / ${history.list.length}`); cx.uiContext.update((prev) => ({ @@ -235,8 +221,6 @@ export class AbilityControl { cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); cx.grid.setAllMovableBlocks(cx); - // console.log(op.enabledAbilities) - this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ ...prev, @@ -245,8 +229,6 @@ export class AbilityControl { redo: history.list.length - history.index, })); - // console.log(this.enabledAbilities) - console.log(`history: ${history.index} / ${history.list.length}`); } redo( @@ -393,7 +375,6 @@ export class AbilityControl { } if (e.key === "z") { console.log(history); - console.log(this.enabledAbilities); this.undo(cx, history); e.preventDefault(); return { From 40cc5ce88f601f09c44bd97d63aa2b552cfe1da7 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Wed, 7 May 2025 10:32:21 +0900 Subject: [PATCH 15/17] =?UTF-8?q?copy=E6=99=82=E3=81=AE=E6=8C=99=E5=8B=95?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 31 ++++++++++++++++--------------- src/grid.ts | 19 ++++++++----------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index 9af17b9..e984d21 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -84,7 +84,10 @@ export class AbilityControl { if (!target || target !== Block.movable) return; const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; - // this.inventory = movableObject; + + // コピー元とは別のオブジェクトとして管理する + movableObject.objectId = self.crypto.randomUUID(); + this.setInventory(cx, movableObject); cx.uiContext.update((prev) => ({ ...prev, @@ -94,7 +97,6 @@ export class AbilityControl { paste(cx: Context, facing: Facing) { if (!this.focused) return; if (!this.inventory /*|| this.inventory === Block.air*/) return; - const objectId = this.inventory.objectId; // 左向きのときにブロックを配置する位置を変更するのに使用 const width = @@ -126,7 +128,7 @@ export class AbilityControl { if (!this.inventoryIsInfinite) { this.setInventory(cx, null); } - const prevEnabled = { ...this.enabledAbilities }; + // const prevEnabled = { ...this.enabledAbilities }; cx.uiContext.update((prev) => ({ ...prev, paste: --this.enabledAbilities.paste, @@ -143,10 +145,9 @@ export class AbilityControl { const prevInventory = this.inventory; const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; - // this.inventory = movableObject; this.setInventory(cx, movableObject); - // cx.gridとinventryは重複しないように - // 取得したオブジェクトは削除する + // cx.grid.movableBlocksとthis.inventryは重複しないように + // 取得したオブジェクトはcs.grid.movableBlocksから削除する cx.grid.movableBlocks = cx.grid.movableBlocks.filter( (block) => block.objectId !== movableObject.objectId, ); @@ -181,9 +182,14 @@ export class AbilityControl { (k, v) => (v === "Infinity" ? Number.POSITIVE_INFINITY : v), ); + console.log(history); + console.log(newHistory); + // オブジェクトの状態について直前と一致するなら記録しない if ( - history.list.length > 0 && + (history.index < history.list.length && + JSON.stringify(history.list[history.index].movableBlocks) === + JSON.stringify(newHistory.movableBlocks)) || JSON.stringify(history.list[history.list.length - 1].movableBlocks) === JSON.stringify(newHistory.movableBlocks) ) { @@ -214,12 +220,9 @@ export class AbilityControl { // すべてのオブジェクトを削除 cx.grid.clearAllMovableBlocks(); - // オブジェクトを配置 // this.inventory = op.inventory - // ? JSON.parse(JSON.stringify(op.inventory)) - // : null; + // オブジェクトを配置 this.setInventory(cx, op.inventory); - cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); - cx.grid.setAllMovableBlocks(cx); + cx.grid.setAllMovableBlocks(cx, op.movableBlocks); this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ @@ -247,9 +250,7 @@ export class AbilityControl { // オブジェクトを配置 this.setInventory(cx, op.inventory); - - cx.grid.movableBlocks = JSON.parse(JSON.stringify(op.movableBlocks)); - cx.grid.setAllMovableBlocks(cx); + cx.grid.setAllMovableBlocks(cx, op.movableBlocks); this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ diff --git a/src/grid.ts b/src/grid.ts index 7efd004..326a2ec 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -198,16 +198,12 @@ export class Grid { }; } } + // 指定された座標にオブジェクトを配置し、this.movableBlocksを更新する setMovableObject(cx: Context, x: number, y: number, object: MovableObject) { const prev = this.cells[y][x]; if (prev.block !== Block.air) { this.stage.removeChild(prev.sprite); } - // 既に同じobjectIdのオブジェクトが設置済みのとき、objectIdを変更して設置する - // copy使用時に対応 - if (this.movableBlocks.filter((b) => b.objectId === object.objectId)) { - object.objectId = self.crypto.randomUUID(); - } for (const i of object.relativePositions) { const positionX = x + i.x; @@ -239,9 +235,9 @@ export class Grid { } // 座標基準で重複を削除 - this.movableBlocks = Array.from( - new Map(this.movableBlocks.map((b) => [`${b.x},${b.y}`, b])).values(), - ); + // this.movableBlocks = Array.from( + // new Map(this.movableBlocks.map((b) => [`${b.x},${b.y}`, b])).values(), + // ); // オブジェクトの座標を更新 object.x = x; @@ -256,12 +252,13 @@ export class Grid { this.setBlock(i.x, i.y, Block.air); } } - setAllMovableBlocks(cx: Context) { + setAllMovableBlocks(cx: Context, newMovableBlocks: MovableBlocks) { + this.movableBlocks = []; const objectIds = Array.from( - new Set(this.movableBlocks.map((block) => block.objectId)), + new Set(newMovableBlocks.map((block) => block.objectId)), ); for (const objectId of objectIds) { - const retrievedBlocks = this.movableBlocks.filter( + const retrievedBlocks = newMovableBlocks.filter( (block) => block.objectId === objectId, ); const movableObject: MovableObject = { From f65c1b9a66c7c96aceedf107bd7bcad65d9ebc01 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Wed, 7 May 2025 11:02:30 +0900 Subject: [PATCH 16/17] =?UTF-8?q?=E8=83=BD=E5=8A=9B=E3=81=AE=E5=AE=9F?= =?UTF-8?q?=E8=A1=8C=E3=81=8C=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AFhistory=E3=81=AB=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ability.ts | 191 +++++++++++++++++++++++-------------------------- 1 file changed, 89 insertions(+), 102 deletions(-) diff --git a/src/ability.ts b/src/ability.ts index e984d21..9985216 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -76,7 +76,11 @@ export class AbilityControl { }; return this.focused; } - copy(cx: Context) { + copy( + cx: Context, + newHistory: History, + history: { list: History[]; index: number }, + ) { if (!this.focused) return; const x = this.focused.x; const y = this.focused.y; @@ -85,6 +89,8 @@ export class AbilityControl { const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; + this.pushHistory(cx, newHistory, history); + // コピー元とは別のオブジェクトとして管理する movableObject.objectId = self.crypto.randomUUID(); @@ -93,8 +99,23 @@ export class AbilityControl { ...prev, copy: --this.enabledAbilities.copy, })); + + this.pushHistory( + cx, + { + ...newHistory, + inventory: this.inventory, + enabledAbilities: this.enabledAbilities, + }, + history, + ); } - paste(cx: Context, facing: Facing) { + paste( + cx: Context, + facing: Facing, + newHistory: History, + history: { list: History[]; index: number }, + ) { if (!this.focused) return; if (!this.inventory /*|| this.inventory === Block.air*/) return; @@ -123,18 +144,36 @@ export class AbilityControl { } } + this.pushHistory(cx, newHistory, history); + cx.grid.setMovableObject(cx, x, y, this.inventory); if (!this.inventoryIsInfinite) { this.setInventory(cx, null); } + // const prevEnabled = { ...this.enabledAbilities }; cx.uiContext.update((prev) => ({ ...prev, paste: --this.enabledAbilities.paste, })); + + this.pushHistory( + cx, + { + ...newHistory, + inventory: this.inventory, + movableBlocks: cx.grid.movableBlocks, + enabledAbilities: this.enabledAbilities, + }, + history, + ); } - cut(cx: Context) { + cut( + cx: Context, + newHistory: History, + history: { list: History[]; index: number }, + ) { if (!this.focused) return; const x = this.focused.x; @@ -145,9 +184,12 @@ export class AbilityControl { const prevInventory = this.inventory; const movableObject = cx.grid.getMovableObject(x, y); if (!movableObject) return; + + this.pushHistory(cx, newHistory, history); + this.setInventory(cx, movableObject); - // cx.grid.movableBlocksとthis.inventryは重複しないように - // 取得したオブジェクトはcs.grid.movableBlocksから削除する + + // cx.movableBlocks を更新 cx.grid.movableBlocks = cx.grid.movableBlocks.filter( (block) => block.objectId !== movableObject.objectId, ); @@ -161,6 +203,17 @@ export class AbilityControl { ...prev, cut: --this.enabledAbilities.cut, })); + + this.pushHistory( + cx, + { + ...newHistory, + inventory: this.inventory, + movableBlocks: cx.grid.movableBlocks, + enabledAbilities: this.enabledAbilities, + }, + history, + ); } // History については、 `docs/history-stack.png` を参照のこと @@ -188,10 +241,10 @@ export class AbilityControl { // オブジェクトの状態について直前と一致するなら記録しない if ( (history.index < history.list.length && - JSON.stringify(history.list[history.index].movableBlocks) === - JSON.stringify(newHistory.movableBlocks)) || - JSON.stringify(history.list[history.list.length - 1].movableBlocks) === - JSON.stringify(newHistory.movableBlocks) + JSON.stringify(history.list[history.index]) === + JSON.stringify(newHistory)) || + JSON.stringify(history.list[history.list.length - 1]) === + JSON.stringify(newHistory) ) { return; } @@ -276,103 +329,37 @@ export class AbilityControl { if (!(e.ctrlKey || e.metaKey)) return undefined; if (this.enabledAbilities.paste > 0 && onGround && e.key === "v") { - this.pushHistory( - cx, - { - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - enabledAbilities: { - ...this.enabledAbilities, - paste: this.enabledAbilities.paste - 1, - }, - }, - history, - ); - this.paste(cx, facing); - this.pushHistory( - cx, - { - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - enabledAbilities: { - ...this.enabledAbilities, - paste: this.enabledAbilities.paste - 1, - }, - }, - history, - ); + const newHistory = { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + enabledAbilities: this.enabledAbilities, + }; + this.paste(cx, facing, newHistory, history); } if (this.enabledAbilities.copy > 0 && onGround && e.key === "c") { - this.pushHistory( - cx, - { - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - enabledAbilities: { - ...this.enabledAbilities, - copy: this.enabledAbilities.copy - 1, - }, - }, - history, - ); - this.copy(cx); - this.pushHistory( - cx, - { - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - enabledAbilities: { - ...this.enabledAbilities, - copy: this.enabledAbilities.copy - 1, - }, - }, - history, - ); + const newHistory = { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + enabledAbilities: this.enabledAbilities, + }; + this.copy(cx, newHistory, history); } if (this.enabledAbilities.cut > 0 && onGround && e.key === "x") { - this.pushHistory( - cx, - { - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - enabledAbilities: { - ...this.enabledAbilities, - cut: this.enabledAbilities.cut - 1, - }, - }, - history, - ); - this.cut(cx); - this.pushHistory( - cx, - { - playerX: playerAt.x, - playerY: playerAt.y, - inventory: this.inventory ? this.inventory : null, - playerFacing: facing, - movableBlocks: cx.grid.movableBlocks, - enabledAbilities: { - ...this.enabledAbilities, - cut: this.enabledAbilities.cut - 1, - }, - }, - history, - ); + const newHistory = { + playerX: playerAt.x, + playerY: playerAt.y, + inventory: this.inventory ? this.inventory : null, + playerFacing: facing, + movableBlocks: cx.grid.movableBlocks, + enabledAbilities: this.enabledAbilities, + }; + this.cut(cx, newHistory, history); } if (e.key === "z") { console.log(history); From 1aa35219babc13c0545a23383c7171d3beca8ef0 Mon Sep 17 00:00:00 2001 From: nakomochi Date: Thu, 8 May 2025 11:14:52 +0900 Subject: [PATCH 17/17] =?UTF-8?q?stage4=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/+page.svelte | 1 + src/stages.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/routes/+page.svelte b/routes/+page.svelte index 98f6ce0..db81617 100644 --- a/routes/+page.svelte +++ b/routes/+page.svelte @@ -6,3 +6,4 @@ Stage 1 Stage 2 Stage 3 +Stage 4 diff --git a/src/stages.ts b/src/stages.ts index 4d30702..a3764f3 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -117,4 +117,61 @@ export const stages = new Map([ ], }, ], + [ + "4", + { + stage: [ + "bbbbbbbbbbbbbbbbbbbbbb", + "......................", + "......................", + "m..................bbb", + "bb.................bbb", + "bb..........m......bbb", + "bb.........mm......bbb", + "bb..bb...bbbbb.....bbb", + "bb..bbb............bbb", + "bb..bbbb..m........bbb", + "bb..bbbbbbbbbbb....bbb", + ], + initialPlayerX: 5, + initialPlayerY: 6, + movableBlocks: [ + { + x: 0, + y: 3, + objectId: "1", + relativeX: 0, + relativeY: 0, + }, + { + x: 10, + y: 9, + objectId: "2", + relativeX: 0, + relativeY: 0, + }, + { + x: 11, + y: 6, + objectId: "3", + relativeX: 0, + relativeY: 0, + }, + { + x: 12, + y: 6, + objectId: "3", + relativeX: 1, + relativeY: 0, + }, + { + x: 12, + y: 5, + objectId: "3", + relativeX: 1, + relativeY: -1, + }, + ], + }, + ], ]);