Skip to content

Commit 6fdae23

Browse files
committed
refactor: use functions instead of classes, in order to reduce unnecessary states
1 parent 91e7083 commit 6fdae23

File tree

10 files changed

+612
-568
lines changed

10 files changed

+612
-568
lines changed

src/ability.ts

Lines changed: 194 additions & 234 deletions
Large diffs are not rendered by default.

src/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,9 @@ export enum Facing {
1515
left = "left",
1616
right = "right",
1717
}
18+
export enum Inputs {
19+
Left = 0,
20+
Right = 1,
21+
Up = 2,
22+
Ctrl = 3,
23+
}

src/grid.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ export class Grid {
173173
getBlock(x: number, y: number): Block | undefined {
174174
return this.cells[y]?.[x]?.block;
175175
}
176+
// satisfies: every(MovableObject.relativePositions, (pos) => pos.x >= 0)
177+
// satisfies: every(MovableObject.relativePositions, (pos) => pos.y <= 0)
176178
getMovableObject(x: number, y: number): MovableObject | undefined {
177179
const cell = this.cells[y]?.[x];
178180
if (!cell) return undefined;
@@ -186,12 +188,19 @@ export class Grid {
186188
}
187189
}
188190
}
191+
const minX = retrievedBlocks
192+
.map((v) => v.x)
193+
.reduce((a, b) => Math.min(a, b), Number.POSITIVE_INFINITY);
194+
const maxY = retrievedBlocks
195+
.map((v) => v.y)
196+
.reduce((a, b) => Math.max(a, b), 0);
197+
189198
const retrievedObject: MovableObject = {
190199
block: cell.block,
191200
objectId,
192201
relativePositions: retrievedBlocks.map((block) => ({
193-
x: block.x - x,
194-
y: block.y - y,
202+
x: block.x - minX,
203+
y: block.y - maxY,
195204
})),
196205
};
197206
return retrievedObject;
@@ -202,7 +211,7 @@ export class Grid {
202211
const prev = this.cells[y][x];
203212
if (prev.block === cell.block) return;
204213
if (prevSprite.sprite) {
205-
cx._stage.removeChild(prevSprite.sprite);
214+
cx._stage_container.removeChild(prevSprite.sprite);
206215
}
207216
if (prev.block === Block.air && prevSprite.sprite) {
208217
console.warn(

src/history.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import type { Context, StateSnapshot } from "./public-types.ts";
33

44
export function createSnapshot(cx: Context): StateSnapshot {
55
const game = get(cx.state);
6-
const playerX = cx.dynamic.playerX;
7-
const playerY = cx.dynamic.playerY;
8-
const playerFacing = cx.dynamic.playerFacing;
6+
const playerX = cx.dynamic.player.x;
7+
const playerY = cx.dynamic.player.y;
8+
const playerFacing = cx.dynamic.player.facing;
99
return {
1010
game: structuredClone(game),
1111
playerX,

src/main.ts

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
11
import { Application, Container, type Ticker } from "pixi.js";
2-
import { type Writable, get, writable } from "svelte/store";
2+
import { derived, get, writable } from "svelte/store";
33
import { Facing } from "./constants.ts";
44
import { Grid } from "./grid.ts";
5-
import { Player } from "./player.ts";
6-
import type { Context, GameState, UIContext } from "./public-types.ts";
5+
import * as Player from "./player.ts";
6+
import type { Context, GameState, UIInfo } from "./public-types.ts";
77
import { bunnyTexture } from "./resources.ts";
88
import type { StageDefinition } from "./stages.ts";
9+
import { useUI } from "./ui-info.ts";
910

1011
export async function setup(
1112
el: HTMLElement,
1213
stageDefinition: StageDefinition,
13-
uiContext: Writable<UIContext>,
14-
) {
15-
let paused = false;
16-
uiContext.subscribe((v) => {
17-
paused = v.paused;
18-
});
14+
bindings: {
15+
onpause: () => void;
16+
onresume: () => void;
17+
ondestroy: () => void;
18+
uiInfo: UIInfo;
19+
},
20+
): Promise<void> {
21+
bindings.onpause = () => {
22+
cx.state.update((prev) => {
23+
prev.paused = true;
24+
return prev;
25+
});
26+
};
27+
const cleanups: (() => void)[] = [];
1928
const unlessPaused = (f: (ticker: Ticker) => void) => (ticker: Ticker) => {
29+
const paused = get(cx.state).paused;
2030
if (!paused) {
2131
f(ticker);
2232
}
2333
};
2434

2535
function tick() {
2636
// highlight is re-rendered every tick
27-
const highlight = player.createHighlight(cx);
37+
const highlight = Player.createHighlight(cx);
2838
if (highlight) {
2939
stage.addChild(highlight);
3040
}
@@ -39,6 +49,9 @@ export async function setup(
3949
const app = new Application();
4050
const stage = new Container();
4151
app.stage.addChild(stage);
52+
cleanups.push(() => {
53+
app.destroy(true, { children: true });
54+
});
4255

4356
const gridX = stageDefinition.stage[0].length;
4457
const gridY = stageDefinition.stage.length;
@@ -51,27 +64,49 @@ export async function setup(
5164
);
5265
const grid = new Grid(stage, app.screen.height, blockSize, stageDefinition);
5366

67+
const state = writable<GameState>({
68+
inventory: null,
69+
inventoryIsInfinite: false,
70+
usage: {
71+
// TODO
72+
copy: Number.POSITIVE_INFINITY,
73+
paste: Number.POSITIVE_INFINITY,
74+
cut: Number.POSITIVE_INFINITY,
75+
},
76+
cells: grid.snapshot(),
77+
paused: false,
78+
});
79+
const history = writable({
80+
index: -1,
81+
tree: [],
82+
});
83+
const uiContext = derived([state, history], ([$state, $history]) => {
84+
return useUI($state, $history);
85+
});
5486
const cx: Context = {
55-
_stage: stage,
87+
_stage_container: stage,
5688
grid,
5789
dynamic: {
5890
focus: null,
59-
playerX: stageDefinition.initialPlayerX,
60-
playerY: stageDefinition.initialPlayerY,
61-
playerFacing: Facing.right,
62-
},
63-
64-
state: writable<GameState>({
65-
inventory: null,
66-
inventoryIsInfinite: false,
67-
usage: {
68-
// TODO
69-
copy: Number.POSITIVE_INFINITY,
70-
paste: Number.POSITIVE_INFINITY,
71-
cut: Number.POSITIVE_INFINITY,
91+
player: {
92+
// HACK: these values are immediately overwritten inside Player.init().
93+
sprite: null,
94+
coords() {
95+
return { x: this.x, y: this.y };
96+
},
97+
x: 0,
98+
y: 0,
99+
vx: 0,
100+
vy: 0,
101+
onGround: false,
102+
jumpingBegin: null,
103+
holdingKeys: {},
104+
facing: Facing.left,
72105
},
73-
cells: grid.snapshot(),
74-
}),
106+
},
107+
state: state,
108+
history,
109+
uiContext,
75110
config: writable({
76111
gridX,
77112
gridY,
@@ -80,22 +115,17 @@ export async function setup(
80115
initialPlayerX: stageDefinition.initialPlayerX,
81116
initialPlayerY: stageDefinition.initialPlayerY,
82117
}),
83-
history: writable({
84-
index: -1,
85-
tree: [],
86-
}),
87118
elapsed: writable(0),
88-
uiContext,
89119
};
120+
90121
app.ticker.add(
91122
unlessPaused((ticker) => {
92123
cx.elapsed.update((prev) => prev + ticker.deltaTime);
93124
}),
94125
);
95126

96-
const player = new Player(cx, bunnyTexture);
97-
app.ticker.add(unlessPaused((ticker) => player.tick(cx, ticker)));
98-
app.stage.addChild(player.sprite);
127+
cx.dynamic.player = Player.init(cx, bunnyTexture);
128+
app.ticker.add(unlessPaused((ticker) => Player.tick(cx, ticker)));
99129

100130
let cleanup: undefined | (() => void) = undefined;
101131
app.ticker.add(
@@ -107,8 +137,42 @@ export async function setup(
107137

108138
// Append the application canvas to the document body
109139
el.appendChild(app.canvas);
140+
const onresize = useOnResize(cx, app, grid, gridX, gridY);
141+
window.addEventListener("resize", onresize);
142+
cleanups.push(() => {
143+
window.removeEventListener("resize", onresize);
144+
});
145+
146+
bindings.ondestroy = () => {
147+
for (const cleanup of cleanups) {
148+
cleanup();
149+
}
150+
};
151+
bindings.onresume = () => {
152+
cx.state.update((prev) => {
153+
prev.paused = false;
154+
return prev;
155+
});
156+
};
157+
bindings.onpause = () => {
158+
cx.state.update((prev) => {
159+
prev.paused = true;
160+
return prev;
161+
});
162+
};
163+
uiContext.subscribe((uiInfo) => {
164+
bindings.uiInfo = uiInfo;
165+
});
166+
}
110167

111-
window.addEventListener("resize", () => {
168+
function useOnResize(
169+
cx: Context,
170+
app: Application,
171+
grid: Grid,
172+
gridX: number,
173+
gridY: number,
174+
) {
175+
return () => {
112176
app.renderer.resize(window.innerWidth, window.innerHeight);
113177
const blockSize = Math.min(
114178
app.screen.width / gridX,
@@ -120,6 +184,6 @@ export async function setup(
120184
return prev;
121185
});
122186
cx.grid.rerender(app.screen.height, blockSize);
123-
player.resize(cx);
124-
});
187+
Player.resize(cx);
188+
};
125189
}

0 commit comments

Comments
 (0)