show player logs on view match page

This commit is contained in:
Ilion Beyst 2022-10-15 20:23:20 +02:00
parent b51b7a331d
commit 8feccfeb23
8 changed files with 216 additions and 158 deletions

View file

@ -26,7 +26,7 @@
"prettier": "^2.4.1", "prettier": "^2.4.1",
"prettier-plugin-svelte": "^2.4.0", "prettier-plugin-svelte": "^2.4.0",
"sass": "^1.49.7", "sass": "^1.49.7",
"svelte": "^3.44.0", "svelte": "^3.52.0",
"svelte-check": "^2.2.6", "svelte-check": "^2.2.6",
"svelte-preprocess": "^4.9.4", "svelte-preprocess": "^4.9.4",
"tslib": "^2.3.1", "tslib": "^2.3.1",
@ -41,7 +41,7 @@
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"planetwars-rs": "file:../planetwars-rs/pkg", "planetwars-rs": "file:../planetwars-rs/pkg",
"pw-visualizer": "file:../pw-visualizer", "pw-visualizer": "file:../pw-visualizer",
"svelte-select": "^4.4.7" "svelte-select": "^5.0.0-beta.31"
}, },
"type": "module" "type": "module"
} }

View file

@ -1,157 +1,22 @@
<script lang="ts"> <script lang="ts">
import { parsePlayerLog, PlayerLog } from "$lib/log_parser"; import PlayerLog from "./PlayerLog.svelte";
export let matchLog: string; export let matchLog: string;
let playerLog: PlayerLog;
let showRawStderr = false;
const PLURAL_MAP = {
dispatch: "dispatches",
ship: "ships",
};
function pluralize(num: number, word: string): string {
if (num == 1) {
return `1 ${word}`;
} else {
return `${num} ${PLURAL_MAP[word]}`;
}
}
$: if (matchLog) {
playerLog = parsePlayerLog(1, matchLog);
} else {
playerLog = [];
}
</script> </script>
<div class="output"> <div class="output-pane">
<h3 class="output-header">Player log</h3> <h3 class="output-header">Player log</h3>
{#if showRawStderr} <PlayerLog {matchLog} playerId={1} />
<div class="output-text stderr-text">
{playerLog.flatMap((turn) => turn.stderr).join("\n")}
</div>
{:else}
<div class="output-text">
{#each playerLog as logTurn, i}
<div class="turn">
<div class="turn-header">
<span class="turn-header-text">Turn {i}</span>
{#if logTurn.action?.type === "dispatches"}
{pluralize(logTurn.action.dispatches.length, "dispatch")}
{:else if logTurn.action?.type === "timeout"}
<span class="turn-error">timeout</span>
{:else if logTurn.action?.type === "bad_command"}
<span class="turn-error">invalid command</span>
{/if}
</div>
{#if logTurn.action?.type === "dispatches"}
<div class="dispatches-container">
{#each logTurn.action.dispatches as dispatch}
<div class="dispatch">
<div class="dispatch-text">
{pluralize(dispatch.ship_count, "ship")} from {dispatch.origin} to {dispatch.destination}
</div>
{#if dispatch.error}
<span class="dispatch-error">{dispatch.error}</span>
{/if}
</div>
{/each}
</div>
{:else if logTurn.action?.type === "bad_command"}
<div class="bad-command-container">
<div class="bad-command-text">{logTurn.action.command}</div>
<div class="bad-command-error">Parse error: {logTurn.action.error}</div>
</div>
{/if}
{#if logTurn.stderr.length > 0}
<div class="stderr-header">stderr</div>
<div class="stderr-text-box">
{#each logTurn.stderr as stdErrMsg}
<div class="stderr-text">{stdErrMsg}</div>
{/each}
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div> </div>
<style lang="scss"> <style lang="scss">
.output { .output-pane {
width: 100%; width: 100%;
overflow-y: scroll; overflow-y: scroll;
background-color: rgb(41, 41, 41); background-color: rgb(41, 41, 41);
padding: 15px; padding: 15px;
} }
.turn {
margin: 16px 4px;
}
.output-text {
color: #ccc;
}
.turn-header {
display: flex;
justify-content: space-between;
}
.turn-header-text {
color: #eee;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
}
.turn-error {
color: red;
}
.dispatch {
display: flex;
justify-content: space-between;
}
.dispatch-error {
color: red;
}
.bad-command-container {
border-left: 1px solid red;
margin-left: 4px;
padding-left: 8px;
}
.bad-command-text {
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
padding-bottom: 4px;
}
.bad-command-error {
color: whitesmoke;
}
.stderr-text {
// font-family: monospace;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
white-space: pre-wrap;
}
.stderr-header {
color: #eee;
padding-top: 4px;
}
.stderr-text-box {
border-left: 1px solid #ccc;
margin-left: 4px;
padding-left: 8px;
}
.output-header { .output-header {
color: #eee; color: #eee;
padding-bottom: 20px; padding-bottom: 20px;

View file

@ -0,0 +1,152 @@
<script lang="ts">
import { parsePlayerLog, PlayerLog } from "$lib/log_parser";
export let matchLog: string;
export let playerId: number;
let playerLog: PlayerLog;
let showRawStderr = false;
const PLURAL_MAP = {
dispatch: "dispatches",
ship: "ships",
};
function pluralize(num: number, word: string): string {
if (num == 1) {
return `1 ${word}`;
} else {
return `${num} ${PLURAL_MAP[word]}`;
}
}
$: if (matchLog) {
playerLog = parsePlayerLog(playerId, matchLog);
} else {
playerLog = [];
}
</script>
<div class="output">
{#if showRawStderr}
<div class="output-text stderr-text">
{playerLog.flatMap((turn) => turn.stderr).join("\n")}
</div>
{:else}
<div class="output-text">
{#each playerLog as logTurn, i}
<div class="turn">
<div class="turn-header">
<span class="turn-header-text">Turn {i}</span>
{#if logTurn.action?.type === "dispatches"}
{pluralize(logTurn.action.dispatches.length, "dispatch")}
{:else if logTurn.action?.type === "timeout"}
<span class="turn-error">timeout</span>
{:else if logTurn.action?.type === "bad_command"}
<span class="turn-error">invalid command</span>
{/if}
</div>
{#if logTurn.action?.type === "dispatches"}
<div class="dispatches-container">
{#each logTurn.action.dispatches as dispatch}
<div class="dispatch">
<div class="dispatch-text">
{pluralize(dispatch.ship_count, "ship")} from {dispatch.origin} to {dispatch.destination}
</div>
{#if dispatch.error}
<span class="dispatch-error">{dispatch.error}</span>
{/if}
</div>
{/each}
</div>
{:else if logTurn.action?.type === "bad_command"}
<div class="bad-command-container">
<div class="bad-command-text">{logTurn.action.command}</div>
<div class="bad-command-error">Parse error: {logTurn.action.error}</div>
</div>
{/if}
{#if logTurn.stderr.length > 0}
<div class="stderr-header">stderr</div>
<div class="stderr-text-box">
{#each logTurn.stderr as stdErrMsg}
<div class="stderr-text">{stdErrMsg}</div>
{/each}
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>
<style lang="scss">
.output {
background-color: rgb(41, 41, 41);
}
.turn {
margin: 16px 4px;
}
.output-text {
color: #ccc;
}
.turn-header {
display: flex;
justify-content: space-between;
}
.turn-header-text {
color: #eee;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
}
.turn-error {
color: red;
}
.dispatch {
display: flex;
justify-content: space-between;
}
.dispatch-error {
color: red;
}
.bad-command-container {
border-left: 1px solid red;
margin-left: 4px;
padding-left: 8px;
}
.bad-command-text {
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
padding-bottom: 4px;
}
.bad-command-error {
color: whitesmoke;
}
.stderr-text {
// font-family: monospace;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
white-space: pre-wrap;
}
.stderr-header {
color: #eee;
padding-top: 4px;
}
.stderr-text-box {
border-left: 1px solid #ccc;
margin-left: 4px;
padding-left: 8px;
}
</style>

View file

@ -2,24 +2,13 @@
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import * as visualizer from "pw-visualizer"; import * as visualizer from "pw-visualizer";
import init_wasm_module from "planetwars-rs"; import init_wasm_module from "planetwars-rs";
import { PLAYER_COLORS } from "$lib/constants";
export let matchLog = null; export let matchLog = null;
export let matchData: object; // match object as returned by api export let matchData: object; // match object as returned by api
let initialized = false; let initialized = false;
const PLAYER_COLORS = [
"#ff8000",
"#0080ff",
"#ff6693",
"#3fcb55",
"#cbc33f",
"#cf40e9",
"#ff3f0d",
"#1beef0",
"#0dc5ff",
];
onMount(async () => { onMount(async () => {
await init_wasm_module(); await init_wasm_module();

View file

@ -0,0 +1,11 @@
export const PLAYER_COLORS = [
"#ff8000",
"#0080ff",
"#ff6693",
"#3fcb55",
"#cbc33f",
"#cf40e9",
"#ff3f0d",
"#1beef0",
"#0dc5ff",
];

View file

@ -12,7 +12,6 @@
import { debounce } from "$lib/utils"; import { debounce } from "$lib/utils";
import SubmitPane from "$lib/components/SubmitPane.svelte"; import SubmitPane from "$lib/components/SubmitPane.svelte";
import OutputPane from "$lib/components/OutputPane.svelte"; import OutputPane from "$lib/components/OutputPane.svelte";
import BotName from "./bots/[bot_name].svelte";
enum ViewMode { enum ViewMode {
Editor, Editor,

View file

@ -22,6 +22,9 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import Visualizer from "$lib/components/Visualizer.svelte"; import Visualizer from "$lib/components/Visualizer.svelte";
import PlayerLog from "$lib/components/PlayerLog.svelte";
import Select from "svelte-select";
import { PLAYER_COLORS } from "$lib/constants";
export let matchLog: string | undefined; export let matchLog: string | undefined;
export let matchData: object; export let matchData: object;
@ -30,13 +33,35 @@
const apiClient = new ApiClient(); const apiClient = new ApiClient();
matchLog = await apiClient.getText(`/api/matches/${matchData["id"]}/log`); matchLog = await apiClient.getText(`/api/matches/${matchData["id"]}/log`);
}); });
let selectedPlayer;
$: matchPlayerSelectItems = matchData["players"].map((player: any, index: number) => ({
color: PLAYER_COLORS[index],
value: index,
playerId: index + 1, // stoopid player number + 1
label: player["bot_name"],
}));
</script> </script>
<div class="container"> <div class="container">
<Visualizer {matchLog} {matchData} /> <Visualizer {matchLog} {matchData} />
<div class="output-pane">
<div class="player-select">
<Select items={matchPlayerSelectItems} clearable={false} bind:value={selectedPlayer}>
<div slot="item" let:item>
<span style:color={item.color}>{item.label}</span>
</div>
</Select>
</div>
<div class="player-log">
<PlayerLog {matchLog} playerId={selectedPlayer?.playerId} />
</div>
</div>
</div> </div>
<style lang="scss"> <style lang="scss">
@use "src/styles/variables";
.container { .container {
display: flex; display: flex;
// these are needed for making the visualizer fill the screen. // these are needed for making the visualizer fill the screen.
@ -44,4 +69,21 @@
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
} }
.player-select {
padding: 20px;
}
.player-log {
padding: 15px;
overflow-y: scroll;
}
.output-pane {
width: 600px;
// overflow: hidden;
display: flex;
flex-direction: column;
background-color: variables.$bg-color;
}
</style> </style>

View file

@ -12,13 +12,13 @@ const config = {
preprocess: [ preprocess: [
sveltePreprocess(), sveltePreprocess(),
mdsvex({ mdsvex({
extensions: ['.md'], extensions: [".md"],
layout: { layout: {
docs: 'src/routes/docs/doc.svelte', docs: "src/routes/docs/doc.svelte",
} },
}), }),
], ],
extensions: ['.svelte', '.md'], extensions: [".svelte", ".md"],
kit: { kit: {
adapter: adapter(), adapter: adapter(),