Skip to content

jengine-blast

Bomberman - a destructible grid, blasts that chain, and a map that can be refused for trapping somebody.

KindProject package: linked into the builds of the projects that list it
Sourcepackages/jengine-blast
Version0.1.0
Entry pointjengine_blast::BlastPackage
Script namespaceBlast
Listed byprojects/arcade-classics

List it in the top-level "packages" array of project.jengine:

{
"packages": [
{ "name": "jengine-blast", "source": { "type": "path", "path": "../../packages/jengine-blast" } }
]
}

Then rebuild the editor viewport for the project (jengine viewport-build projects/<name>, or the editor’s Rebuild viewport banner). Native and web builds link it on their own.

A scene that uses its components must be a v2 scene ("version": 2): a v1 scene has no field for a package component and keeps it as inert custom data.

A script can check that the package is linked with if Blast == nil then return end.

Scene keyRust type
sf_matchMatchpackage only: needs a v2 scene

Commands run in every runtime and return nothing; a package publishes what scripts read onto components (see the notes below). The flat name also works.

CallFlat name
Blast.start(e)sf_start
Blast.next_round(e)sf_next_round
Blast.quit(e)sf_quit
Blast.input(e, seat, dx, dy, bomb)sf_input
  • projects/arcade-classics/assets/scenes/shortfuse.scene

From packages/jengine-blast/src/lib.rs.

Short Fuse - a destructible grid, blasts that chain, and an arena that closes in so a round cannot simply expire.

  • [grid]: the arena, the two rules a blast obeys, and the threat map.
  • [arena]: building one, and refusing one nobody can win on.
  • [run]: the match - the bomb rule, deaths, rounds, sudden death.
  • [bot]: an opponent that never bombs itself into a corner.
  • [content]: the arena table.

Honestly: less than most of the arcade. The destructible grid it would have justified is already done - jengine-settlement proved a tile edit is cheap enough to be the main verb, and BUGS #148 made the collider rebuild per 32x32 chunk rather than per frame. Local multiplayer is already proven by Pale Orbit. So this one earns its place on two narrower things.

One rule, two readers. The blast walk is computed in exactly one place (grid::Field::blast_cells) and read by both the explosion and the bot’s danger map. The obvious alternative - a bot with its own idea of what a blast covers - fails in the worst possible way: the bot dies to explosions it was sure were safe, the bug reads as bad AI, and nobody looks at the two copies of a rule. Sudden death then joins the same map: a closing wall is scheduled as a pending tile change with a warning time, so it appears in grid::Field::threat and the bot runs from one without the bot knowing sudden death exists.

A grid game whose whole simulation is unit-testable. No physics, no renderer: 62 tests decide the game. That includes the one measurement that actually found a bug - bots playing twelve whole rounds against each other. It caught the bot planning mid-step, where the plan it acted on was made for a body already committed to walking somewhere else: it dropped the bomb, the steering was discarded, and it walked into its own blast. 24 self-kills against 9 real ones, and every unit test above it passed.

sf_match, on the same entity as the tilemap and its transform.

"sf_match": {
"tables": { "arenas": "data/sf_arenas" },
"which": 0, "seats": 4, "humans": 1, "best_of": 3,
"bot_skill": 0.6,
"layer": "arena", "tile": 32.0,
"floor_gid": 1, "hard_gid": 2, "soft_gid": 3
}

The package owns the grid and writes it into the tilemap: the whole layer at the start of a round, then one set_tile per block broken or wall dropped, which is the cheap path the chunked rebuild exists for. Everything that moves - players, bombs, fire, pickups - is published in the view with world coordinates already worked out, so a script draws sprites at the numbers it is given and does no grid arithmetic at all.

CallDoes
Blast.input(e, seat, dx, dy, bomb)one human seat’s intent for this frame
Blast.start(e)begin (or restart) the match
Blast.next_round(e)after a round ends, deal the next arena
Blast.quit(e)abandon the match

Seats at or above humans are driven by bot inside the package, so a one-player scene sends one sf_input and gets three opponents.