// SPDX-FileCopyrightText: In 2019, Chris Pressey, the original author of this work, placed it into the public domain.
// SPDX-License-Identifier: Unlicense
// For more information, please refer to <https://unlicense.org/>
import { concoctJaft as concoct } from '../contrib/jaft-0.2/jaftConcoctor.js';
import { newMap, get, setr, update, newList, push, map, reduce } from './immutable.js';
import {
error, not, isUndefined, newNull,
countDown, incr, decr, fireWentDown,
degreesToRadians, sin, cos,
makeAction
} from './utils.js';
import { makePlayer, resetPlayer, updatePlayer } from './player.js';
import { makeBoulders, updateBoulder } from './boulder.js';
import { makeMissile, updateMissile } from './missile.js';
import { detectCollisions } from './collision.js';
/* --[ Game objects ]-------------------------------------------------------- */
// modes are: ATTRACT_TITLE | ATTRACT_HISCORES | GAME_ON | GAME_OVER
const resetGame = concoct({ setr, makePlayer, makeBoulders, newList }, `(game) =>
let game = setr("player", makePlayer(), game);
let game = setr("boulders", makeBoulders(), game);
let game = setr("missiles", newList(), game);
let game = setr("mode", "ATTRACT_TITLE", game);
let game = setr("timer", 400, game);
game
`);
const makeGame = concoct({ newMap, newNull, setr, resetGame }, `() =>
let m = newMap();
let m = setr("credits", 0, m);
let m = setr("highScore", 0, m);
let m = setr("timer", newNull(), m);
resetGame(m)
`);
// Utils
const shootMissile = concoct({ get, setr, push, cos, sin, degreesToRadians, makeMissile: makeMissile }, `(game) =>
const player = get(game, "player");
const mx = get(player, "x");
const my = get(player, "y");
const h = get(player, "h");
const mv = 2.0;
const mvx = cos(degreesToRadians(h)) * mv;
const mvy = sin(degreesToRadians(h)) * mv;
const missiles = get(game, "missiles");
const newMissiles = push(missiles, makeMissile(mx, my, mvx, mvy));
setr("missiles", newMissiles, game)
`);
const updateBoulderMapper = concoct({ updateBoulder, makeAction }, `(boulder) =>
updateBoulder(boulder, makeAction("STEP"))
`)
const updateMissileReducer = concoct({ updateMissile, makeAction, get, push }, `(accum, missile) =>
const missile2 = updateMissile(missile, makeAction("STEP"));
get(missile2, "mode") === "GONE" ? accum : push(accum, missile2)
`)
const updateGameFrameReadyInGame = concoct({
get, setr, newList, map, reduce, makeAction,
updatePlayer, updateBoulderMapper, updateMissileReducer,
detectCollisions, makeBoulders
}, `(game) =>
const player = updatePlayer(get(game, "player"), makeAction("STEP"));
const boulders = map(get(game, "boulders"), updateBoulderMapper);
const missiles = reduce(get(game, "missiles"), updateMissileReducer, newList());
const [player2, missiles2, boulders2] = detectCollisions(player, missiles, boulders);
const highScore = get(player2, "score") > get(game, "highScore") ? get(player2, "score") : get(game, "highScore");
const game2 = get(player2, "mode") === "GONE" ?
setr("mode", 'GAME_OVER', setr("timer", 100, setr("highScore", highScore, game))) : game;
setr("player", player2, setr("boulders", boulders2.size === 0 ? makeBoulders() : boulders2, setr("missiles", missiles2, game2)))`);
const updateGameControlsChanged = concoct({
error, not, get, setr, update, decr, fireWentDown, shootMissile, updatePlayer, resetPlayer
}, `(game, action) =>
get(game, "mode") === "ATTRACT_TITLE" || get(game, "mode") === "ATTRACT_HISCORES" ? (
action.prev.startPressed && (not(action.startPressed)) ? (
get(game, "credits") > 0 ? (
const player = resetPlayer(get(game, "player"));
setr("player", player, setr("mode", "GAME_ON", update(game, "credits", decr)))
) : game
) : game
) : (
get(game, "mode") === "GAME_ON" ? (
const player = get(game, "player");
fireWentDown(action) && get(player, "mode") === "PLAYING" ?
shootMissile(game) :
setr("player", updatePlayer(get(game, "player"), action), game)
) : (
get(game, "mode") === "GAME_OVER" ? game : error("Unhandled mode", get(game, "mode"))
)
)
`);
const updateGameCoinInserted = concoct({ update, incr, get, setr }, `(game) =>
let game2 = update(game, "credits", incr);
get(game2, "mode") === "ATTRACT_HISCORES" ? setr("mode", "ATTRACT_TITLE", game2) : game2
`);
const updateGameAttractHiScores = concoct({ setr }, `(game) =>
setr("mode", "ATTRACT_HISCORES", setr("timer", 400, game))
`);
const updateGameAttractTitle = concoct({ setr }, `(game) =>
setr("mode", "ATTRACT_TITLE", setr("timer", 400, game))
`);
const updateGameFrameReady = concoct({
error, get, countDown, resetGame,
updateGameFrameReadyInGame, updateGameAttractHiScores, updateGameAttractTitle
}, `(game) =>
get(game, "mode") === "GAME_ON" ? updateGameFrameReadyInGame(game) : (
get(game, "mode") === "GAME_OVER" ? countDown(game, resetGame) : (
get(game, "mode") === "ATTRACT_TITLE" ? (
get(game, "credits") > 0 ? game : countDown(game, updateGameAttractHiScores)
) : (
get(game, "mode") === "ATTRACT_HISCORES" ? countDown(game, updateGameAttractTitle) : error("Unhandled mode", get(game, "mode"))
)
)
)`);
// Reducer that takes a Game and an Action and returns a new Game.
// Action must be one of: FRAME_READY | CONTROLS_CHANGED | COIN_INSERTED
const updateGame = concoct({
error, isUndefined, makeGame, updateGameFrameReady, updateGameControlsChanged, updateGameCoinInserted
}, `(game, action) =>
isUndefined(game) ? makeGame() : (
action.type === "FRAME_READY" ? updateGameFrameReady(game) : (
action.type === "CONTROLS_CHANGED" ? updateGameControlsChanged(game, action) : (
action.type === "COIN_INSERTED" ? updateGameCoinInserted(game) : (
error("Unhandled action", action.type)
)
)
)
)`);
export { makeGame, updateGame };