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/ability.ts b/src/ability.ts index 2edea7c..9985216 100644 --- a/src/ability.ts +++ b/src/ability.ts @@ -1,5 +1,7 @@ -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"; +import type { History } from "./player.ts"; export type Coords = { x: number; @@ -15,28 +17,14 @@ export type AbilityEnableOptions = { paste: number; cut: number; }; -type History = { - at: { x: number; y: number }; - from: Block; - to: Block; - inventory: { - before: Block | null; - after: Block | null; - }; - enabled: { - before: AbilityEnableOptions; - after: AbilityEnableOptions; - }; -}; + export class AbilityControl { - history: History[] = []; - historyIndex = 0; - inventory: Block | null = null; - inventoryIsInfinite: boolean; - enabled: AbilityEnableOptions; + inventory: MovableObject | null = null; + inventoryIsInfinite = false; + 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, @@ -46,25 +34,25 @@ export class AbilityControl { ...prev, inventory: this.inventory, inventoryIsInfinite: this.inventoryIsInfinite, - ...this.enabled, - undo: 0, + ...this.enabledAbilities, + undo: 1, redo: 0, })); - document.addEventListener("copy", (e) => { - e.preventDefault(); - if (this.enabled.copy > 0) this.copy(cx); - }); - document.addEventListener("cut", (e) => { - e.preventDefault(); - if (this.enabled.cut > 0) this.cut(cx); - }); - document.addEventListener("paste", (e) => { - e.preventDefault(); - if (this.enabled.paste > 0) this.paste(cx); - }); + // document.addEventListener("copy", (e) => { + // e.preventDefault(); + // if (this.enabled.copy > 0) this.copy(cx); + // }); + // document.addEventListener("cut", (e) => { + // e.preventDefault(); + // if (this.enabled.cut > 0) this.cut(cx); + // }); + // document.addEventListener("paste", (e) => { + // e.preventDefault(); + // if (this.enabled.paste > 0) this.paste(cx, facing); + // }); } - setInventory(cx: Context, inventory: Block | null) { - this.inventory = inventory; + setInventory(cx: Context, inventory: MovableObject | null) { + this.inventory = JSON.parse(JSON.stringify(inventory)); cx.uiContext.update((prev) => ({ ...prev, inventory, @@ -88,134 +76,308 @@ export class AbilityControl { }; return this.focused; } - copy(cx: Context) { + copy( + cx: Context, + newHistory: History, + history: { list: History[]; index: number }, + ) { 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.setInventory(cx, target); + const movableObject = cx.grid.getMovableObject(x, y); + if (!movableObject) return; + + this.pushHistory(cx, newHistory, history); + + // コピー元とは別のオブジェクトとして管理する + movableObject.objectId = self.crypto.randomUUID(); + + this.setInventory(cx, movableObject); cx.uiContext.update((prev) => ({ ...prev, - copy: --this.enabled.copy, + copy: --this.enabledAbilities.copy, })); + + this.pushHistory( + cx, + { + ...newHistory, + inventory: this.inventory, + enabledAbilities: this.enabledAbilities, + }, + history, + ); } - paste(cx: Context) { + paste( + cx: Context, + facing: Facing, + newHistory: History, + history: { list: History[]; index: number }, + ) { if (!this.focused) return; - if (!this.inventory || this.inventory === 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.setBlock(cx, this.focused.x, this.focused.y, this.inventory); + if (!this.inventory /*|| this.inventory === Block.air*/) return; + + // 左向きのときにブロックを配置する位置を変更するのに使用 + 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; + + 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; + } + } + + this.pushHistory(cx, newHistory, history); + + cx.grid.setMovableObject(cx, x, y, this.inventory); + 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 }, - from: Block.air, - to: prevInventory, - inventory: { - before: prevInventory, - after: this.inventory, - }, - enabled: { - before: prevEnabled, - after: this.enabled, + + 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 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 prevInventory = this.inventory; - this.setInventory(cx, target); - cx.grid.setBlock(cx, this.focused.x, this.focused.y, Block.air); - const prevEnabled = { ...this.enabled }; + const movableObject = cx.grid.getMovableObject(x, y); + if (!movableObject) return; + + this.pushHistory(cx, newHistory, history); + + this.setInventory(cx, movableObject); + + // cx.movableBlocks を更新 + 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(positionX, positionY, Block.air); + } + const prevEnabled = { ...this.enabledAbilities }; cx.uiContext.update((prev) => ({ ...prev, - cut: --this.enabled.cut, + cut: --this.enabledAbilities.cut, })); - this.pushHistory(cx, { - at: { ...this.focused }, - from: target, - to: Block.air, - inventory: { - before: prevInventory, - after: target, - }, - enabled: { - before: prevEnabled, - after: this.enabled, + + this.pushHistory( + cx, + { + ...newHistory, + inventory: this.inventory, + movableBlocks: cx.grid.movableBlocks, + enabledAbilities: this.enabledAbilities, }, - }); + history, + ); } // History については、 `docs/history-stack.png` を参照のこと - pushHistory(cx: Context, h: History) { - this.history = this.history.slice(0, this.historyIndex); - this.history.push(h); - this.historyIndex = this.history.length; - console.log(`history: ${this.historyIndex} / ${this.history.length}`); + pushHistory( + cx: Context, + h: History, + history: { + list: History[]; + index: number; + }, + ) { + // history.listの先頭(初期状態)は残す + history.list = history.list.slice(0, Math.max(history.index, 1)); + // Infinityはnullにならないように置換してDeepCopy + const newHistory = JSON.parse( + JSON.stringify(h, (k, v) => + v === Number.POSITIVE_INFINITY ? "Infinity" : v, + ), + (k, v) => (v === "Infinity" ? Number.POSITIVE_INFINITY : v), + ); + + console.log(history); + console.log(newHistory); + + // オブジェクトの状態について直前と一致するなら記録しない + if ( + (history.index < history.list.length && + JSON.stringify(history.list[history.index]) === + JSON.stringify(newHistory)) || + JSON.stringify(history.list[history.list.length - 1]) === + JSON.stringify(newHistory) + ) { + return; + } + + history.list.push(newHistory); + + history.index = history.list.length; + console.log(`history: ${history.index} / ${history.list.length}`); cx.uiContext.update((prev) => ({ ...prev, - undo: this.historyIndex, + undo: history.index, redo: 0, })); } - undo(cx: Context) { - 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); - this.setInventory(cx, op.inventory.before); - this.enabled = op.enabled.before; + undo( + cx: Context, + history: { + list: History[]; + index: number; + }, + ) { + if (history.index <= 0) return; + history.index--; // undo は、巻き戻し後の index で計算する + const op = history.list[history.index]; + + // すべてのオブジェクトを削除 + cx.grid.clearAllMovableBlocks(); + + // オブジェクトを配置 + this.setInventory(cx, op.inventory); + cx.grid.setAllMovableBlocks(cx, op.movableBlocks); + + this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ ...prev, - ...this.enabled, - undo: this.historyIndex, - redo: this.history.length - this.historyIndex, + ...this.enabledAbilities, + undo: history.index, + redo: history.list.length - history.index, })); - 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 - this.setInventory(cx, op.inventory.after); - cx.grid.setBlock(cx, op.at.x, op.at.y, op.to); - this.enabled = op.enabled.after; + 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(); + + // オブジェクトを配置 + this.setInventory(cx, op.inventory); + cx.grid.setAllMovableBlocks(cx, op.movableBlocks); + + this.enabledAbilities = op.enabledAbilities; cx.uiContext.update((prev) => ({ ...prev, - ...this.enabled, - undo: this.historyIndex, - redo: this.history.length - this.historyIndex, + ...this.enabledAbilities, + undo: history.index, + redo: history.list.length - history.index, })); - console.log(`history: ${this.historyIndex} / ${this.history.length}`); + + console.log(`history: ${history.index} / ${history.list.length}`); } - handleKeyDown(cx: Context, e: KeyboardEvent /*, onGround: boolean*/) { - if (!(e.ctrlKey || e.metaKey)) return; - - // if (this.enabled.paste > 0 && onGround && e.key === "v") { - // this.paste(cx); - // } - // if (this.enabled.copy > 0 && onGround && e.key === "c") { - // this.copy(cx); - // } - // if (this.enabled.cut > 0 && onGround && e.key === "x") { - // this.cut(cx); - // } + handleKeyDown( + cx: Context, + e: KeyboardEvent, + onGround: boolean, + facing: Facing, + history: { + list: History[]; + index: number; + }, + playerAt: Coords, + ) { + if (!(e.ctrlKey || e.metaKey)) return undefined; + + if (this.enabledAbilities.paste > 0 && onGround && e.key === "v") { + 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") { + 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") { + 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") { - this.undo(cx); + console.log(history); + 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); + 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/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 f6de514..1017de0 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,7 @@ import type { Container } from "pixi.js"; import type { Writable } from "svelte/store"; import type { Block } from "./constants.ts"; -import type { Grid } from "./grid.ts"; +import type { Grid, MovableObject } from "./grid.ts"; export type Context = { stage: Container; @@ -13,6 +13,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; @@ -20,7 +24,7 @@ export type Context = { }; export type UIContext = { - inventory: Block | null; + inventory: MovableObject | null; inventoryIsInfinite: boolean; copy: number; paste: number; diff --git a/src/grid.ts b/src/grid.ts index cadd79c..326a2ec 100644 --- a/src/grid.ts +++ b/src/grid.ts @@ -6,17 +6,46 @@ import type { StageDefinition } from "./stages.ts"; type GridCell = | { - block: Block.block | Block.movable; + block: Block.block; sprite: Sprite; } + | { + block: Block.movable; + sprite: Sprite; + objectId: string; + relativePosition: { + x: number; + y: number; + }; + } | { block: Block.air; sprite: null; }; +export type MovableBlocks = { + x: number; + y: number; + objectId: string; + // 基準ブロックからの相対位置 + relativeX: number; + relativeY: number; +}[]; + +export type MovableObject = { + objectId: string; + x: number; + y: number; + relativePositions: { + x: number; + y: number; + }[]; +}; + export class Grid { private stage: Container; cells: GridCell[][]; + movableBlocks: MovableBlocks; marginY: number; // windowの上端とy=0上端の距離(px) oobSprites: Sprite[]; constructor( @@ -26,11 +55,12 @@ export class Grid { stageDefinition: StageDefinition, ) { this.stage = stage; + this.movableBlocks = stageDefinition.movableBlocks; this.oobSprites = []; - this.marginY = (height - cellSize * stageDefinition.length) / 2; + this.marginY = (height - cellSize * stageDefinition.stage.length) / 2; 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]; @@ -41,6 +71,23 @@ export class Grid { sprite: null, }; row.push(cell); + } else if (block === Block.movable) { + const sprite = createSprite(cellSize, block, x, y, this.marginY); + 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, this.marginY); stage.addChild(sprite); @@ -112,7 +159,33 @@ export class Grid { getBlock(x: number, y: number): Block | undefined { return this.cells[y]?.[x]?.block; } - setBlock(cx: Context, x: number, y: number, block: Block) { + getMovableObject(x: number, y: number): MovableObject | undefined { + const cell = this.cells[y]?.[x]; + if (!cell) return undefined; + if (cell.block !== Block.movable) return undefined; + const objectId = cell.objectId; + const retrievedBlocks = this.movableBlocks.filter( + (block) => block.objectId === objectId, + ); + if (!retrievedBlocks) return undefined; + 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, + })), + }; + if (!retrievedObject) return undefined; + + return retrievedObject; + } + setBlock(x: number, y: number, block: Block) { const prev = this.cells[y][x]; if (block === prev.block) return; if (prev.block !== Block.air) { @@ -123,13 +196,90 @@ export class Grid { block, sprite: null, }; - } else { - const sprite = createSprite(cx.blockSize, block, x, y, this.marginY); + } + } + // 指定された座標にオブジェクトを配置し、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); + } + + for (const i of object.relativePositions) { + const positionX = x + i.x; + const positionY = y + i.y; + const sprite = createSprite( + cx.blockSize, + Block.movable, + positionX, + positionY, + cx.marginY, + ); 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, + }, + }; + 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; + } + 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); + } + this.setBlock(i.x, i.y, Block.air); + } + } + setAllMovableBlocks(cx: Context, newMovableBlocks: MovableBlocks) { + this.movableBlocks = []; + const objectIds = Array.from( + new Set(newMovableBlocks.map((block) => block.objectId)), + ); + for (const objectId of objectIds) { + const retrievedBlocks = newMovableBlocks.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/main.ts b/src/main.ts index f9c158d..876bca0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -39,8 +39,8 @@ export async function setup( 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 }); @@ -56,6 +56,8 @@ export async function setup( gridY, marginY: grid.marginY, blockSize, + initialPlayerX: stageDefinition.initialPlayerX, + initialPlayerY: stageDefinition.initialPlayerY, grid, elapsed: 0, uiContext, diff --git a/src/player.ts b/src/player.ts index 6e62e85..56d7ca1 100644 --- a/src/player.ts +++ b/src/player.ts @@ -1,10 +1,28 @@ import { Sprite, type SpriteOptions, type Texture, type Ticker } from "pixi.js"; import { AbilityControl, type AbilityInit } from "./ability.ts"; +import type { AbilityEnableOptions } 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"; +export type History = { + playerX: number; + playerY: number; + playerFacing: c.Facing; + inventory: MovableObject | null; + movableBlocks: { + x: number; + y: number; + objectId: string; + // 基準ブロックからの相対位置 + relativeX: number; + relativeY: number; + }[]; + enabledAbilities: AbilityEnableOptions; +}; + enum Inputs { Left = 0, Right = 1, @@ -20,6 +38,10 @@ export class Player { jumpingBegin: number | null; facing: c.Facing = c.Facing.right; ability: AbilityControl; + history = { + list: [] as History[], + index: 1, + }; constructor( cx: Context, spriteOptions?: SpriteOptions | Texture, @@ -30,9 +52,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 + cx.marginY; + + this.sprite.x = cx.blockSize * cx.initialPlayerX; + this.sprite.y = cx.blockSize * cx.initialPlayerY + cx.marginY; this.sprite.width = c.playerWidth * cx.blockSize; this.sprite.height = c.playerHeight * cx.blockSize; @@ -49,6 +71,14 @@ 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, + enabledAbilities: this.ability.enabledAbilities, + }); } get x() { return this.sprite.x; @@ -84,7 +114,18 @@ export class Player { } handleInput(_cx: Context, event: KeyboardEvent, eventIsKeyDown: boolean) { if (eventIsKeyDown) { - this.ability.handleKeyDown(_cx, event /*, this.onGround*/); + const playerPosition = this.ability.handleKeyDown( + _cx, + event, + this.onGround, + this.facing, + this.history, + { x: this.x, y: this.y }, + ); + if (playerPosition) { + this.x = playerPosition.x; + this.y = playerPosition.y; + } } switch (event.key) { case "Control": diff --git a/src/stages.ts b/src/stages.ts index 69473bf..a3764f3 100644 --- a/src/stages.ts +++ b/src/stages.ts @@ -1,38 +1,177 @@ -export type StageDefinition = string[]; +export type StageDefinition = { + stage: string[]; + initialPlayerX: number; + initialPlayerY: number; + movableBlocks: { + x: number; + y: number; + objectId: string; + // 基準ブロックからの相対位置 + // 基準ブロックは原則オブジェクトの左下で + // 右を向くときに目の前に来るブロック + relativeX: number; + relativeY: 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: 5, + movableBlocks: [ + { + x: 9, + y: 4, + objectId: "1", + relativeX: 0, + relativeY: 0, + }, + ], + }, ], [ "2", - [ - "bbbbbbbbbbbbbbbbbb", - "..................", - "..................", - "m............bbbbb", - "bb...........bbbbb", - "bb.....m.....bbbbb", - "bbbbbbbbbbbbbbbbbb", - ], + { + stage: [ + "bbbbbbbbbbbbbbbbbb", + "..................", + "..................", + "m............bbbbb", + "bb...........bbbbb", + "bb.....m.....bbbbb", + "bbbbbbbbbbbbbbbbbb", + ], + initialPlayerX: 3, + initialPlayerY: 6, + movableBlocks: [ + { + x: 0, + y: 3, + objectId: "1", + relativeX: 0, + relativeY: 0, + }, + { + x: 7, + y: 5, + objectId: "2", + relativeX: 0, + relativeY: 0, + }, + ], + }, ], [ "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: 6, + 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, + }, + ], + }, + ], + [ + "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, + }, + ], + }, ], ]); diff --git a/src/ui-components/Game.svelte b/src/ui-components/Game.svelte index e02a10f..ab5c947 100644 --- a/src/ui-components/Game.svelte +++ b/src/ui-components/Game.svelte @@ -52,7 +52,7 @@ $effect(() => { Clipboard:
- {#if $uiContext.inventory === Block.movable} + {#if $uiContext.inventory !== null}