diff options
author | Ilion Beyst <ilion.beyst@gmail.com> | 2021-12-25 21:49:16 +0100 |
---|---|---|
committer | Ilion Beyst <ilion.beyst@gmail.com> | 2021-12-25 21:49:16 +0100 |
commit | b1e9490f55e4f360c249a107dcc5809a663dec42 (patch) | |
tree | 185894b396f1b844ae8667bd55ea7bc36b1efced | |
parent | e681eb91cd8293cefed983e2a877ab99813ae1c5 (diff) | |
download | planetwars.dev-b1e9490f55e4f360c249a107dcc5809a663dec42.tar.xz planetwars.dev-b1e9490f55e4f360c249a107dcc5809a663dec42.zip |
add match meta header to logs
-rw-r--r-- | planetwars-localdev/Cargo.toml | 2 | ||||
-rw-r--r-- | planetwars-localdev/src/lib.rs | 1 | ||||
-rw-r--r-- | planetwars-localdev/src/match_runner/mod.rs | 37 | ||||
-rw-r--r-- | planetwars-localdev/src/web/mod.rs | 47 | ||||
-rw-r--r-- | web/pw-frontend/package.json | 1 | ||||
-rw-r--r-- | web/pw-frontend/src/lib/MatchBrowser.svelte | 51 |
6 files changed, 125 insertions, 14 deletions
diff --git a/planetwars-localdev/Cargo.toml b/planetwars-localdev/Cargo.toml index 31c403d..8595d14 100644 --- a/planetwars-localdev/Cargo.toml +++ b/planetwars-localdev/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1.0" toml = "0.5" planetwars-rules = { path = "../planetwars-rules" } clap = { version = "3.0.0-rc.8", features = ["derive"] } -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } rust-embed = "6.3.0" axum = "0.4" diff --git a/planetwars-localdev/src/lib.rs b/planetwars-localdev/src/lib.rs index c64fb55..7eea75a 100644 --- a/planetwars-localdev/src/lib.rs +++ b/planetwars-localdev/src/lib.rs @@ -98,6 +98,7 @@ async fn run_match(command: RunMatchCommand) -> io::Result<()> { .collect(); let match_config = MatchConfig { + map_name: command.map, map_path, log_path, players, diff --git a/planetwars-localdev/src/match_runner/mod.rs b/planetwars-localdev/src/match_runner/mod.rs index 2715b96..50b7a3b 100644 --- a/planetwars-localdev/src/match_runner/mod.rs +++ b/planetwars-localdev/src/match_runner/mod.rs @@ -3,23 +3,38 @@ mod match_context; mod pw_match; use std::{ + io::Write, path::PathBuf, sync::{Arc, Mutex}, }; use match_context::MatchCtx; use planetwars_rules::PwConfig; +use serde::{Deserialize, Serialize}; use crate::BotConfig; use self::match_context::{EventBus, PlayerHandle}; pub struct MatchConfig { + pub map_name: String, pub map_path: PathBuf, pub log_path: PathBuf, pub players: Vec<MatchBot>, } +#[derive(Serialize, Deserialize)] +pub struct MatchMeta { + pub map_name: String, + pub timestamp: chrono::DateTime<chrono::Local>, + pub players: Vec<PlayerInfo>, +} + +#[derive(Serialize, Deserialize)] +pub struct PlayerInfo { + pub name: String, +} + pub struct MatchBot { pub name: String, pub bot_config: BotConfig, @@ -48,7 +63,27 @@ pub async fn run_match(config: MatchConfig) { (player_id, Box::new(handle) as Box<dyn PlayerHandle>) }) .collect(); - let log_file = std::fs::File::create(config.log_path).expect("could not create log file"); + let mut log_file = std::fs::File::create(config.log_path).expect("could not create log file"); + + // assemble the math meta struct + let match_meta = MatchMeta { + map_name: config.map_name.clone(), + timestamp: chrono::Local::now(), + players: config + .players + .iter() + .map(|bot| PlayerInfo { + name: bot.name.clone(), + }) + .collect(), + }; + write!( + log_file, + "{}\n", + serde_json::to_string(&match_meta).unwrap() + ) + .unwrap(); + let match_ctx = MatchCtx::new(event_bus, players, log_file); let match_state = pw_match::PwMatch::create(match_ctx, pw_config); diff --git a/planetwars-localdev/src/web/mod.rs b/planetwars-localdev/src/web/mod.rs index a5d0989..cd94f5e 100644 --- a/planetwars-localdev/src/web/mod.rs +++ b/planetwars-localdev/src/web/mod.rs @@ -11,11 +11,15 @@ use mime_guess; use rust_embed::RustEmbed; use serde::{Deserialize, Serialize}; use std::{ + fs, + io::{self, BufRead}, net::SocketAddr, path::{self, PathBuf}, sync::Arc, }; +use crate::match_runner::MatchMeta; + struct State { project_root: PathBuf, } @@ -47,11 +51,13 @@ pub async fn run(project_root: PathBuf) { } #[derive(Serialize, Deserialize)] -struct Match { +struct MatchData { name: String, + #[serde(flatten)] + meta: MatchMeta, } -async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<Match>> { +async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<MatchData>> { let matches = state .project_root .join("matches") @@ -59,29 +65,48 @@ async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<Match .unwrap() .filter_map(|entry| { let entry = entry.unwrap(); - extract_match_name(entry).map(|name| Match { name }) + get_match_data(&entry).ok() }) .collect::<Vec<_>>(); Json(matches) } // extracts 'filename' if the entry matches'$filename.log'. -fn extract_match_name(entry: std::fs::DirEntry) -> Option<String> { +fn get_match_data(entry: &fs::DirEntry) -> io::Result<MatchData> { let file_name = entry.file_name(); let path = path::Path::new(&file_name); - if path.extension() == Some("log".as_ref()) { - path.file_stem() - .and_then(|name| name.to_str()) - .map(|name| name.to_string()) - } else { - None + + let name = get_match_name(&path) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid match name"))?; + + let meta = read_match_meta(&entry.path())?; + + Ok(MatchData { name, meta }) +} + +fn get_match_name(path: &path::Path) -> Option<String> { + if path.extension() != Some("log".as_ref()) { + return None; } + + path.file_stem() + .and_then(|name| name.to_str()) + .map(|name| name.to_string()) +} + +fn read_match_meta(path: &path::Path) -> io::Result<MatchMeta> { + let file = fs::File::open(path)?; + let mut reader = io::BufReader::new(file); + let mut line = String::new(); + reader.read_line(&mut line)?; + let meta: MatchMeta = serde_json::from_str(&line)?; + Ok(meta) } async fn get_match(Extension(state): Extension<Arc<State>>, Path(id): Path<String>) -> String { let mut match_path = state.project_root.join("matches").join(id); match_path.set_extension("log"); - std::fs::read_to_string(match_path).unwrap() + fs::read_to_string(match_path).unwrap() } async fn index_handler() -> impl IntoResponse { diff --git a/web/pw-frontend/package.json b/web/pw-frontend/package.json index 2f31ebb..daf64af 100644 --- a/web/pw-frontend/package.json +++ b/web/pw-frontend/package.json @@ -26,6 +26,7 @@ "buffer": "^6.0.3", "extract-svg-path": "^2.1.0", "load-svg": "^1.0.0", + "moment": "^2.29.1", "svg-mesh-3d": "^1.1.0", "ts-heap": "^1.1.3" } diff --git a/web/pw-frontend/src/lib/MatchBrowser.svelte b/web/pw-frontend/src/lib/MatchBrowser.svelte index 71fadb2..6885446 100644 --- a/web/pw-frontend/src/lib/MatchBrowser.svelte +++ b/web/pw-frontend/src/lib/MatchBrowser.svelte @@ -1,6 +1,19 @@ <script lang="ts"> import { onMount } from "svelte"; import Visualizer from "./Visualizer.svelte"; + import moment from "moment"; + + const PLAYER_COLORS = [ + "#FF8000", + "#0080FF", + "#FF6693", + "#3FCB55", + "#CBC33F", + "#CF40E9", + "#FF3F0D", + "#1BEEF0", + "#0DC5FF", + ]; let matches = []; let selectedMatch = null; @@ -21,6 +34,19 @@ selectedMatchLog = log; }); } + + function showTimestamp(dateStr: string): string { + let t = moment(dateStr); + if (t.day() == moment().day()) { + return moment(dateStr).format("HH:mm"); + } else { + return moment(dateStr).format("DD/MM"); + } + } + + function playerColor(player_num: number): string { + return PLAYER_COLORS[player_num % PLAYER_COLORS.length]; + } </script> <div class="container"> @@ -33,7 +59,15 @@ class:selected={selectedMatch === match.name} class="match-card" > - {match.name} + <span class="match-timestamp"> {showTimestamp(match.timestamp)}</span> + <span class="match-mapname">{match.map_name}</span> + <ul class="match-player-list"> + {#each match.players as player, ix} + <li class="match-player" style="color: {playerColor(ix)}"> + {player.name} + </li> + {/each} + </ul> </li> {/each} </ul> @@ -86,4 +120,19 @@ .match-card:hover { background-color: #333; } + + .match-timestamp { + color: #ccc; + } + + .match-mapname { + padding: 0 0.5em; + } + + .match-player-list { + list-style: none; + overflow: hidden; + text-overflow: ellipsis; + padding-left: 18px; + } </style> |