/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones import { $QuitDataspace, Double, Facet, Inbound, Outbound, floatValue } from '@syndicate-lang/core'; activate import { WindowEvent, template, Anchor } from '@syndicate-lang/html'; activate import { PeriodicTick } from '@syndicate-lang/timer'; assertion type Position(x, y); assertion type GameOver(); assertion type Score(count); message type IncreaseScore(); message type Reset(); const BOARD_HEIGHT = 567; const FLAPPY_WIDTH = 57; const FLAPPY_HEIGHT = 41; const FLAPPY_XPOS = 212; const PILLAR_WIDTH = 86; const PILLAR_GAP = 158; const FIELD_HEIGHT = 561; const PILLAR_HEAD_HEIGHT = 40; boot { spawn named 'game-factory' { on start spawnGame(thisFacet); during GameOver() => { on stop spawnGame(thisFacet); on message WindowEvent('+keypress', $_e) => send message Reset(); on message WindowEvent('+click', $_e) => send message Reset(); } } } function spawnGame(thisFacet: Facet) { spawn dataspace named 'GameInstance' { spawn named 'game-instance-control' { during GameOver() => assert Outbound(GameOver()); on message Inbound(Reset()) => send message $QuitDataspace; } spawn named 'score' { let ui = new Anchor(); field score: number = 0; assert Score(this.score); on start react { assert Outbound(ui.html('#board-area', template`

${this.score}

`)); stop on asserted GameOver() => react { assert Outbound(ui.html('#board-area', template`

${this.score}
GAME OVER

`)); } } on message IncreaseScore() => this.score++; } spawn named 'flappy' { let ui = new Anchor(); field xpos: number = 0; field ypos: number = 312; field yvel: number = 0; assert Position(Double(this.xpos), Double(this.ypos)); assert Outbound( ui.html('#board-area', template`
`)); on (this.ypos > BOARD_HEIGHT - FLAPPY_HEIGHT) { this.ypos = BOARD_HEIGHT - FLAPPY_HEIGHT; react { assert GameOver(); } } on start react { stop on asserted GameOver(); on message Inbound(WindowEvent('+keypress', $_e)) => this.yvel = -10; on message Inbound(WindowEvent('+click', $_e)) => this.yvel = -10; const ms_per_tick = 1000.0 / 60; on message Inbound(PeriodicTick(Double(ms_per_tick))) => { this.xpos += 0.15 * ms_per_tick; this.ypos = (this.ypos + this.yvel); this.yvel += ms_per_tick * 0.05; } } } spawn named 'border-scroll' { let ui = new Anchor(); field pos: number = 0; on asserted Position($xpos, _) => this.pos = floatValue(xpos) % 23; assert Outbound(ui.html( '#board-area', template`
`, 0)); } spawn named 'pipe-factory' { field nextPipe: number = 0; on asserted Score(this.nextPipe) => spawnPipe(thisFacet, this.nextPipe++); } function spawnPipe(thisFacet: Facet, i: number) { spawn named ['pipe', i] { let ui = new Anchor(); const xlocation = (i + 1) * 324; const upperHeight = Math.random() * (FIELD_HEIGHT - PILLAR_GAP - PILLAR_HEAD_HEIGHT * 6) + PILLAR_HEAD_HEIGHT * 3; const lowerHeight = FIELD_HEIGHT - upperHeight - PILLAR_GAP; stop on (this.xpos < -(PILLAR_WIDTH + FLAPPY_XPOS)); on start react { stop on (this.xpos <= 0) send message IncreaseScore(); }; field xpos: number = xlocation; on asserted Position($xpos, _) => this.xpos = xlocation - floatValue(xpos); on asserted Position($xpos, $ypos) => { if (touchingPillar(floatValue(xpos), floatValue(ypos))) { react { assert GameOver(); } } } assert Outbound(ui.html( '#board-area', template`
`)); function touchingPillar(xpos: number, ypos: number): boolean { const inHorizontalRange = (xpos + FLAPPY_WIDTH >= xlocation) && (xpos <= xlocation + PILLAR_WIDTH); const aboveGapTop = (ypos <= upperHeight); const belowGapBottom = (ypos + FLAPPY_HEIGHT >= upperHeight + PILLAR_GAP); return inHorizontalRange && (aboveGapTop || belowGapBottom); } } } } }