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