git @ Cat's Eye Technologies Cosmos-Boulders / master src / game.js
master

Tree @master (Download .tar.gz)

game.js @masterraw · history · blame

// 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 };