/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones import { Turn, Dataspace, Double, floatValue, Ref, Facet } from '@syndicate-lang/core'; import { boot as bootHtml, WindowEvent, template, Anchor } from '@syndicate-lang/html'; import { boot as bootTimer, 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; export function main() { Dataspace.boot(mainDs => { bootHtml(mainDs); bootTimer(mainDs); spawn named 'game-factory' { spawnGame(mainDs); at mainDs { during GameOver() => { on stop spawnGame(mainDs); on message WindowEvent('+keypress', $_e) => send message Reset(); on message WindowEvent('+click', $_e) => send message Reset(); } } } }); } function spawnGame(mainDs: Ref) { spawn named 'GameInstance' { const gameDs = create new Dataspace(); at gameDs { during GameOver() => at mainDs { assert GameOver(); } } at mainDs { stop on message Reset(); } spawn linked named 'score' { let ui = new Anchor(); field score: number = 0; at gameDs { assert Score(score.value); on message IncreaseScore() => score.value++; } react { at mainDs { assert ui.html('#board-area', template`

${score.value}

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

${score.value}
GAME OVER

`); } } } } } spawn linked named 'flappy' { let ui = new Anchor(); field xpos: number = 0; field ypos: number = 312; field yvel: number = 0; at gameDs { assert Position(Double(xpos.value), Double(ypos.value)); on (ypos.value > BOARD_HEIGHT - FLAPPY_HEIGHT) { ypos.value = BOARD_HEIGHT - FLAPPY_HEIGHT; assert GameOver(); } } at mainDs { assert ui.html( '#board-area', template`
`); } react { at gameDs { stop on asserted GameOver(); } at mainDs { on message WindowEvent('+keypress', $_e) => yvel.value = -10; on message WindowEvent('+click', $_e) => yvel.value = -10; const seconds_per_tick = 1 / 60; const seconds_per_tick_d = Double(seconds_per_tick); on message PeriodicTick(seconds_per_tick_d) => { xpos.value += 150 * seconds_per_tick; ypos.value = (ypos.value + yvel.value); yvel.value += seconds_per_tick * 50; } } } } spawn linked named 'border-scroll' { let ui = new Anchor(); field pos: number = 0; at gameDs { on asserted Position($xpos, _) => pos.value = floatValue(xpos) % 23; } at mainDs { assert ui.html( '#board-area', template`
`, 0); } } spawn linked named 'pipe-factory' { field nextPipe: number = 0; at gameDs { on asserted Score(nextPipe.value) => { react { const pipeNumber = nextPipe.value++; spawn linked named ['pipe', pipeNumber] { runPipe(pipeNumber, Turn.activeFacet); } } } } } function runPipe(i: number, mainPipeFacet: Facet) { const xlocation = (i + 1) * 324; let ui = new Anchor(); field xpos: number = xlocation; const upperHeight = Math.random() * (FIELD_HEIGHT - PILLAR_GAP - PILLAR_HEAD_HEIGHT * 6) + PILLAR_HEAD_HEIGHT * 3; const lowerHeight = FIELD_HEIGHT - upperHeight - PILLAR_GAP; stop mainPipeFacet on (xpos.value < -(PILLAR_WIDTH + FLAPPY_XPOS)); at gameDs { once (xpos.value <= 0) send message IncreaseScore(); on asserted Position($flappyXpos, _) => xpos.value = xlocation - floatValue(flappyXpos); on asserted Position($xpos, $ypos) => { if (touchingPillar(floatValue(xpos), floatValue(ypos))) { assert GameOver(); } } } at mainDs { assert 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); } } } }