git @ Cat's Eye Technologies Cosmos-Boulders / master src / cosmos-boulders / 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 { countDown, incr, decr, fireWentDown, degreesToRadians } 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 ]-------------------------------------------------------- */


function resetGame(game) {
    return game.set("player", makePlayer())
               .set("boulders", makeBoulders())
               .set("missiles", Immutable.List())
               .set("mode", 'ATTRACT_TITLE')  // ATTRACT_TITLE | ATTRACT_HISCORES | GAME_ON | GAME_OVER
               .set("timer", 400);
}


function makeGame() {
    return resetGame(Immutable.Map({
        credits: 0,
        highScore: 0,
        timer: null
    }));
}


// Utils

function shootMissile(game) {
    const player = game.get("player");
    const mx = player.get("x");
    const my = player.get("y");
    const h = player.get("h");
    const mv = 2.0;
    const mvx = Math.cos(degreesToRadians(h)) * mv;
    const mvy = Math.sin(degreesToRadians(h)) * mv;
    return game.update("missiles", function(m) {
        return m.push(makeMissile(mx, my, mvx, mvy));
    });
}


// Reducer that takes a Game and an Action and returns a new Game.
// Action must be one of: FRAME_READY | CONTROLS_CHANGED | COIN_INSERTED
function updateGame(game, action) {
    if (typeof game === 'undefined') return makeGame();

    if (action.type === 'FRAME_READY') {
        return updateGameFrameReady(game);
    } else if (action.type === 'CONTROLS_CHANGED') {
        return updateGameControlsChanged(game, action);
    } else if (action.type === 'COIN_INSERTED') {
        return updateGameCoinInserted(game);
    }
    throw new Error("Unhandled action: " + action.type);
}


function updateGameFrameReady(game) {
    if (game.get("mode") === 'GAME_ON') {
        return updateGameFrameReadyInGame(game);
    } else if (game.get("mode") === 'GAME_OVER') {
        return countDown(game, resetGame);
    } else if (game.get("mode") === 'ATTRACT_TITLE') {
        return (game.get("credits") > 0) ? game : countDown(game, function(game) {
            return game.set("mode", 'ATTRACT_HISCORES').set("timer", 400);
        });
    } else if (game.get("mode") === 'ATTRACT_HISCORES') {
        return countDown(game, function(game) {
            return game.set("mode", 'ATTRACT_TITLE').set("timer", 400);
        });
    }
    throw new Error("Unhandled mode: " + game.get("mode"));
}


function updateGameFrameReadyInGame(game) {
    const player = updatePlayer(game.get("player"), { 'type': 'STEP' });

    const boulders = game.get("boulders").map(function(boulder) {
        return updateBoulder(boulder, { 'type': 'STEP' });
    });

    const missiles = game.get("missiles").reduce(function(accum, missile) {
        const missile2 = updateMissile(missile, { 'type': 'STEP' });
        if (missile2.get("mode") == 'GONE') {
            return accum;
        } else {
            return accum.push(missile2);
        }
    }, Immutable.List());

    const collisionResult = detectCollisions(player, missiles, boulders);
    const player2 = collisionResult[0];
    const missiles2 = collisionResult[1];
    const boulders2 = collisionResult[2];

    /* Assemble new game state from all that */
    const game2 = (player2.get("mode") !== 'GONE') ? game :
        game.set("mode", 'GAME_OVER')
            .set("timer", 100)
            .set("highScore",
                player2.get("score") > game.get("highScore") ? player2.get("score") : game.get("highScore")
            );
    return game2.set("player", player2)
                .set("boulders", boulders2.size === 0 ? makeBoulders() : boulders2)
                .set("missiles", missiles2);
}


function updateGameControlsChanged(game, action) {
    if (game.get("mode") === 'ATTRACT_TITLE' || game.get("mode") === 'ATTRACT_HISCORES') {
        if (action.prev.startPressed && (!action.startPressed)) {
            if (game.get("credits") > 0) {
                const player = resetPlayer(game.get("player"));
                return game.set("player", player).update("credits", decr).set("mode", 'GAME_ON');
            } else {
                return game;
            }
        } else {
            return game;
        }
    } else if (game.get("mode") === 'GAME_ON') {
        const player = game.get("player");
        if (fireWentDown(action) && player.get("mode") === 'PLAYING') {
            return shootMissile(game);
        } else {
            return game.set("player", updatePlayer(game.get("player"), action));
        }
    } else if (game.get("mode") === 'GAME_OVER') {
        return game;
    }
    throw new Error("Unhandled mode: " + game.get("mode"));
}


function updateGameCoinInserted(game) {
    const game2 = game.update("credits", incr);
    if (game2.get("mode") === 'ATTRACT_HISCORES') {
        return game2.set("mode", 'ATTRACT_TITLE');
    } else {
        return game2;
    }
}


export { makeGame, updateGame };