diff --git a/client/css/main.css b/client/css/main.css index bfb7426..d1d97e8 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -39,7 +39,7 @@ body { .sprite { position: absolute; - transition: left .1s, top .1s; + /* transition: left .1s, top .1s; */ z-index: 2; } @@ -72,3 +72,13 @@ input { margin-bottom: .215rem; } +#fps { + position: absolute; + left: 0; + top: 0; + z-index: 99; + font-size: 20; + font-style: monospace; + background-color: black; + color: white; +} diff --git a/client/img/dot.png b/client/img/dot.png new file mode 100644 index 0000000..b911cea Binary files /dev/null and b/client/img/dot.png differ diff --git a/client/img/pac.png b/client/img/pac.png new file mode 100644 index 0000000..7dc0989 Binary files /dev/null and b/client/img/pac.png differ diff --git a/client/img/wall_cross.png b/client/img/wall_cross.png new file mode 100644 index 0000000..9553645 Binary files /dev/null and b/client/img/wall_cross.png differ diff --git a/client/img/wall_dot.png b/client/img/wall_dot.png new file mode 100644 index 0000000..2e7fa15 Binary files /dev/null and b/client/img/wall_dot.png differ diff --git a/client/static/empty.png b/client/img/wall_empty.png similarity index 100% rename from client/static/empty.png rename to client/img/wall_empty.png diff --git a/client/img/wall_end.png b/client/img/wall_end.png new file mode 100644 index 0000000..752f73b Binary files /dev/null and b/client/img/wall_end.png differ diff --git a/client/img/wall_straight.png b/client/img/wall_straight.png new file mode 100644 index 0000000..2dc5448 Binary files /dev/null and b/client/img/wall_straight.png differ diff --git a/client/img/wall_tee.png b/client/img/wall_tee.png new file mode 100644 index 0000000..31742bd Binary files /dev/null and b/client/img/wall_tee.png differ diff --git a/client/img/wall_turn.png b/client/img/wall_turn.png new file mode 100644 index 0000000..dbe5837 Binary files /dev/null and b/client/img/wall_turn.png differ diff --git a/client/index.html b/client/index.html index 0740f5f..50ae8ef 100644 --- a/client/index.html +++ b/client/index.html @@ -5,6 +5,7 @@
+
@@ -15,6 +16,7 @@
+ diff --git a/client/js/gfx/graphics.js b/client/js/gfx/graphics.js index e73927e..31565a8 100644 --- a/client/js/gfx/graphics.js +++ b/client/js/gfx/graphics.js @@ -1,56 +1,105 @@ import { Sprite } from './sprite.js' -import { Rotation } from '../logic.js' +import { ItemType, Rotation } from '../logic.js' +const draw_players = (data, players, sprites) => { + for (let id of players) { + let pos = data.players[id].pos + sprites[id].move(pos[0], pos[1]) + switch (data.players[id].move_rot) { + case Rotation.NORTH: + sprites[id].rotate(270) + break + case Rotation.EAST: + sprites[id].rotate(0) + break + case Rotation.SOUTH: + sprites[id].rotate(90) + break + case Rotation.WEST: + sprites[id].rotate(180) + break + } + } +} + +const update_player_sprites = (data, players, sprites) => { + for (const sprite of sprites) { + if (sprite !== undefined) { + sprite.destroy() + } + } + + let new_sprites = Array(players) + new_sprites.fill(undefined) + + for (let id of players) { + let sprite = new Sprite("/img/pac.png", data.map) + sprite.layer(3) + sprite.resize(1,1) + sprite.show() + new_sprites[id] = sprite + } + + return new_sprites +} + +const create_map_dot = (data, x, y) => { + let dot = new Sprite("/img/dot.png", data.map) + dot.move(x, y) + dot.resize(.2,.2) + dot.show() + return dot +} + +const update_item_sprites = (data) => { + + let item_sprites = {} + + for (let item_key in data.map.items) { + let item = data.map.items[item_key] + switch (item.type) { + case ItemType.DOT: + item_sprites[item_key] = create_map_dot(data, item.pos[0], item.pos[1]) + break; + } + } + + return item_sprites +} + export const startGraphicsUpdater = () => { - let sprites = [] + let player_sprites = [] + let item_sprites = {} /** * @type {(data: import("../logic.js").GameState) => void} */ return (data) => { - if (!data.started) return + if (!data.map || !data.map.visible) return let players = Object.keys(data.players).filter(k => data.players[k] !== undefined) - if (sprites.length !== players.length) { - - for (const sprite of sprites) { - if (sprite !== undefined) { - sprite.destroy() - } - } - - sprites = Array(players) - sprites.fill(undefined) - - for (let id of players) { - let sprite = new Sprite("/static/tux.png", data.map) - sprite.show() - sprite.resize(1.5,1.5) - sprites[id] = sprite - } + if (player_sprites.length !== players.length) { + player_sprites = update_player_sprites(data, players, player_sprites) + console.log("updating player sprites") } - for (let id of players) { - let pos = data.players[id].pos - sprites[id].move(pos[0], pos[1]) - switch (data.players[id].move_rot) { - case Rotation.NORTH: - sprites[id].rotate(270) - break - case Rotation.EAST: - sprites[id].rotate(0) - break - case Rotation.SOUTH: - sprites[id].rotate(90) - break - case Rotation.WEST: - sprites[id].rotate(180) - break - } + for (let item_key of data.items_eaten) { + if (!(item_sprites[item_key])) continue + item_sprites[item_key].destroy() + delete item_sprites[item_key] + delete data.items_eaten[item_key] } + + if (Object.keys(item_sprites).length !== Object.keys(data.map.items).length) { + item_sprites = update_item_sprites(data) + console.log("updating item sprites") + } + + draw_players(data, players, player_sprites) + } } diff --git a/client/js/gfx/map.js b/client/js/gfx/map.js index 0ba7f9c..e629b17 100644 --- a/client/js/gfx/map.js +++ b/client/js/gfx/map.js @@ -1,3 +1,5 @@ +import { ItemType, get_item_key } from "../logic.js"; + const gen_style = (map, style) => { const css = ` * { @@ -63,71 +65,71 @@ const place_tile = (container, type) => { let image_src, class_name; switch(type) { case Direction.EMPTY: - image_src = "/static/empty.png" + image_src = "/img/wall_empty.png" class_name = "" break case Direction.WALL_HZ: - image_src = "/static/wall.png" + image_src = "/img/wall_straight.png" class_name = "" break case Direction.WALL_VT: - image_src = "/static/wall.png" + image_src = "/img/wall_straight.png" class_name = "rotate90" break case Direction.TURN_Q1: - image_src = "/static/turn.png" + image_src = "/img/wall_turn.png" class_name = "" break case Direction.TURN_Q2: - image_src = "/static/turn.png" + image_src = "/img/wall_turn.png" class_name = "rotate270" break case Direction.TURN_Q3: - image_src = "/static/turn.png" + image_src = "/img/wall_turn.png" class_name = "rotate180" break case Direction.TURN_Q4: - image_src = "/static/turn.png" + image_src = "/img/wall_turn.png" class_name = "rotate90" break case Direction.TEE_NORTH: - image_src = "/static/tee.png" + image_src = "/img/wall_tee.png" class_name = "rotate180" break case Direction.TEE_EAST: - image_src = "/static/tee.png" + image_src = "/img/wall_tee.png" class_name = "rotate270" break case Direction.TEE_SOUTH: - image_src = "/static/tee.png" + image_src = "/img/wall_tee.png" class_name = "" break case Direction.TEE_WEST: - image_src = "/static/tee.png" + image_src = "/img/wall_tee.png" class_name = "rotate90" break case Direction.CROSS: - image_src = "/static/cross.png" + image_src = "/img/wall_cross.png" class_name = "" break case Direction.DOT: - image_src = "/static/dot.png" + image_src = "/img/wall_dot.png" class_name = "" break case Direction.WALL_END_NORTH: - image_src = "/static/wall_end.png" + image_src = "/img/wall_end.png" class_name = "" break; case Direction.WALL_END_EAST: - image_src = "/static/wall_end.png" + image_src = "/img/wall_end.png" class_name = "rotate90" break; case Direction.WALL_END_SOUTH: - image_src = "/static/wall_end.png" + image_src = "/img/wall_end.png" class_name = "rotate180" break; case Direction.WALL_END_WEST: - image_src = "/static/wall_end.png" + image_src = "/img/wall_end.png" class_name = "rotate270" break; } @@ -152,11 +154,11 @@ const gen_walls = (width, height, data) => { for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - let north = get_point(width, height, data, x, y-1) - let south = get_point(width, height, data, x, y+1) - let east = get_point(width, height, data, x+1, y) - let west = get_point(width, height, data, x-1, y) - let current = get_point(width, height, data, x, y) + let north = get_point(width, height, data, x, y-1) == 1 + let south = get_point(width, height, data, x, y+1) == 1 + let east = get_point(width, height, data, x+1, y) == 1 + let west = get_point(width, height, data, x-1, y) == 1 + let current = get_point(width, height, data, x, y) == 1 let point = Direction.EMPTY @@ -213,11 +215,45 @@ const gen_map = (map, container) => { place_tile(container, map.walls[y * map.width + x]) } } +} +const gen_items = (map) => { + + let width = map.width + let height = map.height + + let items = {} + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + let tile = get_point(width, height, map.data, x, y) + if (tile != 0) continue + + let item_key = get_item_key(x, y, width) + items[item_key] = {type: ItemType.DOT, pos: [x, y]} + + let tile_south = get_point(width, height, map.data, x, y + 1) + if (tile_south == 0) { + item_key = get_item_key(x, y + .5, width) + items[item_key] = {type: ItemType.DOT, pos: [x, y + .5]} + } + + let tile_east = get_point(width, height, map.data, x + 1, y) + if (tile_east == 0) { + item_key = get_item_key(x + .5, y, width) + items[item_key] = {type: ItemType.DOT, pos: [x + .5, y]} + } + } + } + + return items } export class Map { + static data + static walls + constructor(width, height, data) { let last = document.getElementById("container") @@ -227,6 +263,8 @@ export class Map { this.height = height this.data = data this.walls = gen_walls(width, height, data) + this.items = gen_items(this) + this.visible = false } @@ -250,6 +288,8 @@ export class Map { } gen_style(this, style) + + this.visible = true } hide() { @@ -257,5 +297,7 @@ export class Map { if (container) container.remove() let style = document.getElementById("style") if (style) style.remove() + + this.visible = false } } diff --git a/client/js/gfx/sprite.js b/client/js/gfx/sprite.js index 1ed8136..b793d89 100644 --- a/client/js/gfx/sprite.js +++ b/client/js/gfx/sprite.js @@ -11,6 +11,7 @@ export class Sprite { this.y = 0 this.w = 1 this.h = 1 + this.z = 1 this.d = 0 this.hide() } @@ -26,6 +27,7 @@ export class Sprite { this.element.style.left = `${left}%` this.element.style.top = `${top}%` this.element.style.transform = `rotate(${this.d}deg)` + this.element.style.zIndex = `${this.z}` } move(x, y) { @@ -40,6 +42,11 @@ export class Sprite { this.#update_pos() } + layer(z) { + this.z = z + this.#update_pos() + } + rotate(d) { this.d = d this.#update_pos() diff --git a/client/js/input.js b/client/js/input.js index abe64bb..3928e91 100644 --- a/client/js/input.js +++ b/client/js/input.js @@ -31,7 +31,14 @@ export function startInputListener() { if (ev.code === "KeyB") { debug_enabled = !debug_enabled if (debug_enabled) { - debug_style.innerHTML = "* {box-shadow: 0 0 3px red inset;}" + debug_style.innerHTML = ` \ + #container img { \ + box-shadow: 0 0 1px red inset; \ + } \ + #container .sprite { \ + box-shadow: 0 0 1px white inset; \ + } \ + ` } else { debug_style.innerHTML = "" } diff --git a/client/js/logic.js b/client/js/logic.js index 405366c..e869c56 100644 --- a/client/js/logic.js +++ b/client/js/logic.js @@ -18,10 +18,14 @@ export const Rotation = { WEST: 4 } +export const ItemType = { + DOT: 1 +} + /** * @typedef {[number, number]} Vec2 * - * @typedef {{[key: number]: Key} InputMap + * @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 @@ -31,11 +35,14 @@ export const Rotation = { * * @typedef {{width: number, height: number, data: number[]}} Map * + * @typedef {{type: ItemType, pos: Vec2}} Item + * * @typedef {{ * started: boolean, * input: InputMap, * players: Players, - * map: Map + * map: Map, + * items_eaten: Item[] * }} GameState */ @@ -44,15 +51,26 @@ export const initState = { started: false, input: {}, players: [], - map: {} + map: {}, + items_eaten: [] } +let last = Date.now() +let fps_div = document.getElementById("fps") + export function advance( pastData = initState, input = { players: {} }, frame ) { let data = processInput(pastData, input, frame); + + if (frame % 60 == 0) { + let now = Date.now() + let fps = (now-last)/1000*60 + fps_div.innerHTML = fps.toFixed(2); + last = now + } return data; } @@ -147,16 +165,6 @@ 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 - // ] - let width = 21 let height = 21 let m_data = [ @@ -169,10 +177,10 @@ const init_map = (data) => { 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, - 1,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,1, - 1,0,1,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1, - 1,0,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,1, - 1,1,1,0,1,0,1,0,1,1,0,1,1,0,1,0,1,0,1,1,1, + 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, 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, @@ -195,7 +203,7 @@ const round_pos = (pos) => { 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 + return Math.abs(rpos[0] - pos[0]) < .05 && Math.abs(rpos[1] - pos[1]) < .05 } const get_tile = (map, pos, ox, oy) => { @@ -251,6 +259,21 @@ const increment_pos = (pos, rot, speed) => { } } +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 +} + /** * @param {GameState} data */ @@ -267,7 +290,8 @@ const update_players = (data) => { 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) { + 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) { input_dir = Rotation.NOTHING } @@ -284,12 +308,26 @@ const update_players = (data) => { 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) { + let tile_in_front = get_tile_with_rot(data.map, current_pos, move_dir) + if (tile_in_front != 1 && tile_in_front != 2) { data.players[id].pos = move_pos } else { data.players[id].pos = round_pos(current_pos) } + // 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) + let item = data.map.items[item_key] + if (item) { + data.items_eaten.push(item_key) + delete data.map.items[item_key] + } + } + } + } } diff --git a/client/static/cross.png b/client/static/cross.png deleted file mode 100644 index c8d1054..0000000 Binary files a/client/static/cross.png and /dev/null differ diff --git a/client/static/dot.png b/client/static/dot.png deleted file mode 100644 index bab810b..0000000 Binary files a/client/static/dot.png and /dev/null differ diff --git a/client/static/tee.png b/client/static/tee.png deleted file mode 100644 index ca144be..0000000 Binary files a/client/static/tee.png and /dev/null differ diff --git a/client/static/turn.png b/client/static/turn.png deleted file mode 100644 index c951f17..0000000 Binary files a/client/static/turn.png and /dev/null differ diff --git a/client/static/tux.png b/client/static/tux.png deleted file mode 100644 index a595145..0000000 Binary files a/client/static/tux.png and /dev/null differ diff --git a/client/static/wall.png b/client/static/wall.png deleted file mode 100644 index 19d4168..0000000 Binary files a/client/static/wall.png and /dev/null differ diff --git a/client/static/wall_end.png b/client/static/wall_end.png deleted file mode 100644 index 4f09e19..0000000 Binary files a/client/static/wall_end.png and /dev/null differ