diff options
author | metamuffin <metamuffin@disroot.org> | 2024-06-04 20:48:43 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-06-04 20:48:43 +0200 |
commit | 4ebe819106d82459def54561cf8dc71ec22ba6e4 (patch) | |
tree | 991046e078bf9de21cc392343401ef1d46a8962c | |
parent | e49a85505701740b195a03892e1fc5cf8d6382a2 (diff) | |
download | gpn-tron-rust-4ebe819106d82459def54561cf8dc71ec22ba6e4.tar gpn-tron-rust-4ebe819106d82459def54561cf8dc71ec22ba6e4.tar.bz2 gpn-tron-rust-4ebe819106d82459def54561cf8dc71ec22ba6e4.tar.zst |
save creds
-rw-r--r-- | Cargo.lock | 10 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | config.toml | 3 | ||||
-rw-r--r-- | src/config.rs | 3 | ||||
-rw-r--r-- | src/database.rs | 33 | ||||
-rw-r--r-- | src/game/server.rs | 41 | ||||
-rw-r--r-- | src/lib.rs | 10 | ||||
-rw-r--r-- | src/main.rs | 3 | ||||
-rw-r--r-- | src/spectate/index.html | 35 | ||||
-rw-r--r-- | src/spectate/main.ts | 3 | ||||
-rw-r--r-- | src/spectate/server.rs | 21 | ||||
-rw-r--r-- | src/spectate/style.css | 43 |
12 files changed, 182 insertions, 24 deletions
@@ -436,6 +436,7 @@ dependencies = [ "log", "mime", "rand 0.9.0-alpha.1", + "redb", "serde", "serde_json", "tokio", @@ -828,6 +829,15 @@ dependencies = [ ] [[package]] +name = "redb" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7508e692a49b6b2290b56540384ccae9b1fb4d77065640b165835b56ffe3bb" +dependencies = [ + "libc", +] + +[[package]] name = "redox_syscall" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -17,3 +17,4 @@ serde_json = "1.0.117" headers = "0.4.0" mime = "0.3.17" rand = "0.9.0-alpha.1" +redb = "2.1.0" diff --git a/config.toml b/config.toml index 6eef0ca..e9a72af 100644 --- a/config.toml +++ b/config.toml @@ -9,3 +9,6 @@ tickrate_max = 3 [bot] amount = 10 + +[database] +path = "/tmp/trondb" diff --git a/src/config.rs b/src/config.rs index eea7991..b489271 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,11 @@ -use crate::{bot, game, spectate}; +use crate::{bot, database, game, spectate}; use anyhow::{anyhow, Result}; use serde::Deserialize; use std::fs::read_to_string; #[derive(Deserialize)] pub struct Config { + pub database: database::Config, pub game: game::Config, pub spectate: spectate::Config, pub bot: bot::Config, diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..ac67857 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,33 @@ +use anyhow::Result; +use redb::{Database, ReadableTable, TableDefinition}; +use serde::Deserialize; +use std::path::PathBuf; + +#[derive(Deserialize)] +pub struct Config { + path: PathBuf, +} + +pub fn open_db(config: Config) -> Result<Database> { + let db = Database::create(config.path)?; + + Ok(db) +} + +static T_CREDS: TableDefinition<&str, &str> = TableDefinition::new("creds"); + +pub trait DatabaseExt { + fn check_or_insert_creds(&self, username: &str, password: &str) -> anyhow::Result<bool>; +} + +impl DatabaseExt for Database { + fn check_or_insert_creds(&self, username: &str, password: &str) -> anyhow::Result<bool> { + let txn = self.begin_write()?; + let mut table = txn.open_table(T_CREDS)?; + if let Some(pw) = table.get(username)? { + return Ok(pw.value() == password); + } + table.insert(username, password)?; + Ok(true) + } +} diff --git a/src/game/server.rs b/src/game/server.rs index f5de1bf..0e983b9 100644 --- a/src/game/server.rs +++ b/src/game/server.rs @@ -1,5 +1,5 @@ use super::{protocol::Packet, Config, Game}; -use crate::State; +use crate::{database::DatabaseExt, State}; use anyhow::{anyhow, bail, Result}; use log::{debug, error, info}; use std::{ops::ControlFlow, sync::Arc, time::Duration}; @@ -29,7 +29,7 @@ pub async fn game_server(config: Config, state: Arc<State>) -> Result<()> { } async fn game_loop(config: Config, state: Arc<State>) { - let mut speed = config.tickrate; + let mut speed = config.tickrate; loop { sleep(Duration::from_secs_f32(1. / speed)).await; @@ -43,9 +43,18 @@ async fn game_loop(config: Config, state: Arc<State>) { } 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; + speed = config.tickrate; let _ = state.tick.send(true); } } @@ -81,7 +90,7 @@ async fn handle_client_inner( let mut lines = rx.lines(); let mut ticks = state.tick.subscribe(); let mut chat = state.chat.subscribe(); - tx.send_packet(Packet::Motd("This is the GPN-Tron Rust rewrite. The protocol should be compatible with the original: https://github.com/freehuntx/gpn-tron/blob/master/PROTOCOL.md".to_string())).await?; + tx.send_packet(Packet::Motd("This is the GPN-Tron Rust rewrite. It should be compatible with the original protocol: https://github.com/freehuntx/gpn-tron/blob/master/PROTOCOL.md".to_string())).await?; loop { tokio::select! { message = chat.recv() => { @@ -127,6 +136,9 @@ async fn handle_tick( } cstate.alive = true; } + if !cstate.alive { + return Ok(()); + } { let g = state.game.read().await; if new_game { @@ -153,9 +165,11 @@ async fn handle_tick( events.push(Packet::Die(g.dead.clone())); } if g.dead.contains(&pid) { + cstate.alive = false; events.push(Packet::Lose(0, 0)); // TODO implement stats + } else { + events.push(Packet::Tick); } - events.push(Packet::Tick); } for e in events { tx.send_packet(e).await?; @@ -179,10 +193,19 @@ async fn handle_packet( }; debug!("<- {packet:?}"); match packet { - Packet::Join { - username, - password: _, - } => { + Packet::Join { username, password } => { + if username.len() > 64 || password.len() > 64 { + tx.send_packet(Packet::Error( + "password or username too long (> 64)".to_string(), + )) + .await?; + return Ok(()); + } + if !state.db.check_or_insert_creds(&username, &password)? { + tx.send_packet(Packet::Error("incorrect password".to_string())) + .await?; + return Ok(()); + } if cstate.pid.is_some() { tx.send_packet(Packet::Error("already joined".to_string())) .await? @@ -1,17 +1,21 @@ #![feature(async_closure)] #![feature(iterator_try_collect)] -use std::collections::HashMap; - use game::Game; +use redb::Database; +use std::collections::{HashMap, VecDeque}; use tokio::sync::{broadcast, RwLock}; + +pub mod bot; pub mod config; +pub mod database; pub mod game; pub mod spectate; -pub mod bot; pub struct State { pub tick: broadcast::Sender<bool>, // true for new game pub game: RwLock<Game>, pub players: RwLock<HashMap<u32, String>>, + pub win_history: RwLock<VecDeque<String>>, pub chat: broadcast::Sender<(String, String)>, + pub db: Database, } diff --git a/src/main.rs b/src/main.rs index 74d784b..fb0f0d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use gpn_tron2::{ bot::spawn_bots, config::Config, + database::open_db, game::{server::game_server, Game}, spectate::server::spectate_server, State, @@ -13,9 +14,11 @@ async fn main() -> anyhow::Result<()> { env_logger::init_from_env("LOG"); let config = Config::load()?; let state = Arc::new(State { + db: open_db(config.database)?, tick: broadcast::channel(512).0, game: Game::new(vec![]).into(), players: Default::default(), + win_history: Default::default(), chat: broadcast::channel(512).0, }); spawn(spectate_server(config.spectate, state.clone())); diff --git a/src/spectate/index.html b/src/spectate/index.html index 394b780..5b9dc5a 100644 --- a/src/spectate/index.html +++ b/src/spectate/index.html @@ -12,5 +12,40 @@ This is the GPN-Tron spectator application. You need JavaScript to run it. </noscript> + <div id="side"> + <div id="info"> + <h1>GPN-Tron</h1> + <p> + This is the GPN-Tron Rust rewrite. It should be compatible + with the original. Connect via TCP and join the fun! + </p> + <ul> + <li> + Spectator: + <script> + document.write(`HTTP ${window.location.host}`); + </script> + </li> + <li> + Player: + <script> + document.write( + `TCP ${window.location.hostname}:4000` + ); + </script> + </li> + </ul> + <a href="https://codeberg.org/metamuffin/gpn-tron-rust"> + Source code + </a> + </div> + <div id="scoreboard"> + <h2>Scoreboard</h2> + </div> + <div id="chat"> + <h2>Chat</h2> + </div> + </div> + <div id="board"></div> </body> </html> diff --git a/src/spectate/main.ts b/src/spectate/main.ts index ea84924..508ca30 100644 --- a/src/spectate/main.ts +++ b/src/spectate/main.ts @@ -35,7 +35,7 @@ let ctx: CanvasRenderingContext2D document.addEventListener("DOMContentLoaded", () => { canvas = document.createElement("canvas") ctx = canvas.getContext("2d")! - document.body.append(canvas) + document.getElementById("board")?.append(canvas) canvas.width = 1000; canvas.height = 1000; redraw() @@ -122,7 +122,6 @@ function name_color(name: string): string { ws.onerror = console.error ws.onmessage = message => { const p = JSON.parse(message.data) as Packet - console.log(p); if (p == "tick") { tick_anim = 0 const d = [] diff --git a/src/spectate/server.rs b/src/spectate/server.rs index da18a77..11c4d5a 100644 --- a/src/spectate/server.rs +++ b/src/spectate/server.rs @@ -47,6 +47,19 @@ pub async fn spectate_server(config: Config, state: Arc<State>) -> Result<()> { Ok(()) } +#[cfg(debug_assertions)] +async fn index() -> Html<String> { + use tokio::fs::read_to_string; + Html( + read_to_string(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/spectate/index.html" + )) + .await + .unwrap(), + ) +} +#[cfg(not(debug_assertions))] async fn index() -> Html<&'static str> { Html(include_str!("index.html")) } @@ -91,13 +104,7 @@ async fn css() -> (HeaderMap, &'static str) { use headers::HeaderMapExt; let mut hm = HeaderMap::new(); hm.typed_insert(ContentType::from_str("text/css").unwrap()); - ( - hm, - include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/src/spectate/style.css" - )), - ) + (hm, include_str!("style.css")) } async fn broadcaster(sstate: Arc<SpectateState>, state: Arc<State>) { diff --git a/src/spectate/style.css b/src/spectate/style.css index 52711d0..c391020 100644 --- a/src/spectate/style.css +++ b/src/spectate/style.css @@ -1,4 +1,43 @@ +* { + color: white; +} +body { + padding: 0px; + margin: 0px; + background-color: black; + + width: 100dvw; + height: 100dvh; + display: flex; + flex-direction: row; + overflow-y: hidden; +} + canvas { - height: min(100dvh, 70dvw); - float: left; + object-fit: contain; + width: 100%; + height: 100%; +} +#board { + width: 60%; + height: 100%; +} +#side { + width: 30%; + height: 100%; +} +#info { + width: 100%; + height: 30%; + overflow-y: scroll; +} +#scoreboard { + width: 100%; + height: 30%; + overflow-y: scroll; +} +#chat { + width: 100%; + height: 40%; + overflow-y: scroll; } |