aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--planetwars-localdev/Cargo.toml2
-rw-r--r--planetwars-localdev/src/lib.rs1
-rw-r--r--planetwars-localdev/src/match_runner/mod.rs37
-rw-r--r--planetwars-localdev/src/web/mod.rs47
-rw-r--r--web/pw-frontend/package.json1
-rw-r--r--web/pw-frontend/src/lib/MatchBrowser.svelte51
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>