2023-06-14 01:18:01 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-06-15 03:22:26 +00:00
|
|
|
export const ItemType = {
|
|
|
|
DOT: 1
|
|
|
|
}
|
|
|
|
|
2023-06-14 01:18:01 +00:00
|
|
|
/**
|
|
|
|
* @typedef {[number, number]} Vec2
|
|
|
|
*
|
2023-06-15 03:22:26 +00:00
|
|
|
* @typedef {{[key: number]: Key}} InputMap
|
2023-06-14 01:18:01 +00:00
|
|
|
*
|
2023-06-15 21:17:48 +00:00
|
|
|
* @typedef {{pos: Vec2, move_rot: Rotation, input_rot: Rotation, moving: boolean, name?: string}} Player
|
2023-06-14 01:18:01 +00:00
|
|
|
* @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
|
|
|
|
*
|
2023-06-15 03:22:26 +00:00
|
|
|
* @typedef {{type: ItemType, pos: Vec2}} Item
|
|
|
|
*
|
2023-06-14 01:18:01 +00:00
|
|
|
* @typedef {{
|
|
|
|
* started: boolean,
|
|
|
|
* input: InputMap,
|
|
|
|
* players: Players,
|
2023-06-15 03:22:26 +00:00
|
|
|
* map: Map,
|
2023-06-15 03:56:28 +00:00
|
|
|
* items_removed: Item[]
|
2023-06-14 01:18:01 +00:00
|
|
|
* }} GameState
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @type {GameState} */
|
|
|
|
export const initState = {
|
|
|
|
started: false,
|
|
|
|
input: {},
|
|
|
|
players: [],
|
2023-06-15 03:56:28 +00:00
|
|
|
map: {}
|
2023-06-14 01:18:01 +00:00
|
|
|
}
|
|
|
|
|
2023-06-15 03:22:26 +00:00
|
|
|
let last = Date.now()
|
|
|
|
let fps_div = document.getElementById("fps")
|
2023-06-15 03:56:28 +00:00
|
|
|
let frameCount = 0
|
2023-06-15 03:22:26 +00:00
|
|
|
|
2023-06-14 01:18:01 +00:00
|
|
|
export function advance(
|
|
|
|
pastData = initState,
|
|
|
|
input = { players: {} },
|
|
|
|
frame
|
|
|
|
) {
|
|
|
|
let data = processInput(pastData, input, frame);
|
2023-06-15 03:22:26 +00:00
|
|
|
|
2023-06-15 03:56:28 +00:00
|
|
|
if (frameCount == 60) {
|
|
|
|
frameCount = 0
|
2023-06-15 03:22:26 +00:00
|
|
|
let now = Date.now()
|
|
|
|
let fps = (now-last)/1000*60
|
|
|
|
fps_div.innerHTML = fps.toFixed(2);
|
|
|
|
last = now
|
|
|
|
}
|
2023-06-15 03:56:28 +00:00
|
|
|
|
|
|
|
frameCount++
|
2023-06-14 01:18:01 +00:00
|
|
|
|
|
|
|
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,
|
2023-06-15 21:17:48 +00:00
|
|
|
mov_rot: Rotation.EAST,
|
|
|
|
moving: false
|
2023-06-14 01:18:01 +00:00
|
|
|
};
|
|
|
|
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"
|
|
|
|
|
2023-06-14 02:22:42 +00:00
|
|
|
let width = 21
|
|
|
|
let height = 21
|
2023-06-14 01:18:01 +00:00
|
|
|
let m_data = [
|
2023-06-14 02:22:42 +00:00
|
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
|
|
1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,
|
|
|
|
1,0,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,1,0,1,
|
|
|
|
1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,1,
|
|
|
|
1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,
|
|
|
|
1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,
|
|
|
|
1,0,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,0,1,
|
|
|
|
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
|
|
|
|
1,1,1,0,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,
|
2023-06-15 03:22:26 +00:00
|
|
|
1,0,0,0,0,0,1,0,1,2,2,2,1,0,1,0,0,0,0,0,1,
|
|
|
|
1,0,1,1,1,0,1,0,1,2,2,2,1,0,1,0,1,1,1,0,1,
|
|
|
|
1,0,0,0,0,0,1,0,1,2,2,2,1,0,1,0,0,0,0,0,1,
|
|
|
|
1,1,1,0,1,0,1,0,1,1,2,1,1,0,1,0,1,0,1,1,1,
|
2023-06-14 02:22:42 +00:00
|
|
|
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
|
|
|
|
1,0,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,0,1,
|
|
|
|
1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,
|
|
|
|
1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,
|
|
|
|
1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,1,
|
|
|
|
1,0,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,1,0,1,
|
|
|
|
1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,
|
|
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
2023-06-14 01:18:01 +00:00
|
|
|
]
|
2023-06-14 02:22:42 +00:00
|
|
|
|
2023-06-14 01:18:01 +00:00
|
|
|
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)
|
2023-06-15 03:22:26 +00:00
|
|
|
return Math.abs(rpos[0] - pos[0]) < .05 && Math.abs(rpos[1] - pos[1]) < .05
|
2023-06-14 01:18:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-15 03:22:26 +00:00
|
|
|
export const get_item_key = (x, y, w) => {
|
|
|
|
let nx = Math.round(x * 2)
|
|
|
|
let ny = Math.round(y * 2)
|
|
|
|
let key = ny * w * 2 + nx
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
|
|
|
const ceil_half = (n) => {
|
|
|
|
return Math.ceil(n*2)/2
|
|
|
|
}
|
|
|
|
|
|
|
|
const floor_half = (n) => {
|
|
|
|
return Math.floor(n*2)/2
|
|
|
|
}
|
|
|
|
|
2023-06-14 01:18:01 +00:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
|
2023-06-15 03:22:26 +00:00
|
|
|
let tile_in_front_with_turn = get_tile_with_rot(data.map, current_pos, input_dir)
|
|
|
|
if (tile_in_front_with_turn == 1 || tile_in_front_with_turn == 2) {
|
2023-06-14 01:18:01 +00:00
|
|
|
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)
|
|
|
|
|
2023-06-15 03:22:26 +00:00
|
|
|
let tile_in_front = get_tile_with_rot(data.map, current_pos, move_dir)
|
|
|
|
if (tile_in_front != 1 && tile_in_front != 2) {
|
2023-06-14 01:18:01 +00:00
|
|
|
data.players[id].pos = move_pos
|
2023-06-15 21:17:48 +00:00
|
|
|
data.players[id].moving = true
|
2023-06-14 01:18:01 +00:00
|
|
|
} else {
|
|
|
|
data.players[id].pos = round_pos(current_pos)
|
2023-06-15 21:17:48 +00:00
|
|
|
data.players[id].moving = false
|
2023-06-14 01:18:01 +00:00
|
|
|
}
|
|
|
|
|
2023-06-15 03:22:26 +00:00
|
|
|
// eat items
|
|
|
|
let pos = data.players[id].pos
|
|
|
|
for (let x = ceil_half(pos[0]-.5); x <= floor_half(pos[0]+.5); x += .5) {
|
|
|
|
for (let y = ceil_half(pos[1]-.5); y <= floor_half(pos[1]+.5); y += .5) {
|
|
|
|
let item_key = get_item_key(x, y, data.map.width)
|
2023-06-15 03:56:28 +00:00
|
|
|
delete data.map.items[item_key]
|
2023-06-15 03:22:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-14 01:18:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|