add match meta header to logs

This commit is contained in:
Ilion Beyst 2021-12-25 21:49:16 +01:00
parent e681eb91cd
commit b1e9490f55
6 changed files with 125 additions and 14 deletions

View file

@ -17,7 +17,7 @@ serde_json = "1.0"
toml = "0.5" toml = "0.5"
planetwars-rules = { path = "../planetwars-rules" } planetwars-rules = { path = "../planetwars-rules" }
clap = { version = "3.0.0-rc.8", features = ["derive"] } clap = { version = "3.0.0-rc.8", features = ["derive"] }
chrono = "0.4" chrono = { version = "0.4", features = ["serde"] }
rust-embed = "6.3.0" rust-embed = "6.3.0"
axum = "0.4" axum = "0.4"

View file

@ -98,6 +98,7 @@ async fn run_match(command: RunMatchCommand) -> io::Result<()> {
.collect(); .collect();
let match_config = MatchConfig { let match_config = MatchConfig {
map_name: command.map,
map_path, map_path,
log_path, log_path,
players, players,

View file

@ -3,23 +3,38 @@ mod match_context;
mod pw_match; mod pw_match;
use std::{ use std::{
io::Write,
path::PathBuf, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use match_context::MatchCtx; use match_context::MatchCtx;
use planetwars_rules::PwConfig; use planetwars_rules::PwConfig;
use serde::{Deserialize, Serialize};
use crate::BotConfig; use crate::BotConfig;
use self::match_context::{EventBus, PlayerHandle}; use self::match_context::{EventBus, PlayerHandle};
pub struct MatchConfig { pub struct MatchConfig {
pub map_name: String,
pub map_path: PathBuf, pub map_path: PathBuf,
pub log_path: PathBuf, pub log_path: PathBuf,
pub players: Vec<MatchBot>, 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 struct MatchBot {
pub name: String, pub name: String,
pub bot_config: BotConfig, pub bot_config: BotConfig,
@ -48,7 +63,27 @@ pub async fn run_match(config: MatchConfig) {
(player_id, Box::new(handle) as Box<dyn PlayerHandle>) (player_id, Box::new(handle) as Box<dyn PlayerHandle>)
}) })
.collect(); .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_ctx = MatchCtx::new(event_bus, players, log_file);
let match_state = pw_match::PwMatch::create(match_ctx, pw_config); let match_state = pw_match::PwMatch::create(match_ctx, pw_config);

View file

@ -11,11 +11,15 @@ use mime_guess;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fs,
io::{self, BufRead},
net::SocketAddr, net::SocketAddr,
path::{self, PathBuf}, path::{self, PathBuf},
sync::Arc, sync::Arc,
}; };
use crate::match_runner::MatchMeta;
struct State { struct State {
project_root: PathBuf, project_root: PathBuf,
} }
@ -47,11 +51,13 @@ pub async fn run(project_root: PathBuf) {
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct Match { struct MatchData {
name: String, 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 let matches = state
.project_root .project_root
.join("matches") .join("matches")
@ -59,29 +65,48 @@ async fn list_matches(Extension(state): Extension<Arc<State>>) -> Json<Vec<Match
.unwrap() .unwrap()
.filter_map(|entry| { .filter_map(|entry| {
let entry = entry.unwrap(); let entry = entry.unwrap();
extract_match_name(entry).map(|name| Match { name }) get_match_data(&entry).ok()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Json(matches) Json(matches)
} }
// extracts 'filename' if the entry matches'$filename.log'. // 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 file_name = entry.file_name();
let path = path::Path::new(&file_name); let path = path::Path::new(&file_name);
if path.extension() == Some("log".as_ref()) {
path.file_stem() let name = get_match_name(&path)
.and_then(|name| name.to_str()) .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "invalid match name"))?;
.map(|name| name.to_string())
} else { let meta = read_match_meta(&entry.path())?;
None
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 { 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); let mut match_path = state.project_root.join("matches").join(id);
match_path.set_extension("log"); 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 { async fn index_handler() -> impl IntoResponse {

View file

@ -26,6 +26,7 @@
"buffer": "^6.0.3", "buffer": "^6.0.3",
"extract-svg-path": "^2.1.0", "extract-svg-path": "^2.1.0",
"load-svg": "^1.0.0", "load-svg": "^1.0.0",
"moment": "^2.29.1",
"svg-mesh-3d": "^1.1.0", "svg-mesh-3d": "^1.1.0",
"ts-heap": "^1.1.3" "ts-heap": "^1.1.3"
} }

View file

@ -1,6 +1,19 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import Visualizer from "./Visualizer.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 matches = [];
let selectedMatch = null; let selectedMatch = null;
@ -21,6 +34,19 @@
selectedMatchLog = log; 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> </script>
<div class="container"> <div class="container">
@ -33,7 +59,15 @@
class:selected={selectedMatch === match.name} class:selected={selectedMatch === match.name}
class="match-card" 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> </li>
{/each} {/each}
</ul> </ul>
@ -86,4 +120,19 @@
.match-card:hover { .match-card:hover {
background-color: #333; 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> </style>