diff options
author | Ilion Beyst <ilion.beyst@gmail.com> | 2022-10-30 14:37:38 +0100 |
---|---|---|
committer | Ilion Beyst <ilion.beyst@gmail.com> | 2022-10-30 16:23:35 +0100 |
commit | 00d31df58d0ea68b11600d98ebf53150a2a0cb88 (patch) | |
tree | 523abfde1b55f4a808095a440b88376a63e8d6f3 /web/pw-server/src | |
parent | 67c8a2780c92d247b7343b2107f3d69fc9763797 (diff) | |
download | planetwars.dev-00d31df58d0ea68b11600d98ebf53150a2a0cb88.tar.xz planetwars.dev-00d31df58d0ea68b11600d98ebf53150a2a0cb88.zip |
design new BotMatch view
Diffstat (limited to 'web/pw-server/src')
-rw-r--r-- | web/pw-server/src/lib/api_types.ts | 20 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/PlayerLog.svelte | 3 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/SubmitPane.svelte | 8 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/matches/BotMatchCard.svelte | 104 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/matches/BotMatchList.svelte | 19 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/navbar/UserControls.svelte | 1 | ||||
-rw-r--r-- | web/pw-server/src/lib/matches.ts | 55 | ||||
-rw-r--r-- | web/pw-server/src/routes/bots/[bot_name]/index.svelte | 8 | ||||
-rw-r--r-- | web/pw-server/src/routes/bots/[bot_name]/matches.svelte | 64 | ||||
-rw-r--r-- | web/pw-server/src/routes/docs/rules.md | 2 |
10 files changed, 269 insertions, 15 deletions
diff --git a/web/pw-server/src/lib/api_types.ts b/web/pw-server/src/lib/api_types.ts new file mode 100644 index 0000000..03190cb --- /dev/null +++ b/web/pw-server/src/lib/api_types.ts @@ -0,0 +1,20 @@ +export type Match = { + id: number; + timestamp: string; + state: string; + players: MatchPlayer[]; + winner: number; + map: Map; +}; + +export type MatchPlayer = { + bot_id: number; + bot_version_id: number; + bot_name: string; + owner_id?: number; + had_errors?: boolean; +}; + +export type Map = { + name: string; +}; diff --git a/web/pw-server/src/lib/components/PlayerLog.svelte b/web/pw-server/src/lib/components/PlayerLog.svelte index 52cae8e..60097d8 100644 --- a/web/pw-server/src/lib/components/PlayerLog.svelte +++ b/web/pw-server/src/lib/components/PlayerLog.svelte @@ -3,6 +3,7 @@ export let matchLog: string; export let playerId: number; + export let showStdErr: boolean = true; let playerLog: PlayerLog; @@ -66,7 +67,7 @@ <div class="bad-command-error">Parse error: {logTurn.action.error}</div> </div> {/if} - {#if logTurn.stderr.length > 0} + {#if showStdErr && logTurn.stderr.length > 0} <div class="stderr-header">stderr</div> <div class="stderr-text-box"> {#each logTurn.stderr as stdErrMsg} diff --git a/web/pw-server/src/lib/components/SubmitPane.svelte b/web/pw-server/src/lib/components/SubmitPane.svelte index e46166c..b1aaf9e 100644 --- a/web/pw-server/src/lib/components/SubmitPane.svelte +++ b/web/pw-server/src/lib/components/SubmitPane.svelte @@ -122,13 +122,7 @@ </div> <span>Map</span> <div class="map-select"> - <Select - itemId="name" - label="name" - items={maps} - bind:value={$selectedMap} - clearable={false} - /> + <Select itemId="name" label="name" items={maps} bind:value={$selectedMap} clearable={false} /> </div> <button class="submit-button play-button" on:click={submitBot}>Play</button> </div> diff --git a/web/pw-server/src/lib/components/matches/BotMatchCard.svelte b/web/pw-server/src/lib/components/matches/BotMatchCard.svelte new file mode 100644 index 0000000..2f50173 --- /dev/null +++ b/web/pw-server/src/lib/components/matches/BotMatchCard.svelte @@ -0,0 +1,104 @@ +<script lang="ts"> + import type { BotMatch } from "$lib/matches"; + import dayjs from "dayjs"; + + export let botMatch: BotMatch; +</script> + +<a class="bot-match-card-wrapper" href={`/matches/${botMatch.id}`}> + <div class="bot-match-card"> + <div class="bot-match-outcome"> + {botMatch.outcome} + </div> + <div class="bot-match-card-main"> + <div class="opponent-name"> + <a class="bot-link" href={`/bots/${botMatch.opponent.bot_name}`} + >{botMatch.opponent.bot_name}</a + > + </div> + <div class="map-name"> + {botMatch.map.name} + </div> + </div> + <div class="bot-card-right"> + <div class="match-timestamp"> + {dayjs(botMatch.timestamp).format("YYYY-MM-DD HH:mm")} + </div> + <div class="match-errors"> + {#if botMatch.hadErrors} + ! Had errors + {/if} + </div> + </div> + </div> +</a> + +<style lang="scss"> + @use "src/styles/variables"; + + .bot-match-card { + margin: 4px; + padding: 12px; + display: flex; + border: 1px solid variables.$light-grey; + } + + .bot-match-outcome { + font-size: 24pt; + font-weight: 600; + font-family: "Open Sans", sans-serif; + text-transform: uppercase; + color: #333; + width: 75px; + display: flex; + justify-content: center; + margin-top: -4px; + } + + .bot-match-card-main { + flex-grow: 1; + padding: 0 25px; + } + + .bot-card-right { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-end; + } + + .opponent-name { + font-size: 18pt; + color: variables.$blue-primary; + padding-bottom: 4px; + } + + .match-errors { + color: red; + font-weight: 600; + text-align: right; + } + + .match-timestamp { + text-align: right; + } + + .bot-link { + color: variables.$blue-primary; + text-decoration: none; + } + + .bot-link:hover { + text-decoration: underline; + } + + .bot-match-card:hover { + background-color: rgb(246, 248, 250); + } + + .bot-match-card-wrapper { + text-decoration: none; + color: black; + display: block; + } +</style> diff --git a/web/pw-server/src/lib/components/matches/BotMatchList.svelte b/web/pw-server/src/lib/components/matches/BotMatchList.svelte new file mode 100644 index 0000000..959cd07 --- /dev/null +++ b/web/pw-server/src/lib/components/matches/BotMatchList.svelte @@ -0,0 +1,19 @@ +<script lang="ts"> + import type { BotMatch } from "$lib/matches"; + import BotMatchCard from "./BotMatchCard.svelte"; + + export let botMatches: BotMatch[]; + + function match_url(match: object) { + return `/matches/${match["id"]}`; + } +</script> + +<div> + {#each botMatches as botMatch} + <BotMatchCard {botMatch} /> + {/each} +</div> + +<style lang="scss"> +</style> diff --git a/web/pw-server/src/lib/components/navbar/UserControls.svelte b/web/pw-server/src/lib/components/navbar/UserControls.svelte index 334b3b4..fa920ad 100644 --- a/web/pw-server/src/lib/components/navbar/UserControls.svelte +++ b/web/pw-server/src/lib/components/navbar/UserControls.svelte @@ -39,6 +39,7 @@ <a class="current-user-name" href="/users/{$currentUser['username']}"> {$currentUser["username"]} </a> + <!-- svelte-ignore a11y-click-events-have-key-events --> <div class="sign-out" on:click={signOut}>Sign out</div> {:else} <a class="account-href" href="/login">Sign in</a> diff --git a/web/pw-server/src/lib/matches.ts b/web/pw-server/src/lib/matches.ts new file mode 100644 index 0000000..d889a0d --- /dev/null +++ b/web/pw-server/src/lib/matches.ts @@ -0,0 +1,55 @@ +import type { Match as ApiMatch, MatchPlayer as ApiMatchPlayer, Map } from "./api_types"; + +// match from the perspective of a bot +export type BotMatch = { + id: number; + opponent: BotMatchOpponent; + outcome: BotMatchOutcome; + timestamp: string; + map: Map; + hadErrors?: boolean; +}; + +export type BotMatchOutcome = "win" | "loss" | "tie"; + +export type BotMatchOpponent = { + bot_id: number; + bot_name: string; + owner_id?: number; +}; + +export function apiMatchtoBotMatch(bot_name: string, apiMatch: ApiMatch): BotMatch { + let player: ApiMatchPlayer; + let playerIndex: number; + let opponent: ApiMatchPlayer; + apiMatch.players.forEach((matchPlayer, index) => { + if (matchPlayer.bot_name === bot_name) { + player = matchPlayer; + playerIndex = index; + } else { + opponent = matchPlayer; + } + }); + + if (player === undefined || opponent === undefined || playerIndex === undefined) { + throw "could not assign player and opponent"; + } + + let outcome: BotMatchOutcome; + if (apiMatch.winner === playerIndex) { + outcome = "win"; + } else if (apiMatch.winner) { + outcome = "loss"; + } else { + outcome = "tie"; + } + + return { + id: apiMatch.id, + opponent, + outcome, + timestamp: apiMatch.timestamp, + map: apiMatch.map, + hadErrors: player.had_errors, + }; +} diff --git a/web/pw-server/src/routes/bots/[bot_name]/index.svelte b/web/pw-server/src/routes/bots/[bot_name]/index.svelte index b046754..a5934b6 100644 --- a/web/pw-server/src/routes/bots/[bot_name]/index.svelte +++ b/web/pw-server/src/routes/bots/[bot_name]/index.svelte @@ -100,9 +100,7 @@ <LinkButton href={`/matches?bot=${bot["name"]}&had_errors=true`}>View all</LinkButton> </div> {:else} - <div class="table-placeholder"> - Nothing here yet - </div> + <div class="table-placeholder">Nothing here yet</div> {/if} </div> {/if} @@ -115,9 +113,7 @@ <LinkButton href={`/matches?bot=${bot["name"]}`}>All matches</LinkButton> </div> {:else} - <div class="table-placeholder"> - No matches played yet - </div> + <div class="table-placeholder">No matches played yet</div> {/if} </div> </div> diff --git a/web/pw-server/src/routes/bots/[bot_name]/matches.svelte b/web/pw-server/src/routes/bots/[bot_name]/matches.svelte new file mode 100644 index 0000000..a3c97cb --- /dev/null +++ b/web/pw-server/src/routes/bots/[bot_name]/matches.svelte @@ -0,0 +1,64 @@ +<script lang="ts" context="module"> + import { ApiClient } from "$lib/api_client"; + import type { Match } from "$lib/api_types"; + + const PAGE_SIZE = "50"; + + export async function load({ params, fetch }) { + try { + const apiClient = new ApiClient(fetch); + const botName = params["bot_name"]; + + let { matches, has_next } = await apiClient.get("/api/matches", { bot: botName }); + + // TODO: should this be done client-side? + // if (query["after"]) { + // matches = matches.reverse(); + // } + + return { + props: { + matches, + botName, + hasNext: has_next, + }, + }; + } catch (error) { + return { + status: error.status, + error: new Error("failed to load matches"), + }; + } + } +</script> + +<script lang="ts"> + import LinkButton from "$lib/components/LinkButton.svelte"; + import BotMatchList from "$lib/components/matches/BotMatchList.svelte"; + import { apiMatchtoBotMatch } from "$lib/matches"; + + export let matches: Match[]; + export let botName: string | null; + // whether a next page exists in the current iteration direction (before/after) + // export let hasNext: boolean; + + $: botMatches = matches.map((match) => apiMatchtoBotMatch(botName, match)); +</script> + +<div class="container"> + <BotMatchList {botMatches} /> +</div> + +<style lang="scss"> + .container { + max-width: 800px; + margin: 0 auto; + width: 100%; + } + + .page-controls { + display: flex; + justify-content: center; + margin: 24px 0; + } +</style> diff --git a/web/pw-server/src/routes/docs/rules.md b/web/pw-server/src/routes/docs/rules.md index 116e62c..5a6ee34 100644 --- a/web/pw-server/src/routes/docs/rules.md +++ b/web/pw-server/src/routes/docs/rules.md @@ -65,9 +65,9 @@ Example command: } ] } +``` You can dispatch as many expeditions as you like. -``` ## Rules |