// 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, set, setr, update } from './immutable.js';
import {
isUndefined, error, cos, sin,
screenWidth, screenHeight, decr, cap, degreesToRadians,
countDown, countDownToMode, fireWentUp, updatePosition
} from './utils.js';
/* --[ Player objects ]-------------------------------------------------------- */
// h (heading) is measured in degrees. So, E is 0, S is 90, W is 180, and N is 270.
// f (force) is in the direction of h
// timer is, generally speaking, how many more steps it shall remain in the present mode
const resetPlayer = concoct({ set, screenWidth, screenHeight }, `(player) =>
let player = set(player, "x", screenWidth() / 2);
let player = set(player, "y", screenHeight() / 2);
let player = set(player, "vx", 0.0);
let player = set(player, "vy", 0.0);
let player = set(player, "ax", 0.0);
let player = set(player, "ay", 0.0);
let player = set(player, "h", 270);
let player = set(player, "dh", 0);
let player = set(player, "f", 0);
let player = set(player, "mode", "GET_READY");
let player = set(player, "timer", 200);
player
`);
const makePlayer = concoct({ newMap, set, resetPlayer }, `() =>
let m = newMap();
let m = set(m, "score", 0);
let m = set(m, "lives", 2);
let m = set(m, "mass", 1.0);
resetPlayer(m)
`);
// Utils
const updateAcceleration = concoct({ get, setr, cos, sin, degreesToRadians }, `(player) =>
const f = get(player, "f");
f === 0 ? setr("ay", 0, setr("ax", 0, player)) : (
const theta = degreesToRadians(get(player, "h"));
const fx = cos(theta) * f;
const fy = sin(theta) * f;
const m = get(player, "mass");
setr("ay", fy * m, setr("ax", fx * m, player))
)
`);
const updatePlayerDeath = concoct({ get, setr, update, resetPlayer, decr }, `(player) =>
get(player, "lives") > 0
? resetPlayer(update(player, "lives", decr))
: setr("mode", "GONE", player)
`);
const updatePlayerStep = concoct({ error, get, setr, cap, countDown, countDownToMode, updatePosition, updateAcceleration, updatePlayerDeath }, `(player) =>
get(player, "mode") === "GET_READY" ? countDownToMode(player, "PLAYING") : (
get(player, "mode") === "PLAYING" ? (
let player = updatePosition(player);
let player = setr("vx", cap(get(player, "vx") + get(player, "ax"), 6), player);
let player = setr("vy", cap(get(player, "vy") + get(player, "ay"), 6), player);
let player = setr("h", get(player, "h") + get(player, "dh"), player);
updateAcceleration(player)
) : (
get(player, "mode") === "EXPLODING" ? countDown(player, updatePlayerDeath) : error("Unhandled mode", get(player, "mode"))
)
)
`);
const updatePlayerControlsChanged = concoct({ get, setr, fireWentUp }, `(player, action) =>
get(player, "mode") === "PLAYING" ? (
let dh = action.leftPressed ? 0-5 : (action.rightPressed ? 5 : 0),
f = action.thrustPressed ? 0.05 : 0.0;
setr("dh", dh, setr("f", f, player))
) : (
get(player, "mode") === "GET_READY" && fireWentUp(action) ? (
setr("dh", 0, setr("f", 0, setr("mode", "PLAYING", player)))
) : (
setr("dh", 0, setr("f", 0, player))
)
)
`);
// Reducer that takes a Player and an Action and returns a new Player.
// Action must be one of: STEP | SCORE_POINTS | EXPLODE | CONTROLS_CHANGED
const updatePlayer = concoct({ error, get, set, isUndefined, makePlayer, updatePlayerStep, updatePlayerControlsChanged }, `(player, action) =>
isUndefined(player) ? makePlayer() : (
action.type === "STEP" ? updatePlayerStep(player) : (
action.type === "SCORE_POINTS" ? set(player, "score", get(player, "score") + 10) : (
action.type === "EXPLODE" ? set(set(player, "timer", 50), "mode", "EXPLODING") : (
action.type === "CONTROLS_CHANGED" ? updatePlayerControlsChanged(player, action) : error("Unhandled action", action.type)
)
)
)
)
`);
export { makePlayer, resetPlayer, updatePlayer };