diff options
author | metamuffin <metamuffin@disroot.org> | 2024-06-04 16:48:07 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-06-04 16:48:07 +0200 |
commit | 34967cd3b6530656ef0bf31810f9fd6dfb853765 (patch) | |
tree | 912bb0995db6997b601f246cfb0420b6b3ab2101 /src/spectate | |
parent | ce0b808a01081322abc7ed51e09d0f452b606ad7 (diff) | |
download | gpn-tron-rust-34967cd3b6530656ef0bf31810f9fd6dfb853765.tar gpn-tron-rust-34967cd3b6530656ef0bf31810f9fd6dfb853765.tar.bz2 gpn-tron-rust-34967cd3b6530656ef0bf31810f9fd6dfb853765.tar.zst |
can vie games
Diffstat (limited to 'src/spectate')
-rw-r--r-- | src/spectate/index.html | 6 | ||||
-rw-r--r-- | src/spectate/main.ts | 93 | ||||
-rw-r--r-- | src/spectate/server.rs | 28 |
3 files changed, 124 insertions, 3 deletions
diff --git a/src/spectate/index.html b/src/spectate/index.html index 235a894..94ef698 100644 --- a/src/spectate/index.html +++ b/src/spectate/index.html @@ -4,8 +4,12 @@ <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>GPN Tron</title> + <script src="main.js"></script> </head> <body> - + <noscript> + This is the GPN-Tron spectator application. You need JavaScript to + run it. + </noscript> </body> </html> diff --git a/src/spectate/main.ts b/src/spectate/main.ts new file mode 100644 index 0000000..4d508b6 --- /dev/null +++ b/src/spectate/main.ts @@ -0,0 +1,93 @@ +/// <reference lib="dom" /> + +const ws = new WebSocket("/events") + +type Packet = "tick" + | { pos: { id: number, x: number, y: number } } + | { game: { width: number, height: number } } + | { player: { id: number, name: string } } + +class Snake { + parts: { x: number, y: number, dx: number, dy: number }[] = [] + constructor(public name: string) { } + add_part(x: number, y: number) { + if (!this.parts.length) return this.parts.push({ x, y, dx: 0, dy: 0 }) + const last = this.parts[this.parts.length - 1] + let dx = x - last.x, dy = y - last.y; + if (x > last.x + 1 || x < last.x - 1) dx *= -1, dx /= Math.abs(dx) + if (y > last.y + 1 || y < last.y - 1) dy *= -1, dy /= Math.abs(dy) + this.parts.push({ x, y, dx, dy }) + console.log(this.parts); + + } +} +let size = 0 +let snakes = new Map<number, Snake>() + +let canvas: HTMLCanvasElement +let ctx: CanvasRenderingContext2D + +document.addEventListener("DOMContentLoaded", () => { + canvas = document.createElement("canvas") + ctx = canvas.getContext("2d")! + document.body.append(canvas) + canvas.width = 1000; + canvas.height = 1000; + redraw() +}) + +function redraw() { + ctx.fillStyle = "black" + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.save() + const scale = canvas.width / size + ctx.scale(scale, scale) + for (let x = 0; x < size; x++) { + for (let y = 0; y < size; y++) { + ctx.strokeStyle = "grey" + ctx.lineWidth = 0.02 + ctx.strokeRect(x, y, 1, 1) + } + } + ctx.translate(0.5, 0.5) + for (const [xo, yo] of [[0, 0], [-size, 0], [size, 0], [0, -size], [0, size]]) { + ctx.save() + ctx.translate(xo, yo) + for (const snake of snakes.values()) { + ctx.beginPath(); + for (let i = 0; i < snake.parts.length; i++) { + const p = snake.parts[i]; + ctx.moveTo(p.x - p.dx, p.y - p.dy) + ctx.lineTo(p.x, p.y) + } + ctx.lineCap = "round" + ctx.lineJoin = "round" + ctx.lineWidth = 0.6; + ctx.strokeStyle = "red" + ctx.stroke() + } + ctx.restore() + } + ctx.restore() + + requestAnimationFrame(redraw) +} + +ws.onerror = console.error +ws.onmessage = message => { + const p = JSON.parse(message.data) as Packet + console.log(p); + if (p == "tick") { + + } else if ("game" in p) { + snakes.clear() + size = p.game.width + } 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) + } + +} + diff --git a/src/spectate/server.rs b/src/spectate/server.rs index 9dbfebe..e3e9f80 100644 --- a/src/spectate/server.rs +++ b/src/spectate/server.rs @@ -6,6 +6,7 @@ use anyhow::Result; use axum::extract; use axum::extract::connect_info::ConnectInfo; use axum::extract::ws::Message; +use axum::http::HeaderMap; use axum::response::Html; use axum::{ extract::ws::{WebSocket, WebSocketUpgrade}, @@ -13,8 +14,10 @@ use axum::{ routing::get, Router, }; +use headers::ContentType; use log::{info, warn}; use std::net::SocketAddr; +use std::str::FromStr; use std::sync::Arc; use tokio::spawn; use tokio::sync::{broadcast, RwLock}; @@ -32,6 +35,7 @@ pub async fn spectate_server(config: Config, state: Arc<State>) -> Result<()> { spawn(broadcaster(sstate.clone(), state)); let app = Router::new() .route("/", get(index)) + .route("/main.js", get(javascript)) .route("/events", get(ws_handler)) .with_state(sstate); let listener = tokio::net::TcpListener::bind(config.bind).await.unwrap(); @@ -47,6 +51,26 @@ pub async fn spectate_server(config: Config, state: Arc<State>) -> Result<()> { async fn index() -> Html<&'static str> { Html(include_str!("index.html")) } +#[cfg(debug_assertions)] +async fn javascript() -> (HeaderMap, String) { + use headers::HeaderMapExt; + use tokio::fs::read_to_string; + let mut hm = HeaderMap::new(); + hm.typed_insert(ContentType::from_str("application/javascript").unwrap()); + ( + hm, + read_to_string(concat!(env!("OUT_DIR"), "/main.js")) + .await + .unwrap(), + ) +} +#[cfg(not(debug_assertions))] +async fn javascript() -> (HeaderMap, &'static str) { + use headers::HeaderMapExt; + let mut hm = HeaderMap::new(); + hm.typed_insert(ContentType::from_str("application/javascript").unwrap()); + (hm, include_str!(concat!(env!("OUT_DIR"), "/main.js"))) +} async fn broadcaster(sstate: Arc<SpectateState>, state: Arc<State>) { let mut ticks = state.tick.subscribe(); @@ -59,8 +83,8 @@ async fn broadcaster(sstate: Arc<SpectateState>, state: Arc<State>) { sstate.past_events.write().await.clear(); events.push(Packet::Game { my_id: 0, - width: g.size.x as usize, - height: g.size.y as usize, + width: g.map.size.x as usize, + height: g.map.size.y as usize, }); for (player, (_, _, name)) in &g.heads { events.push(Packet::Player { |