add match meta header to logs
This commit is contained in:
parent
e681eb91cd
commit
b1e9490f55
6 changed files with 125 additions and 14 deletions
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
||||
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())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue