tuxman/client/js/logic.js
2023-06-13 21:18:01 -04:00

270 lines
6.4 KiB
JavaScript

import { Map } from "./gfx/map.js";
// enum
export const Key = {
NOTHING: undefined,
UP: 1,
DOWN: 2,
LEFT: 3,
RIGHT: 4,
}
// enum
export const Rotation = {
NOTHING: undefined,
NORTH: 1,
EAST: 2,
SOUTH: 3,
WEST: 4
}
/**
* @typedef {[number, number]} Vec2
*
* @typedef {{[key: number]: Key} InputMap
*
* @typedef {{pos: Vec2, move_rot: Rotation, input_rot: Rotation, name?: string}} Player
* @typedef {{start: boolean, key: Key, name?: string}} PlayerInput
* @typedef {{players: {[key: number]: PlayerInput}, added?: number[], removed?: number[] }} Input
*
* @typedef {{[key: number]: Player}} Players
*
* @typedef {{width: number, height: number, data: number[]}} Map
*
* @typedef {{
* started: boolean,
* input: InputMap,
* players: Players,
* map: Map
* }} GameState
*/
/** @type {GameState} */
export const initState = {
started: false,
input: {},
players: [],
map: {}
}
export function advance(
pastData = initState,
input = { players: {} },
frame
) {
let data = processInput(pastData, input, frame);
return data;
}
/**
* @param {GameState} pastData
* @param {Input} input
* @param {number} frame
*/
function processInput(pastData, input) {
/** @type {GameState} */
let data = structuredClone(pastData)
let startPressed = false;
for(const added of input.added || []) {
if (data.started || Object.keys(data.players).length >= 4) continue;
console.log("added", added);
data.input[added] ||= {
pos: [1, 1],
input_rot: Rotation.EAST,
mov_rot: Rotation.EAST
};
if(!(added in data.players)) {
data.players[added] = structuredClone(data.input[added])
}
}
for(const id in input.players) {
if(!input.players[id]) {
continue;
}
if(id in data.players && input.players[id].name !== undefined) {
let name = input.players[id].name;
name = name.substring(0, 16);
data.players[id] = {
...data.players[id],
name,
};
}
startPressed ||= input.players[id].start;
data.input[id] = input.players[id].dir
}
const player_display = document.getElementById("players")
for (const id in data.players) {
if (data.players[id] === null) continue
let name = data.players[id].name
if (name === undefined) continue
let element_id = 'span' + id
let element = player_display.children[element_id]
if (element === null || element === undefined) {
let span = document.createElement("span")
span.innerHTML = `[${id}] ${name}`
span.id = element_id
player_display.appendChild(span)
}
}
if (startPressed && !data.started) {
init_map(data)
data.started ||= startPressed;
}
if (data.started) {
update_players(data)
}
for(const removed of input.removed || []) {
console.log("removed", removed);
delete data.input[removed];
delete data.players[removed];
let element_id = 'span' + removed
let element = document.getElementById(element_id)
if (element !== null && element !== undefined) element.remove()
}
return data
}
const init_map = (data) => {
document.getElementById("lobby").style.display = "none"
let width = 13
let height = 5
let m_data = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1,
1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1,
1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1
]
data.map = new Map(width, height, m_data)
data.map.show()
}
const MOVE_SPEED = .1
const round_pos = (pos) => {
return [Math.round(pos[0]), Math.round(pos[1])]
}
const is_stable_pos = (pos) => {
let rpos = round_pos(pos)
return Math.abs(rpos[0] - pos[0]) < MOVE_SPEED && Math.abs(rpos[1] - pos[1]) < MOVE_SPEED
}
const get_tile = (map, pos, ox, oy) => {
let x = Math.round(pos[0] + ox)
let y = Math.round(pos[1] + oy)
if (x < 0 || x >= map.width || y < 0 || y >= map.height) return 1
return map.data[y * map.width + x]
}
const get_tile_with_rot = (map, pos, rot) => {
let collider = 1
switch(rot) {
case Rotation.NORTH:
collider = get_tile(map, pos, 0, -.51)
break
case Rotation.SOUTH:
collider = get_tile(map, pos, 0, .51)
break
case Rotation.WEST:
collider = get_tile(map, pos, -.51, 0)
break
case Rotation.EAST:
collider = get_tile(map, pos, .51, 0)
break
}
return collider
}
const get_rot = (dir) => {
switch (dir) {
case Key.UP: return Rotation.NORTH
case Key.DOWN: return Rotation.SOUTH
case Key.LEFT: return Rotation.WEST
case Key.RIGHT: return Rotation.EAST
case Key.NOTHING: return Rotation.NOTHING
}
}
const increment_pos = (pos, rot, speed) => {
switch (rot) {
case Rotation.NORTH:
pos[1] -= speed
break
case Rotation.SOUTH:
pos[1] += speed
break
case Rotation.WEST:
pos[0] -= speed
break
case Rotation.EAST:
pos[0] += speed
break
}
}
/**
* @param {GameState} data
*/
const update_players = (data) => {
for(const id in data.input) { // move players
if(!(id in data.players)) {
console.log("what. player undefined???", id);
continue;
}
let input_key = data.input[id]
let input_dir = get_rot(input_key)
let move_dir = data.players[id].move_rot
let current_pos = data.players[id].pos
if (get_tile_with_rot(data.map, current_pos, input_dir) == 1) {
input_dir = Rotation.NOTHING
}
let turning = input_dir != Key.NOTHING && input_dir != move_dir
data.players[id].input_rot = input_dir
if (turning && is_stable_pos(current_pos)) {
current_pos = round_pos(current_pos)
data.players[id].move_rot = input_dir
move_dir = input_dir
}
let move_pos = structuredClone(current_pos)
increment_pos(move_pos, move_dir, MOVE_SPEED)
if (get_tile_with_rot(data.map, current_pos, move_dir) != 1) {
data.players[id].pos = move_pos
} else {
data.players[id].pos = round_pos(current_pos)
}
}
}