dots and new sprites
|
@ -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;
|
||||
}
|
||||
|
|
BIN
client/img/dot.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
client/img/pac.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
client/img/wall_cross.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
client/img/wall_dot.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 563 B After Width: | Height: | Size: 563 B |
BIN
client/img/wall_end.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
client/img/wall_straight.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
client/img/wall_tee.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
client/img/wall_turn.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
|
@ -5,6 +5,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="center">
|
||||
<div id="fps"></div>
|
||||
<form id="join" autocomplete="off">
|
||||
<input type="text" id="room_code" name="room_code" placeholder="Room Code">
|
||||
<input type="text" id="name" name="name" placeholder="Player Name">
|
||||
|
@ -15,6 +16,7 @@
|
|||
<div id="players"></div>
|
||||
<input type="button" id="start" value="Start Game"/>
|
||||
</div>
|
||||
<canvas id="can"></canvas>
|
||||
</div>
|
||||
<script src="/js/main.js" type="module"></script>
|
||||
</body>
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 = ""
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 718 B |
Before Width: | Height: | Size: 699 B |
Before Width: | Height: | Size: 687 B |
Before Width: | Height: | Size: 687 B |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 668 B |
Before Width: | Height: | Size: 658 B |