diff --git a/routes/+page.svelte b/routes/+page.svelte
index d9dc78f..65b2c76 100644
--- a/routes/+page.svelte
+++ b/routes/+page.svelte
@@ -15,4 +15,6 @@
Stage 3-1
Stage 3-2
+ Stage 3-3
+ Stage 3-4
diff --git a/src/ability.ts b/src/ability.ts
index 4d2cace..18e6b11 100644
--- a/src/ability.ts
+++ b/src/ability.ts
@@ -5,17 +5,7 @@ import { createSnapshot } from "./history.ts";
import * as History from "./history.ts";
import type { AbilityInit, Context, Coords, MovableObject } from "./public-types.ts";
-export function init(cx: Context, options?: AbilityInit) {
- cx.state.update((prev) => ({
- ...prev,
- usage: options?.enabled ?? {
- copy: Number.POSITIVE_INFINITY,
- paste: Number.POSITIVE_INFINITY,
- cut: Number.POSITIVE_INFINITY,
- },
- inventoryIsInfinite: options?.inventoryIsInfinite ?? false,
- }));
-
+export function init(cx: Context) {
console.log("ability init");
document.addEventListener("copy", (e) => {
const { onGround } = cx.dynamic.player;
@@ -160,12 +150,13 @@ export function placeMovableObject(cx: Context, x: number, y: number, object: Mo
console.error("[placeMovableObject] cannot place object");
return;
}
+ const newObjectId = Math.random().toString();
for (const rel of object.relativePositions) {
const positionX = x + rel.x;
const positionY = y + rel.y;
grid.setBlock(cx, positionX, positionY, {
block: object.block,
- objectId: object.objectId,
+ objectId: newObjectId,
switchId: undefined,
});
}
diff --git a/src/constants.ts b/src/constants.ts
index b919bad..9bf8d03 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -21,8 +21,10 @@ export enum Block {
fallable = "fallable",
switch = "switch",
switchBase = "switch-base",
- switchingBlockOFF = "switching-block-off",
- switchingBlockON = "switching-block-on",
+ switchingBlockOFF = "switching-block-off", // 初期状態で出現している
+ switchingBlockON = "switching-block-on", // スイッチを押すと隠れる
+ inverseSwitchingBlockOFF = "inverse-switching-block-off", // 初期状態で隠れている
+ inverseSwitchingBlockON = "inverse-switching-block-on", // スイッチを押すと出現する
switchPressed = "switch-pressed",
spike = "spike",
goal = "goal",
@@ -46,6 +48,7 @@ export const BlockDefinitionMap = new Map([
["s", Block.switch],
["S", Block.switchBase],
["w", Block.switchingBlockOFF],
+ ["W", Block.inverseSwitchingBlockOFF],
["^", Block.spike],
["g", Block.goal],
]);
diff --git a/src/grid.ts b/src/grid.ts
index 60fcf2c..72ea568 100644
--- a/src/grid.ts
+++ b/src/grid.ts
@@ -40,7 +40,13 @@ export type GridCell =
}
| {
// switches / triggerable blocks
- block: Block.switch | Block.switchingBlockOFF | Block.switchingBlockON | Block.switchPressed;
+ block:
+ | Block.switch
+ | Block.switchingBlockOFF
+ | Block.switchingBlockON
+ | Block.inverseSwitchingBlockOFF
+ | Block.inverseSwitchingBlockON
+ | Block.switchPressed;
switchId?: string; // optional でいいの?
objectId?: unknown;
};
@@ -49,6 +55,7 @@ export class Grid {
__vsom: VirtualSOM;
marginY: number; // windowの上端とy=0上端の距離(px)
oobSprites: Sprite[]; // グリッド定義の外を埋めるやつ
+ oobFallableSprites: { sprite: Sprite | null; vy: number }[]; // グリッド定義外に落ちたfallable
constructor(
cx: {
_stage_container: Container;
@@ -61,6 +68,7 @@ export class Grid {
) {
const stage = cx._stage_container;
this.oobSprites = [];
+ this.oobFallableSprites = [];
this.marginY = (height - cellSize * stageDefinition.stage.length) / 2;
const vsprites: VirtualSOM = [];
@@ -104,6 +112,7 @@ export class Grid {
});
break;
}
+ case Block.inverseSwitchingBlockOFF:
case Block.switchingBlockOFF: {
const switchId = (
get(cx.state).cells[y][x] as {
@@ -126,6 +135,7 @@ export class Grid {
});
break;
}
+ case Block.inverseSwitchingBlockON:
case Block.switchingBlockON:
case Block.switchPressed:
throw new Error(`[Grid.constructor]: block is not supported: ${dblock}`);
@@ -226,6 +236,15 @@ export class Grid {
}
}
this.initOOBSprites(cx, cellSize);
+ this.clearFallableSprites(cx);
+ }
+ // 画面外のfallableはステージ内のfallableのコピーでありhistoryの対象ではないので、
+ // undoなどでステージ全体に変更を加えるときに使う
+ clearFallableSprites(cx: { _stage_container: Container }) {
+ for (const sprite of this.oobFallableSprites) {
+ if (sprite.sprite) cx._stage_container.removeChild(sprite.sprite);
+ }
+ this.oobFallableSprites = [];
}
getBlock(cx: Context, x: number, y: number): Block | null {
return get(cx.state).cells[y]?.[x]?.block ?? null;
@@ -307,7 +326,7 @@ export class Grid {
vprev.block = Block.switchPressed;
vprev.dy = 0;
vprev.vy = 0;
- } else if (vprev?.block === Block.switchPressed) {
+ } else if (vprev?.block === Block.switchPressed && cNewCell.block === Block.switch) {
// switchがプレイヤーに押されているのが戻るとき
assert(
cprev.block === Block.switchPressed || cprev.block === Block.switch,
@@ -328,7 +347,7 @@ export class Grid {
vprev.vy = 0;
}
// switch上にオブジェクトを置くとき
- else if (vprev?.block === Block.switch) {
+ else if (vprev?.block === Block.switch || vprev?.block === Block.switchPressed) {
if (cNewCell.block !== Block.movable && cNewCell.block !== Block.fallable) {
console.warn("No block other than movable cannot be placed on the switch");
console.log("cell.block", cNewCell.block);
@@ -338,7 +357,10 @@ export class Grid {
stage.addChild(movableSprite);
assert(cNewCell.objectId !== undefined, "movable block must have objectId");
assert(
- (cprev.block === Block.switch || cprev.block === Block.movable || cprev.block === Block.fallable) &&
+ (cprev.block === Block.switch ||
+ cprev.block === Block.switchPressed ||
+ cprev.block === Block.movable ||
+ cprev.block === Block.fallable) &&
cprev.switchId !== undefined,
"block is not switch",
);
@@ -354,6 +376,7 @@ export class Grid {
get(cx.state).switches.filter((s) => {
if (s.x === x && s.y === y) {
s.pressedByBlock = true;
+ s.pressedByPlayer = false;
}
return s;
});
@@ -391,50 +414,60 @@ export class Grid {
});
}
// switchingBlockOFFがONに切り替わるとき
- else if (vprev?.block === Block.switchingBlockOFF) {
- if (cNewCell.block !== Block.switchingBlockON) {
+ else if (vprev?.block === Block.switchingBlockOFF || vprev?.block === Block.inverseSwitchingBlockOFF) {
+ if (cNewCell.block !== Block.switchingBlockON && cNewCell.block !== Block.inverseSwitchingBlockON) {
console.warn("No block other than switchingBlockON cannot replace the switchingBlockOFF");
return;
}
assert(
- cprev.block === Block.switchingBlockOFF || cprev.block === Block.switchingBlockON,
+ cprev.block === Block.switchingBlockOFF ||
+ cprev.block === Block.switchingBlockON ||
+ cprev.block === Block.inverseSwitchingBlockOFF ||
+ cprev.block === Block.inverseSwitchingBlockON,
"block is not switchingBlock",
);
+ const inversed = vprev.block === Block.inverseSwitchingBlockOFF;
+ const switchONBlock = inversed ? Block.inverseSwitchingBlockON : Block.switchingBlockON;
const switchId = cprev.switchId;
if (!switchId) throw new Error("switchId is undefined");
- const blockSprite = createSprite(blockSize, Block.switchingBlockON, x, y, marginY, switchColor(switchId));
+ const blockSprite = createSprite(blockSize, switchONBlock, x, y, marginY, switchColor(switchId));
stage.addChild(blockSprite);
cells[y][x] = {
- block: Block.switchingBlockON,
+ block: switchONBlock,
switchId,
objectId: undefined,
};
vprev.sprite = blockSprite;
- vprev.block = Block.switchingBlockON;
+ vprev.block = switchONBlock;
vprev.dy = 0;
vprev.vy = 0;
}
// switchingBlockONがOFFに切り替わるとき
- else if (vprev?.block === Block.switchingBlockON) {
- if (cNewCell.block !== Block.switchingBlockOFF) {
+ else if (vprev?.block === Block.switchingBlockON || vprev?.block === Block.inverseSwitchingBlockON) {
+ if (cNewCell.block !== Block.switchingBlockOFF && cNewCell.block !== Block.inverseSwitchingBlockOFF) {
console.warn("No block other than switchingBlockOFF cannot replace the switchingBlockON");
return;
}
assert(
- cprev.block === Block.switchingBlockOFF || cprev.block === Block.switchingBlockON,
+ cprev.block === Block.switchingBlockOFF ||
+ cprev.block === Block.switchingBlockON ||
+ cprev.block === Block.inverseSwitchingBlockOFF ||
+ cprev.block === Block.inverseSwitchingBlockON,
"block is not switchingBlock",
);
+ const inversed = vprev.block === Block.inverseSwitchingBlockON;
+ const switchOFFBlock = inversed ? Block.inverseSwitchingBlockOFF : Block.switchingBlockOFF;
const switchId = cprev.switchId;
if (!switchId) throw new Error("switchId is undefined");
- const blockSprite = createSprite(blockSize, Block.switchingBlockOFF, x, y, marginY, switchColor(switchId));
+ const blockSprite = createSprite(blockSize, switchOFFBlock, x, y, marginY, switchColor(switchId));
stage.addChild(blockSprite);
cells[y][x] = {
- block: Block.switchingBlockOFF,
+ block: switchOFFBlock,
switchId,
objectId: undefined,
};
vprev.sprite = blockSprite;
- vprev.block = Block.switchingBlockOFF;
+ vprev.block = switchOFFBlock;
vprev.dy = 0;
vprev.vy = 0;
} else {
@@ -507,6 +540,19 @@ export class Grid {
tick(cx: Context, ticker: Ticker) {
const { blockSize, gridX, gridY, marginY } = get(cx.config);
const cells = get(cx.state).cells;
+
+ for (const s of this.oobFallableSprites) {
+ if (s.sprite) {
+ s.sprite.y += s.vy * ticker.deltaTime;
+ s.vy += consts.gravity * blockSize * ticker.deltaTime;
+ if (s.sprite.y > gridY * blockSize + marginY * 2) {
+ // 画面外に出たら消す
+ s.sprite.parent?.removeChild(s.sprite);
+ s.sprite = null;
+ }
+ }
+ }
+
for (let y = gridY - 1; y >= 0; y--) {
for (let x = 0; x < gridX; x++) {
const vcell = this.__vsom[y][x];
@@ -522,14 +568,20 @@ export class Grid {
}
let swapDiff = 0;
+ let goOOB = false;
while (swapDiff * blockSize <= vcell.dy) {
// 下にブロックがあるなどの要因で止まる
if (!isAvail(cells, x, y + swapDiff + 1)) break;
vcell.dy -= blockSize;
swapDiff++;
+ if (y + swapDiff >= cells.length) {
+ goOOB = true;
+ break;
+ }
}
+
// これ以上下に行けない
- if (!isAvail(cells, x, y + swapDiff)) {
+ if (!goOOB && !isAvail(cells, x, y + swapDiff)) {
// 着地 (dy はたいてい 0 未満なので、別で判定が必要)
if (vcell.dy >= 0) {
vcell.dy = 0;
@@ -538,7 +590,18 @@ export class Grid {
}
vcell.sprite.y = y * blockSize + marginY + vcell.dy;
- if (swapDiff > 0) {
+
+ if (goOOB) {
+ // vcell.sprite はsetBlockで消えてしまうので、あたらしく作る
+ const oobSprite = createSprite(blockSize, Block.fallable, x, y, marginY);
+ oobSprite.y = (y + swapDiff) * blockSize + marginY + vcell.dy;
+ cx._stage_container.addChild(oobSprite);
+ this.oobFallableSprites.push({
+ sprite: oobSprite,
+ vy: vcell.vy,
+ });
+ this.setBlock(cx, x, y, { block: null });
+ } else if (swapDiff > 0) {
this.setBlock(cx, x, y, { block: null });
this.setBlock(cx, x, y + swapDiff, ccell);
const vSwapCell = this.__vsom[y + swapDiff][x];
@@ -561,8 +624,7 @@ function isAvail(cells: GridCell[][], x: number, y: number) {
switch (true) {
case y >= cells.length:
// 床が抜けている
- // どうする?
- return false;
+ return true;
case cell.block === null || cell.block === Block.switch:
// 下にブロックがない
return true;
@@ -609,6 +671,8 @@ export function createCellsFromStageDefinition(stageDefinition: StageDefinition)
}
// switches
case Block.switch:
+ case Block.inverseSwitchingBlockON:
+ case Block.inverseSwitchingBlockOFF:
case Block.switchingBlockON:
case Block.switchingBlockOFF: {
const group = stageDefinition.switchGroups.find((b) => b.x === x && b.y === y);
@@ -681,6 +745,7 @@ function createSprite(
updateSprite(switchBaseSprite, blockSize, x, y, marginY, 0);
return switchBaseSprite;
}
+ case Block.inverseSwitchingBlockON:
case Block.switchingBlockOFF: {
const sprite = new Sprite(rockTexture);
if (switchColor) sprite.tint = switchColor;
@@ -688,6 +753,7 @@ function createSprite(
updateSprite(sprite, blockSize, x, y, marginY, 0);
return sprite;
}
+ case Block.inverseSwitchingBlockOFF:
case Block.switchingBlockON: {
const sprite = new Sprite(rockTexture);
if (switchColor) sprite.tint = switchColor;
diff --git a/src/history.ts b/src/history.ts
index 1d4cfa9..698b673 100644
--- a/src/history.ts
+++ b/src/history.ts
@@ -105,6 +105,7 @@ function restore(cx: Context, ss: StateSnapshot) {
cx.dynamic.player.y = ss.playerY;
cx.dynamic.player.facing = ss.playerFacing;
cx.grid.diffAndUpdateTo(cx, ss.game.cells);
+ cx.grid.clearFallableSprites(cx);
printCells(ss.game.cells, "restore");
}
function stash(cx: Context) {
diff --git a/src/main.ts b/src/main.ts
index 1910142..5b3561b 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -73,11 +73,11 @@ export async function setup(
};
const initialGameState = {
inventory: null,
- inventoryIsInfinite: false,
+ inventoryIsInfinite: !!stageDefinition.inventoryIsInfinite,
usage: stageDefinition.usage ?? {
copy: 0,
- cut: Infinity,
- paste: Infinity,
+ cut: Number.POSITIVE_INFINITY,
+ paste: Number.POSITIVE_INFINITY,
},
cells: createCellsFromStageDefinition(stageDefinition),
paused: false,
diff --git a/src/player.ts b/src/player.ts
index f7da457..3dfc261 100644
--- a/src/player.ts
+++ b/src/player.ts
@@ -7,13 +7,7 @@ import { Block } from "./constants.ts";
import type { AbilityInit, Context } from "./public-types.ts";
import { highlightHoldTexture, highlightTexture } from "./resources.ts";
-export function init(
- cx: Context,
- spriteOptions?: SpriteOptions | Texture,
- options?: {
- ability?: AbilityInit;
- },
-) {
+export function init(cx: Context, spriteOptions?: SpriteOptions | Texture) {
const sprite = new Sprite(spriteOptions);
// Center the sprite's anchor point
sprite.anchor.set(0.5, 1);
@@ -36,7 +30,7 @@ export function init(
document.addEventListener("keydown", (event) => handleInput(cx, event, true));
document.addEventListener("keyup", (event) => handleInput(cx, event, false));
console.log("player init");
- Ability.init(cx, options?.ability);
+ Ability.init(cx);
return {
sprite,
get coords() {
@@ -185,6 +179,7 @@ export function tick(cx: Context, ticker: Ticker) {
cx.grid.getBlock(cx, Math.floor(x), Math.floor(y)) !== Block.switch &&
cx.grid.getBlock(cx, Math.floor(x), Math.floor(y)) !== Block.switchPressed &&
cx.grid.getBlock(cx, Math.floor(x), Math.floor(y)) !== Block.switchingBlockON &&
+ cx.grid.getBlock(cx, Math.floor(x), Math.floor(y)) !== Block.inverseSwitchingBlockOFF &&
cx.grid.getBlock(cx, Math.floor(x), Math.floor(y)) !== Block.goal &&
cx.grid.getBlock(cx, Math.floor(x), Math.floor(y)) !== undefined;
const isSwitchBase = (x: number, y: number) =>
@@ -254,7 +249,10 @@ export function tick(cx: Context, ticker: Ticker) {
pressedByPlayer: true,
};
}
- return s;
+ return {
+ ...s,
+ pressedByPlayer: false,
+ };
});
return prev;
});
@@ -282,14 +280,18 @@ export function tick(cx: Context, ticker: Ticker) {
// スイッチの状態を反映
const switches = get(cx.state).switches;
- for (const s of switches) {
- const switchingBlock = get(cx.state).switchingBlocks.filter((sb) => sb.id === s.id);
+ const switchIds = [...new Set(switches.map((s) => s.id))]; // 重複削除
+ for (const sId of switchIds) {
+ const switchingBlock = get(cx.state).switchingBlocks.filter((sb) => sb.id === sId);
// スイッチが押されているとき
- if (s.pressedByPlayer || s.pressedByBlock) {
+ if (switches.filter((s) => s.id === sId).some((s) => s.pressedByPlayer || s.pressedByBlock)) {
for (const sb of switchingBlock) {
if (cx.grid.getBlock(cx, sb.x, sb.y) === Block.switchingBlockOFF) {
cx.grid.setBlock(cx, sb.x, sb.y, { block: Block.switchingBlockON });
}
+ if (cx.grid.getBlock(cx, sb.x, sb.y) === Block.inverseSwitchingBlockOFF) {
+ cx.grid.setBlock(cx, sb.x, sb.y, { block: Block.inverseSwitchingBlockON });
+ }
}
} else {
// スイッチが押されていないとき
@@ -297,6 +299,9 @@ export function tick(cx: Context, ticker: Ticker) {
if (cx.grid.getBlock(cx, sb.x, sb.y) === Block.switchingBlockON) {
cx.grid.setBlock(cx, sb.x, sb.y, { block: Block.switchingBlockOFF });
}
+ if (cx.grid.getBlock(cx, sb.x, sb.y) === Block.inverseSwitchingBlockON) {
+ cx.grid.setBlock(cx, sb.x, sb.y, { block: Block.inverseSwitchingBlockOFF });
+ }
}
}
}
diff --git a/src/public-types.ts b/src/public-types.ts
index 3702d12..2e803b9 100644
--- a/src/public-types.ts
+++ b/src/public-types.ts
@@ -105,7 +105,6 @@ export type Coords = {
// Ability
export type AbilityInit = {
enabled?: AbilityUsage;
- inventoryIsInfinite?: boolean;
};
export type AbilityUsage = {
// 回数 or Number.POSITIVE_INFINITY
diff --git a/src/stage-preprocessor.ts b/src/stage-preprocessor.ts
index 95d5b46..4769e81 100644
--- a/src/stage-preprocessor.ts
+++ b/src/stage-preprocessor.ts
@@ -128,6 +128,6 @@ function validate(input: PreprocessInput) {
function validateLen(stage: Stage, expectY: number, expectX: number) {
assert(stage.length === expectY, `stage length do not equal: expected ${expectY}, got ${stage.length}`);
for (const row of stage) {
- assert(row.length === expectX, `stage rows' lengths are not equal: expected ${length}, got ${row.length}`);
+ assert(row.length === expectX, `stage rows' lengths are not equal: expected ${expectX}, got ${row.length}`);
}
}
diff --git a/src/stages.ts b/src/stages.ts
index 84c57d6..5dc6b7b 100644
--- a/src/stages.ts
+++ b/src/stages.ts
@@ -22,6 +22,8 @@ export const stages = new Map([
["8", stage8],
["3-1", world3.stage1],
["3-2", world3.stage2],
+ ["3-3", world3.stage3],
+ ["3-4", world3.stage4],
["4-1", world4.stage1],
["4-2", world4.stage2],
["4-3", world4.stage3],
diff --git a/src/stages/type.ts b/src/stages/type.ts
index a9183a5..e4d7380 100644
--- a/src/stages/type.ts
+++ b/src/stages/type.ts
@@ -26,6 +26,7 @@ export type StageDefinition = {
isTutorial?: boolean;
initialPlayerX: number; // 左端から0-indexed
initialPlayerY: number; // 上端から0-indexed +1すると浮かずに地面に立つ
+ inventoryIsInfinite?: boolean;
// ブロックと fallable のグループ
// 複数ブロックからなるオブジェクトについては明示的に指定
blockGroups: BlockGroup[];
diff --git a/src/stages/world3.ts b/src/stages/world3.ts
index c74d1b0..71acb76 100644
--- a/src/stages/world3.ts
+++ b/src/stages/world3.ts
@@ -5,47 +5,116 @@ export namespace world3 {
stage: [
"bbbbbbbbbbbbbbbbbb",
".........b........",
- ".........b........",
- ".........w........",
- ".s....f..w.....g..",
- "bSbbbbbbbbbbbbbbbb",
+ ".........b......g.",
+ ".........b...bbbbb",
+ ".........b...bbbbb",
+ ".........m...bbbbb",
+ "bbbbbbbbbbbbbbbbbb",
],
- initialPlayerX: 3,
- initialPlayerY: 5,
+ isTutorial: false,
+ initialPlayerX: 1,
+ initialPlayerY: 6,
+ inventoryIsInfinite: true,
blockGroups: [],
- switchGroups: [
- {
- x: 1,
- y: 4,
- switchId: "1",
- },
- {
- x: 9,
- y: 3,
- switchId: "1",
- },
- {
- x: 9,
- y: 4,
- switchId: "1",
- },
- ],
+ switchGroups: [],
+ usage: {
+ copy: Number.POSITIVE_INFINITY,
+ cut: Number.POSITIVE_INFINITY,
+ paste: Number.POSITIVE_INFINITY,
+ },
};
- // fallable+スイッチのテスト用 あとでけす
export const stage2: StageDefinition = {
stage: [
"bbbbbbbbbbbbbbbbbb",
"..................",
- "........bb.....g..",
- ".......b.....bbbbb",
- "......b......bbbbb",
- ".....b.......bbbbb",
- "....b........bbbbb",
- "bbbbbbbbbb.bbbbbbb",
+ "................g.",
+ ".............bbbbb",
+ "......f......bbbbb",
+ "......f......bbbbb",
+ "......f......bbbbb",
+ "bbbbbbbbb.bb.bbbbb",
],
+ isTutorial: false,
initialPlayerX: 1,
- initialPlayerY: 2,
+ initialPlayerY: 6,
+ inventoryIsInfinite: true,
blockGroups: [],
switchGroups: [],
+ usage: {
+ copy: Number.POSITIVE_INFINITY,
+ cut: Number.POSITIVE_INFINITY,
+ paste: Number.POSITIVE_INFINITY,
+ },
+ };
+ export const stage3: StageDefinition = {
+ stage: [
+ "bbbbbbbbbbbbbbbbb",
+ "g...W........w..b",
+ "b...bb..bb..bb..b",
+ "b...bb..bb..bb..b",
+ "b....bf..bs..bm.b",
+ "b..bbbbbbbSbbbbbb",
+ ],
+ isTutorial: false,
+ initialPlayerX: 5,
+ initialPlayerY: 1,
+ inventoryIsInfinite: true,
+ blockGroups: [],
+ switchGroups: [
+ { x: 4, y: 1, switchId: "1" },
+ { x: 13, y: 1, switchId: "1" },
+ { x: 10, y: 4, switchId: "1" },
+ ],
+ usage: {
+ copy: Number.POSITIVE_INFINITY,
+ cut: Number.POSITIVE_INFINITY,
+ paste: Number.POSITIVE_INFINITY,
+ },
+ };
+ export const stage4: StageDefinition = {
+ stage: [
+ // 2345678901234567890123
+ "bbbbbbbbbbbbbbbbbbbbbbbb",
+ "bbbb..W..w.....bmmmmW...", // 1
+ "bbbb..bb.w.........W....", // 2
+ ".......b.bbbbbbbbbbb....", // 3
+ ".s............w....w....", // 4
+ "bSbbb.........w..m.w...b", // 5
+ "bbbbb.........bbbbbb..bb", // 6
+ "bbbbb.........bg.....bbb", // 7
+ "bbbbbssss...f.b.....bbbb", // 8
+ "bbbbbSSSSbbbbbb....bbbbb",
+ ],
+ isTutorial: false,
+ initialPlayerX: 10,
+ initialPlayerY: 8,
+ inventoryIsInfinite: true,
+ blockGroups: [
+ { x: 16, y: 1, objectId: "1" },
+ { x: 17, y: 1, objectId: "1" },
+ { x: 18, y: 1, objectId: "1" },
+ { x: 19, y: 1, objectId: "1" },
+ ],
+ switchGroups: [
+ { x: 5, y: 8, switchId: "1" },
+ { x: 6, y: 8, switchId: "1" },
+ { x: 7, y: 8, switchId: "1" },
+ { x: 8, y: 8, switchId: "1" },
+ { x: 1, y: 4, switchId: "2" },
+ { x: 6, y: 1, switchId: "1" },
+ { x: 9, y: 1, switchId: "1" },
+ { x: 9, y: 2, switchId: "1" },
+ { x: 14, y: 4, switchId: "2" },
+ { x: 14, y: 5, switchId: "2" },
+ { x: 19, y: 4, switchId: "2" },
+ { x: 19, y: 5, switchId: "2" },
+ { x: 19, y: 2, switchId: "2" },
+ { x: 20, y: 1, switchId: "2" },
+ ],
+ usage: {
+ copy: Number.POSITIVE_INFINITY,
+ cut: Number.POSITIVE_INFINITY,
+ paste: Number.POSITIVE_INFINITY,
+ },
};
}