270 lines
6.4 KiB
JavaScript
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)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|