dots and new sprites

This commit is contained in:
Freya Murphy 2023-06-14 23:22:26 -04:00
parent 5de011115c
commit 94e03b3f3c
23 changed files with 236 additions and 81 deletions

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
client/img/pac.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
client/img/wall_cross.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

BIN
client/img/wall_dot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

Before

Width:  |  Height:  |  Size: 563 B

After

Width:  |  Height:  |  Size: 563 B

BIN
client/img/wall_end.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
client/img/wall_tee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
client/img/wall_turn.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -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>

View file

@ -1,39 +1,8 @@
import { Sprite } from './sprite.js'
import { Rotation } from '../logic.js'
import { ItemType, Rotation } from '../logic.js'
export const startGraphicsUpdater = () => {
let sprites = []
/**
* @type {(data: import("../logic.js").GameState) => void}
*/
return (data) => {
if (!data.started) 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
}
}
const draw_players = (data, players, sprites) => {
for (let id of players) {
let pos = data.players[id].pos
sprites[id].move(pos[0], pos[1])
@ -52,5 +21,85 @@ export const startGraphicsUpdater = () => {
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 player_sprites = []
let item_sprites = {}
/**
* @type {(data: import("../logic.js").GameState) => void}
*/
return (data) => {
if (!data.map || !data.map.visible) return
let players = Object.keys(data.players).filter(k => data.players[k] !== undefined)
if (player_sprites.length !== players.length) {
player_sprites = update_player_sprites(data, players, player_sprites)
console.log("updating player sprites")
}
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)
}
}

View file

@ -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
}
}

View file

@ -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()

View file

@ -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 = ""
}

View file

@ -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,9 +51,13 @@ 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: {} },
@ -54,6 +65,13 @@ export function advance(
) {
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]
}
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 687 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 B