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": "^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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
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 { 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();
|
||||||
|
|
||||||
|
|
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 { 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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue