Skip to content

Commit 12d921d

Browse files
committed
feat: add break motion
1 parent 94b8d57 commit 12d921d

5 files changed

Lines changed: 113 additions & 7 deletions

File tree

game/game-screen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { Field } from "./field.ts"
1010
import { Actor } from "../model/actor.ts"
1111

1212
/** The starting X coordinate */
13-
const I = 50
13+
const I = 32
1414
/** The starting Y coordinate */
15-
const J = 45
15+
const J = 40
1616

1717
/**
1818
* The area which is visible to the user

model/action-queue.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ type CommonAction = {
1313
radius: number
1414
}
1515

16+
export type Motion = {
17+
step(): void
18+
finished: boolean
19+
}
20+
1621
export type PropAction =
1722
| CommonAction
18-
| { type: "break"; dir: Dir }
23+
| { type: "break"; dir: Dir; cb?: (motion: Motion) => void }
1924
| { type: "remove" }
2025

2126
export type ActorAction =

model/prop.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { CELL_SIZE } from "../util/constants.ts"
22
import type {
3+
Dir,
34
IField,
45
IProp,
56
LoadOptions,
@@ -22,11 +23,14 @@ export class Prop implements IProp {
2223
readonly j: number
2324
readonly type: PropType
2425
readonly def: PropDefinition
26+
#motion: Motion | null = null
2527
readonly #actionQueue = new ActionQueue<Prop, PropAction>(
2628
(field, action) => {
2729
switch (action.type) {
2830
case "break": {
29-
return "next"
31+
this.#motion = new MotionBreak(this.#image!, action.dir)
32+
this.#motion.cb = action.cb
33+
return "end"
3034
}
3135
case "remove": {
3236
field.props.remove(this.i, this.j)
@@ -81,6 +85,16 @@ export class Prop implements IProp {
8185
this.#pushed = pushed
8286
}
8387

88+
/**
89+
* Vanishes the prop, but keeps its space occupied
90+
* Useful for removing the visual representation, but keeping the processing of the action queue
91+
*/
92+
vanish(): void {
93+
const canvas = new OffscreenCanvas(CELL_SIZE, CELL_SIZE)
94+
canvas.getContext("2d")
95+
this.#image = canvas.transferToImageBitmap()
96+
}
97+
8498
async loadAssets(options: LoadOptions) {
8599
const loadImage = options.loadImage
86100
if (!loadImage) {
@@ -94,6 +108,9 @@ export class Prop implements IProp {
94108
}
95109

96110
image(): ImageBitmap {
111+
if (this.#motion instanceof MotionBreak) {
112+
return this.#motion.image()
113+
}
97114
return this.#image ?? fallbackImage
98115
}
99116

@@ -118,7 +135,17 @@ export class Prop implements IProp {
118135
}
119136

120137
step(field: IField) {
121-
this.#actionQueue.process(this, field)
138+
if (!this.#motion) {
139+
this.#actionQueue.process(this, field)
140+
}
141+
142+
if (this.#motion) {
143+
this.#motion.step()
144+
if (this.#motion.finished) {
145+
this.#motion.cb?.(this.#motion)
146+
this.#motion = null
147+
}
148+
}
122149
}
123150

124151
onPushed(event: PushedEvent, field: IField): void {
@@ -142,8 +169,62 @@ class PushedDelegateBreak implements PushedDelegate {
142169
onPushed(event: PushedEvent, prop: Prop, field: IField): void {
143170
prop.enqueueActions(
144171
{ type: "wait", until: field.time + event.peakAt },
172+
{
173+
type: "break",
174+
dir: event.dir,
175+
cb: () => {
176+
prop.vanish()
177+
},
178+
},
145179
{ type: "splash", hue: 0, sat: 1, light: 0, alpha: 0.15, radius: 2 },
146180
{ type: "remove" },
147181
)
148182
}
149183
}
184+
185+
interface Motion {
186+
step(): void
187+
finished: boolean
188+
cb?: (motion: Motion) => void
189+
}
190+
191+
class MotionBreak implements Motion {
192+
#image: ImageBitmap
193+
#dir: Dir
194+
#positions = [...Array(16).keys()]
195+
cb?: (motion: Motion) => void
196+
constructor(image: ImageBitmap, dir: Dir) {
197+
this.#image = image
198+
this.#dir = dir
199+
}
200+
201+
step(): void {
202+
const y = this.#positions.shift()!
203+
const canvas = new OffscreenCanvas(CELL_SIZE, CELL_SIZE)
204+
const ctx = canvas.getContext("2d")!
205+
ctx.drawImage(this.#image, 0, 0)
206+
switch (this.#dir) {
207+
case "up":
208+
ctx.clearRect(0, CELL_SIZE - 1 - y, CELL_SIZE, 1)
209+
break
210+
case "down":
211+
ctx.clearRect(0, y, CELL_SIZE, 1)
212+
break
213+
case "left":
214+
ctx.clearRect(CELL_SIZE - 1 - y, 0, 1, CELL_SIZE)
215+
break
216+
case "right":
217+
ctx.clearRect(y, 0, 1, CELL_SIZE)
218+
break
219+
}
220+
this.#image = canvas.transferToImageBitmap()
221+
}
222+
223+
image(): ImageBitmap {
224+
return this.#image
225+
}
226+
227+
get finished(): boolean {
228+
return this.#positions.length === 0
229+
}
230+
}

static/map/block_0.0.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7543,6 +7543,16 @@
75437543
"i": 55,
75447544
"j": 45,
75457545
"type": "crate"
7546+
},
7547+
{
7548+
"i": 36,
7549+
"j": 37,
7550+
"type": "crate"
7551+
},
7552+
{
7553+
"i": 37,
7554+
"j": 37,
7555+
"type": "crate"
75467556
}
75477557
],
75487558
"field": [

util/random.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import seedrandom from "seedrandom"
22

3-
const { randomInt, choice } = seed("Hello.")
3+
const { randomInt, choice, shuffle } = seed("Hello.")
44

5-
export { choice, randomInt }
5+
export { choice, randomInt, shuffle }
66

77
export function seed(seed: string) {
88
const rng = seedrandom(seed)
@@ -17,9 +17,19 @@ export function seed(seed: string) {
1717
return arr[randomInt(arr.length)]
1818
}
1919

20+
const shuffle = <T>(arr: T[]): T[] => {
21+
const result = arr.slice()
22+
for (let i = result.length - 1; i > 0; i--) {
23+
const j = randomInt(i + 1)
24+
;[result[i], result[j]] = [result[j], result[i]]
25+
}
26+
return result
27+
}
28+
2029
return {
2130
rng,
2231
randomInt,
2332
choice,
33+
shuffle,
2434
}
2535
}

0 commit comments

Comments
 (0)