// 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 {
SCREEN_WIDTH, SCREEN_HEIGHT, decr, cap, degreesToRadians,
countDown, countDownToMode, fireWentUp, updatePosition
} from './utils.js';
/* --[ Player objects ]-------------------------------------------------------- */
function resetPlayer(player) {
return player.set("x", SCREEN_WIDTH / 2)
.set("y", SCREEN_HEIGHT / 2)
.set("vx", 0.0)
.set("vy", 0.0)
.set("ax", 0.0)
.set("ay", 0.0)
// h (heading) is measured in degrees. So, E is 0, S is 90, W is 180, and N is 270.
.set("h", 270)
.set("dh", 0)
// f (force) is in the direction of h
.set("f", 0)
.set("mode", 'GET_READY') // GET_READY | PLAYING | EXPLODING | GONE
// generally speaking, this is how many more steps it shall remain in the present mode
.set("timer", 200);
}
function makePlayer() {
return resetPlayer(Immutable.Map({
score: 0,
lives: 2,
mass: 1.0
}));
}
// Utils
function updateAcceleration(player) {
const f = player.get("f");
if (f === 0) {
// optimization: no force = no acceleration, so skip all that trig
return player.set("ax", 0).set("ay", 0);
}
const theta = degreesToRadians(player.get("h"));
const fx = Math.cos(theta) * f;
const fy = Math.sin(theta) * f;
// F=ma, so a = F/m
const m = player.get("mass");
return player.set("ax", fx * m).set("ay", fy * m);
}
// Reducer that takes a Player and an Action and returns a new Player.
// Action must be one of: STEP | SCORE_POINTS | EXPLODE | CONTROLS_CHANGED
function updatePlayer(player, action) {
if (typeof player === 'undefined') return makePlayer();
if (action.type === 'STEP') {
return updatePlayerStep(player);
} else if (action.type === 'SCORE_POINTS') {
return player.set("score", player.get("score") + 10);
} else if (action.type === 'EXPLODE') {
return player.set("mode", 'EXPLODING').set("timer", 50);
} else if (action.type === 'CONTROLS_CHANGED') {
return updatePlayerControlsChanged(player, action);
}
throw new Error("Unhandled action: " + action.type);
}
function updatePlayerStep(player) {
if (player.get("mode") === 'GET_READY') {
return countDownToMode(player, 'PLAYING');
} else if (player.get("mode") == 'PLAYING') {
return updateAcceleration(
updatePosition(player).set("vx", cap(player.get("vx") + player.get("ax"), 6))
.set("vy", cap(player.get("vy") + player.get("ay"), 6))
.set("h", player.get("h") + player.get("dh"))
);
} else if (player.get("mode") === 'EXPLODING') {
return countDown(player, function(player) {
if (player.get("lives") > 0) {
return resetPlayer(player.update("lives", decr));
} else {
return player.set("mode", 'GONE');
}
});
}
throw new Error("Unhandled mode: " + player.get("mode"));
}
function updatePlayerControlsChanged(player, action) {
if (player.get("mode") === 'PLAYING') {
return player.set("dh", action.leftPressed ? -5 : (action.rightPressed ? 5 : 0))
.set("f", action.thrustPressed ? 0.05 : 0.0);
} else if (player.get("mode") === 'GET_READY' && fireWentUp(action)) {
return player.set("dh", 0).set("f", 0).set("mode", 'PLAYING');
} else {
return player.set("dh", 0).set("f", 0);
}
}
export { makePlayer, resetPlayer, updatePlayer };