git @ Cat's Eye Technologies Cosmos-Boulders / master src / cosmos-boulders / player.js
master

Tree @master (Download .tar.gz)

player.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 {
    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 };