syndicate-js/examples/syndicate-html-example-flap.../src/index.ts

196 lines
6.7 KiB
TypeScript

/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
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`<h1 class="score">${score.value}</h1>`);
}
at gameDs {
stop on asserted GameOver() => react {
at mainDs {
assert ui.html(
'#board-area',
template`<h1 class="score">${score.value}<br/>GAME OVER</h1>`);
}
}
}
}
}
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`<div class="flappy"
style="${`transform: rotate(${2 * yvel.value}deg);
top: ${ypos.value}px`}"></div>`);
}
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`<div class="scrolling-border" style="${`background-position-x: ${-pos.value}px`}"></div>`,
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`<div class="pillars">
<div class="pillar pillar-upper"
style="${`left: ${xpos.value + FLAPPY_XPOS}px; height: ${upperHeight}px;`}"></div>
<div class="pillar pillar-lower"
style="${`left: ${xpos.value + FLAPPY_XPOS}px; height: ${lowerHeight}px;`}"></div>
</div>`);
}
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);
}
}
}
}