show player logs on view match page
This commit is contained in:
parent
b51b7a331d
commit
8feccfeb23
8 changed files with 216 additions and 158 deletions
|
@ -26,7 +26,7 @@
|
|||
"prettier": "^2.4.1",
|
||||
"prettier-plugin-svelte": "^2.4.0",
|
||||
"sass": "^1.49.7",
|
||||
"svelte": "^3.44.0",
|
||||
"svelte": "^3.52.0",
|
||||
"svelte-check": "^2.2.6",
|
||||
"svelte-preprocess": "^4.9.4",
|
||||
"tslib": "^2.3.1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"dayjs": "^1.10.7",
|
||||
"planetwars-rs": "file:../planetwars-rs/pkg",
|
||||
"pw-visualizer": "file:../pw-visualizer",
|
||||
"svelte-select": "^4.4.7"
|
||||
"svelte-select": "^5.0.0-beta.31"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
|
|
@ -1,157 +1,22 @@
|
|||
<script lang="ts">
|
||||
import { parsePlayerLog, PlayerLog } from "$lib/log_parser";
|
||||
import PlayerLog from "./PlayerLog.svelte";
|
||||
|
||||
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>
|
||||
|
||||
<div class="output">
|
||||
<div class="output-pane">
|
||||
<h3 class="output-header">Player log</h3>
|
||||
{#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}
|
||||
<PlayerLog {matchLog} playerId={1} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.output {
|
||||
.output-pane {
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
background-color: rgb(41, 41, 41);
|
||||
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 {
|
||||
color: #eee;
|
||||
padding-bottom: 20px;
|
||||
|
|
152
web/pw-server/src/lib/components/PlayerLog.svelte
Normal file
152
web/pw-server/src/lib/components/PlayerLog.svelte
Normal 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>
|
|
@ -2,24 +2,13 @@
|
|||
import { onDestroy, onMount } from "svelte";
|
||||
import * as visualizer from "pw-visualizer";
|
||||
import init_wasm_module from "planetwars-rs";
|
||||
import { PLAYER_COLORS } from "$lib/constants";
|
||||
|
||||
export let matchLog = null;
|
||||
export let matchData: object; // match object as returned by api
|
||||
|
||||
let initialized = false;
|
||||
|
||||
const PLAYER_COLORS = [
|
||||
"#ff8000",
|
||||
"#0080ff",
|
||||
"#ff6693",
|
||||
"#3fcb55",
|
||||
"#cbc33f",
|
||||
"#cf40e9",
|
||||
"#ff3f0d",
|
||||
"#1beef0",
|
||||
"#0dc5ff",
|
||||
];
|
||||
|
||||
onMount(async () => {
|
||||
await init_wasm_module();
|
||||
|
||||
|
|
11
web/pw-server/src/lib/constants.ts
Normal file
11
web/pw-server/src/lib/constants.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const PLAYER_COLORS = [
|
||||
"#ff8000",
|
||||
"#0080ff",
|
||||
"#ff6693",
|
||||
"#3fcb55",
|
||||
"#cbc33f",
|
||||
"#cf40e9",
|
||||
"#ff3f0d",
|
||||
"#1beef0",
|
||||
"#0dc5ff",
|
||||
];
|
|
@ -12,7 +12,6 @@
|
|||
import { debounce } from "$lib/utils";
|
||||
import SubmitPane from "$lib/components/SubmitPane.svelte";
|
||||
import OutputPane from "$lib/components/OutputPane.svelte";
|
||||
import BotName from "./bots/[bot_name].svelte";
|
||||
|
||||
enum ViewMode {
|
||||
Editor,
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "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 matchData: object;
|
||||
|
@ -30,13 +33,35 @@
|
|||
const apiClient = new ApiClient();
|
||||
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>
|
||||
|
||||
<div class="container">
|
||||
<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>
|
||||
|
||||
<style lang="scss">
|
||||
@use "src/styles/variables";
|
||||
.container {
|
||||
display: flex;
|
||||
// these are needed for making the visualizer fill the screen.
|
||||
|
@ -44,4 +69,21 @@
|
|||
flex-grow: 1;
|
||||
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>
|
||||
|
|
|
@ -12,13 +12,13 @@ const config = {
|
|||
preprocess: [
|
||||
sveltePreprocess(),
|
||||
mdsvex({
|
||||
extensions: ['.md'],
|
||||
extensions: [".md"],
|
||||
layout: {
|
||||
docs: 'src/routes/docs/doc.svelte',
|
||||
}
|
||||
docs: "src/routes/docs/doc.svelte",
|
||||
},
|
||||
}),
|
||||
],
|
||||
extensions: ['.svelte', '.md'],
|
||||
extensions: [".svelte", ".md"],
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
|
||||
|
|
Loading…
Reference in a new issue