aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/game/server.rs26
-rw-r--r--src/lib.rs2
-rw-r--r--src/spectate/index.html1
-rw-r--r--src/spectate/main.ts21
-rw-r--r--src/spectate/mod.rs20
-rw-r--r--src/spectate/server.rs6
-rw-r--r--src/spectate/style.css16
7 files changed, 73 insertions, 19 deletions
diff --git a/src/game/server.rs b/src/game/server.rs
index 2cc5d43..65a5cc9 100644
--- a/src/game/server.rs
+++ b/src/game/server.rs
@@ -37,25 +37,16 @@ async fn game_loop(config: Config, state: Arc<State>) {
let res = g.tick();
match res {
ControlFlow::Continue(()) => {
- let _ = state.tick.send(false);
+ let _ = state.tick.send(None);
speed += config.tickrate_speedup;
speed = speed.min(config.tickrate_max)
}
ControlFlow::Break(winner) => {
info!("winner: {winner:?}");
- if let Some(winner) = winner {
- if let Some(winner) = state.players.write().await.get(&winner).cloned() {
- let mut h = state.win_history.write().await;
- h.push_front(winner);
- while h.len() > 64 {
- h.pop_back();
- }
- }
- }
let p = state.players.read().await;
*g = Game::new(p.clone().into_iter().collect());
speed = config.tickrate;
- let _ = state.tick.send(true);
+ let _ = state.tick.send(Some(winner));
}
}
drop(g);
@@ -126,12 +117,12 @@ async fn handle_tick(
mut tx: impl AsyncWrite + Unpin,
cstate: &mut ClientState,
state: &Arc<State>,
- new_game: bool,
+ new_game: Option<Option<u32>>,
) -> anyhow::Result<()> {
let Some(pid) = cstate.pid else { return Ok(()) };
let mut events = Vec::new();
- if new_game {
- if cstate.alive {
+ if let Some(winner) = new_game {
+ if winner == cstate.pid {
tx.send_packet(Packet::Win(0, 0)).await?;
}
cstate.alive = true;
@@ -141,7 +132,7 @@ async fn handle_tick(
}
{
let g = state.game.read().await;
- if new_game {
+ if new_game.is_some() {
events.push(Packet::Game {
my_id: pid,
width: g.map.size.x as usize,
@@ -235,6 +226,11 @@ async fn handle_packet(
}
}
Packet::Chat(message) => {
+ if message.len() > 128 {
+ tx.send_packet(Packet::Error("chat message too long (> 128)".to_string()))
+ .await?;
+ return Ok(());
+ }
let _ = state.chat.send(("".to_string(), message));
}
_ => {
diff --git a/src/lib.rs b/src/lib.rs
index 757fb13..e216643 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,7 +12,7 @@ pub mod game;
pub mod spectate;
pub struct State {
- pub tick: broadcast::Sender<bool>, // true for new game
+ pub tick: broadcast::Sender<Option<Option<u32>>>,
pub game: RwLock<Game>,
pub players: RwLock<HashMap<u32, String>>,
pub win_history: RwLock<VecDeque<String>>,
diff --git a/src/spectate/index.html b/src/spectate/index.html
index 5b9dc5a..0c86aec 100644
--- a/src/spectate/index.html
+++ b/src/spectate/index.html
@@ -44,6 +44,7 @@
</div>
<div id="chat">
<h2>Chat</h2>
+ <div id="chat_messages"></div>
</div>
</div>
<div id="board"></div>
diff --git a/src/spectate/main.ts b/src/spectate/main.ts
index 508ca30..3fcf414 100644
--- a/src/spectate/main.ts
+++ b/src/spectate/main.ts
@@ -7,6 +7,8 @@ type Packet = "tick"
| { game: { width: number, height: number } }
| { player: { id: number, name: string } }
| { die: number[] }
+ | { win: [number, number] }
+ | { chat: string }
class Snake {
parts: { x: number, y: number, dx: number, dy: number }[] = []
@@ -132,15 +134,34 @@ ws.onmessage = message => {
} else if ("game" in p) {
snakes.clear()
size = p.game.width
+ } else if ("win" in p) {
+ const winner = snakes.get(p.win[0])?.name;
+ if (winner) chat(`${winner} won this round.`, "win")
+ else chat(`round ended in a tie.`, "tie")
} else if ("player" in p) {
snakes.set(p.player.id, new Snake(p.player.name))
} else if ("pos" in p) {
snakes.get(p.pos.id)?.add_part(p.pos.x, p.pos.y)
} else if ("die" in p) {
+ chat(`${p.die.map(e => snakes.get(e)?.name).join(", ")} died.`, "die")
for (const d of p.die) {
const s = snakes.get(d)
if (s) s.dead = true
}
+ } else if ("chat" in p) {
+ chat(p.chat)
}
}
+const chat_history: HTMLElement[] = [];
+function chat(message: string, clas = "chat") {
+ const e = document.createElement("p")
+ e.textContent = message;
+ e.classList.add("message")
+ e.classList.add(clas)
+ document.getElementById("chat_messages")!.prepend(e)
+ chat_history.push(e);
+ while (chat_history.length > 64) {
+ chat_history.splice(0, 1).forEach(e => e.remove())
+ }
+}
diff --git a/src/spectate/mod.rs b/src/spectate/mod.rs
index f80551e..3da4a0d 100644
--- a/src/spectate/mod.rs
+++ b/src/spectate/mod.rs
@@ -1,5 +1,6 @@
+use crate::State;
use serde::Deserialize;
-use std::net::SocketAddr;
+use std::{net::SocketAddr, sync::Arc};
pub mod server;
@@ -7,3 +8,20 @@ pub mod server;
pub struct Config {
bind: SocketAddr,
}
+
+async fn stats_loop(state: Arc<State>) {
+ let mut ticks = state.tick.subscribe();
+ while let Ok(end) = ticks.recv().await {
+ if let Some(winner) = end {
+ if let Some(winner) = winner {
+ if let Some(winner) = state.players.write().await.get(&winner).cloned() {
+ let mut h = state.win_history.write().await;
+ h.push_front(winner);
+ while h.len() > 64 {
+ h.pop_back();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/spectate/server.rs b/src/spectate/server.rs
index 11c4d5a..0f51c2a 100644
--- a/src/spectate/server.rs
+++ b/src/spectate/server.rs
@@ -1,5 +1,5 @@
use super::Config;
-use crate::{game::protocol::Packet, State};
+use crate::{game::protocol::Packet, spectate::stats_loop, State};
use anyhow::{anyhow, Result};
use axum::{
extract::{
@@ -26,6 +26,7 @@ struct SpectateState {
}
pub async fn spectate_server(config: Config, state: Arc<State>) -> Result<()> {
+ spawn(stats_loop(state.clone()));
let sstate = Arc::new(SpectateState {
past_events: Default::default(),
events: broadcast::channel(512).0,
@@ -115,7 +116,8 @@ async fn broadcaster(sstate: Arc<SpectateState>, state: Arc<State>) {
{
let g = state.game.read().await;
events.push(Packet::Tick);
- if new_game {
+ if let Some(winner) = new_game {
+ events.push(Packet::Win(winner.unwrap_or(u32::MAX) as usize, 0)); // TODO packet misuse
sstate.past_events.write().await.clear();
events.push(Packet::Game {
my_id: 0,
diff --git a/src/spectate/style.css b/src/spectate/style.css
index c391020..5d15e9a 100644
--- a/src/spectate/style.css
+++ b/src/spectate/style.css
@@ -41,3 +41,19 @@ canvas {
height: 40%;
overflow-y: scroll;
}
+
+.message {
+ margin: 2px;
+}
+.message.chat {
+ color: rgb(187, 187, 187);
+}
+.message.die {
+ color: rgb(255, 136, 136);
+}
+.message.win {
+ color: rgb(138, 255, 128);
+}
+.message.tie {
+ color: rgb(255, 221, 128);
+}