syndicate-js/packages/flappy-bird-demo/src/index.js

179 lines
5.8 KiB
JavaScript
Raw Normal View History

2018-11-08 10:54:21 +00:00
"use strict";
//---------------------------------------------------------------------------
// @syndicate-lang/flappy-bird-demo
// Copyright (C) 2016-2018 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//---------------------------------------------------------------------------
2020-08-05 10:36:53 +00:00
import { bootModule, $QuitDataspace, Inbound, Outbound, Dataspace, Double } from "@syndicate-lang/core";
2018-11-08 10:54:21 +00:00
let UI = activate require("@syndicate-lang/driver-browser-ui");
// @jsx UI.html
// @jsxFrag UI.htmlFragment
let { PeriodicTick } = activate require("@syndicate-lang/driver-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;
spawn named 'game-factory' {
on start spawnGame();
during GameOver() {
on stop spawnGame();
on message UI.WindowEvent('+keypress', $e) send Reset();
on message UI.WindowEvent('+click', $e) send Reset();
2018-11-08 10:54:21 +00:00
}
}
2018-11-08 10:54:21 +00:00
function spawnGame() {
spawn dataspace named 'GameInstance' {
spawn named 'game-instance-control' {
during GameOver() assert Outbound(GameOver());
on message Inbound(Reset()) send $QuitDataspace;
}
2018-11-08 10:54:21 +00:00
spawn named 'score' {
let ui = new UI.Anchor();
field this.score = 0;
2018-11-08 10:54:21 +00:00
assert Score(this.score);
2018-11-08 10:54:21 +00:00
on start react {
assert Outbound(ui.html('#board-area', <h1 class="score">{this.score}</h1>));
stop on asserted GameOver() react {
assert Outbound(ui.html('#board-area', <h1 class="score">{this.score}<br/>GAME OVER</h1>));
}
}
2018-11-08 10:54:21 +00:00
on message IncreaseScore() {
this.score++;
2018-11-08 10:54:21 +00:00
}
}
spawn named 'flappy' {
let ui = new UI.Anchor();
field this.xpos = 0;
field this.ypos = 312;
field this.yvel = 0;
2018-11-08 10:54:21 +00:00
assert Position(Double(this.xpos), Double(this.ypos));
2018-11-08 10:54:21 +00:00
assert Outbound(ui.html('#board-area', <div class="flappy"
style={`transform: rotate(${2 * this.yvel}deg);
top: ${this.ypos}px`}></div>));
2018-11-08 10:54:21 +00:00
on (this.ypos > BOARD_HEIGHT - FLAPPY_HEIGHT) {
this.ypos = BOARD_HEIGHT - FLAPPY_HEIGHT;
react {
assert GameOver();
}
}
2018-11-08 10:54:21 +00:00
on start react {
stop on asserted GameOver();
2018-11-08 10:54:21 +00:00
on message Inbound(UI.WindowEvent('+keypress', $e)) this.yvel = -10;
on message Inbound(UI.WindowEvent('+click', $e)) this.yvel = -10;
2018-11-08 10:54:21 +00:00
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;
}
}
}
2018-11-08 10:54:21 +00:00
spawn named 'border-scroll' {
let ui = new UI.Anchor();
field this.pos = 0;
on asserted Position($xpos, _) this.pos = xpos.value % 23;
assert Outbound(ui.html(
'#board-area',
<div class="scrolling-border" style={`background-position-x: ${-this.pos}px`}></div>, 0));
2018-11-08 10:54:21 +00:00
}
spawn named 'pipe-factory' {
field this.nextPipe = 0;
on asserted Score(this.nextPipe) {
spawnPipe(this.nextPipe++);
}
}
2018-11-08 10:54:21 +00:00
function spawnPipe(i) {
spawn named ['pipe', i] {
let ui = new UI.Anchor();
2018-11-08 10:54:21 +00:00
const xlocation = (i + 1) * 324;
2018-11-08 10:54:21 +00:00
const upperHeight =
Math.random() * (FIELD_HEIGHT - PILLAR_GAP - PILLAR_HEAD_HEIGHT * 6)
+ PILLAR_HEAD_HEIGHT * 3;
const lowerHeight = FIELD_HEIGHT - upperHeight - PILLAR_GAP;
2018-11-08 10:54:21 +00:00
stop on (this.xpos < -(PILLAR_WIDTH + FLAPPY_XPOS));
2018-11-08 10:54:21 +00:00
on start react stop on (this.xpos <= 0) send IncreaseScore();
2018-11-08 10:54:21 +00:00
field this.xpos = xlocation;
on asserted Position($xpos, _) this.xpos = xlocation - xpos.value;
2018-11-08 10:54:21 +00:00
on asserted Position($xpos, $ypos) {
if (touchingPillar(xpos.value, ypos.value)) {
react {
assert GameOver();
}
2018-11-08 10:54:21 +00:00
}
}
assert Outbound(ui.html(
'#board-area',
<div class="pillars">
2018-11-08 10:54:21 +00:00
<div class="pillar pillar-upper"
style={`left: ${this.xpos + FLAPPY_XPOS}px; height: ${upperHeight}px;`}></div>
2018-11-08 10:54:21 +00:00
<div class="pillar pillar-lower"
style={`left: ${this.xpos + FLAPPY_XPOS}px; height: ${lowerHeight}px;`}></div>
</div>));
function touchingPillar(xpos, ypos) {
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);
}
2018-11-08 10:54:21 +00:00
}
}
}
}
2020-08-05 10:36:53 +00:00
// Running Syndicate programs in node.js doesn't require this -- it
// automatically detects the "main" module -- but in the browser, when
// using rollup, the mechanism doesn't work properly so we explicitly
// activate *ourselves* here.
bootModule(module);