Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9093ca5
fallableを追加
na-trium-144 May 13, 2025
9f8c188
highlight周りのtick関数を名前変更&移動
na-trium-144 May 14, 2025
6101831
Revert "highlight周りのtick関数を名前変更&移動"
na-trium-144 May 14, 2025
7e46429
grid.tick()とfallableの落下処理を実装
na-trium-144 May 14, 2025
f7d590a
pasteやredo時にdyをリセット
na-trium-144 May 14, 2025
5891bb4
switchWithObjectを削除し、movableとfallableにswitchIdを追加
na-trium-144 May 15, 2025
fc215de
tick()とctrl+Z時の動作をfallable+スイッチに対応
na-trium-144 May 15, 2025
ab1a0ee
fallableの速度が保存されていなかった
na-trium-144 May 15, 2025
0431547
Merge remote-tracking branch 'origin/main' into fallable
na-trium-144 May 15, 2025
31431ca
同じidの複数のスイッチを設置可能にする
na-trium-144 May 16, 2025
976f43a
Merge branch 'main' into fallable
na-trium-144 May 17, 2025
c8b6743
inverseSwitchingBlock実装
na-trium-144 May 17, 2025
15aa9d2
画面外に落下するfallableの挙動を実装
na-trium-144 May 17, 2025
3d0979a
inventoryIsInfiniteをステージ定義で設定可能にする
na-trium-144 May 17, 2025
0004d07
ステージ実装、バグ修正
na-trium-144 May 17, 2025
fac70f4
Merge remote-tracking branch 'origin/main' into fallable
na-trium-144 May 17, 2025
119e64e
world3ではcopyを許可
na-trium-144 May 17, 2025
6300af9
fix
na-trium-144 May 17, 2025
5f7ad05
fix
na-trium-144 May 17, 2025
7d0121d
fix
na-trium-144 May 17, 2025
2826fad
fix
na-trium-144 May 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@
<p>
<a href="/game?stage=3-1">Stage 3-1</a>
<a href="/game?stage=3-2">Stage 3-2</a>
<a href="/game?stage=3-3">Stage 3-3</a>
<a href="/game?stage=3-4">Stage 3-4</a>
</p>
15 changes: 3 additions & 12 deletions src/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
});
}
Expand Down
7 changes: 5 additions & 2 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -46,6 +48,7 @@ export const BlockDefinitionMap = new Map<string, Block | null>([
["s", Block.switch],
["S", Block.switchBase],
["w", Block.switchingBlockOFF],
["W", Block.inverseSwitchingBlockOFF],
["^", Block.spike],
["g", Block.goal],
]);
Expand Down
106 changes: 86 additions & 20 deletions src/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand All @@ -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;
Expand All @@ -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 = [];

Expand Down Expand Up @@ -104,6 +112,7 @@ export class Grid {
});
break;
}
case Block.inverseSwitchingBlockOFF:
case Block.switchingBlockOFF: {
const switchId = (
get(cx.state).cells[y][x] as {
Expand All @@ -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}`);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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",
);
Expand All @@ -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;
});
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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];
Expand All @@ -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;
Expand All @@ -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];
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -681,13 +745,15 @@ 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;
else sprite.tint = 0xffa500;
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;
Expand Down
1 change: 1 addition & 0 deletions src/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading