Shop-Topbar: Level/XP/Odds links, Lock rechts; Shine nur bei eigenem Champion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -55,8 +55,9 @@ def test_invalid_action_is_400(client):
|
|||||||
def test_state_shape(client):
|
def test_state_shape(client):
|
||||||
state = client.post("/api/game", params={"seed": 5}).json()
|
state = client.post("/api/game", params={"seed": 5}).json()
|
||||||
for key in ("round", "player", "board", "bench", "shop", "traits",
|
for key in ("round", "player", "board", "bench", "shop", "traits",
|
||||||
"opponents", "items", "over", "log"):
|
"opponents", "items", "over", "log", "shop_odds", "shop_locked"):
|
||||||
assert key in state
|
assert key in state
|
||||||
|
assert sum(state["shop_odds"]) == 100
|
||||||
assert state["player"]["score"] == 0.0
|
assert state["player"]["score"] == 0.0
|
||||||
assert all(o["board"] is not None for o in state["opponents"])
|
assert all(o["board"] is not None for o in state["opponents"])
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,7 @@ def serialize(game: Game) -> dict:
|
|||||||
for api in game.shop
|
for api in game.shop
|
||||||
],
|
],
|
||||||
"shop_locked": game.shop_locked,
|
"shop_locked": game.shop_locked,
|
||||||
|
"shop_odds": game.cfg["shop"]["odds"][game.level - 1],
|
||||||
"traits": traits_panel,
|
"traits": traits_panel,
|
||||||
"augment_offer": [
|
"augment_offer": [
|
||||||
{"api_name": a, "name": static["augments"].get(a, {}).get("name", a),
|
{"api_name": a, "name": static["augments"].get(a, {}).get("name", a),
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ const kindLabel = { pvp: 'PvP', pve: 'PvE', carousel: 'Carousel' }
|
|||||||
<header>
|
<header>
|
||||||
<span class="round">Runde {{ s.round.label }} · {{ kindLabel[s.round.kind] }}</span>
|
<span class="round">Runde {{ s.round.label }} · {{ kindLabel[s.round.kind] }}</span>
|
||||||
<span class="stat">❤️ {{ s.player.hp }}</span>
|
<span class="stat">❤️ {{ s.player.hp }}</span>
|
||||||
<span class="stat">
|
|
||||||
Lvl {{ s.player.level }}
|
|
||||||
<template v-if="s.player.xp_needed !== null">({{ s.player.xp }}/{{ s.player.xp_needed }})</template>
|
|
||||||
</span>
|
|
||||||
<span class="score">Boardstärke {{ s.player.score }}</span>
|
<span class="score">Boardstärke {{ s.player.score }}</span>
|
||||||
<button class="fight" :disabled="s.over" @click="store.next()">
|
<button class="fight" :disabled="s.over" @click="store.next()">
|
||||||
{{ s.round.kind === 'pvp' ? 'Kampf' : 'Weiter' }} ▶
|
{{ s.round.kind === 'pvp' ? 'Kampf' : 'Weiter' }} ▶
|
||||||
|
|||||||
@@ -1,19 +1,48 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { store } from '../store.js'
|
import { store } from '../store.js'
|
||||||
const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
||||||
|
|
||||||
|
function owned(api) {
|
||||||
|
return [...store.state.board, ...store.state.bench].some((u) => u.api_name === api)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="shopwrap">
|
<div class="shopwrap">
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<span class="pill gold">🪙 {{ store.state.player.gold }}</span>
|
<div class="tleft">
|
||||||
<span
|
<span class="lvl">Lvl. {{ store.state.player.level }}</span>
|
||||||
v-if="store.state.player.streak !== 0"
|
<span v-if="store.state.player.xp_needed !== null" class="xp">
|
||||||
class="pill streak"
|
{{ store.state.player.xp }}/{{ store.state.player.xp_needed }}
|
||||||
:class="store.state.player.streak > 0 ? 'hot' : 'cold'"
|
</span>
|
||||||
>
|
<span class="odds">
|
||||||
<span class="flame">🔥</span> {{ Math.abs(store.state.player.streak) }}
|
<span
|
||||||
</span>
|
v-for="(p, i) in store.state.shop_odds"
|
||||||
|
:key="i"
|
||||||
|
:style="{ color: costColors[i] }"
|
||||||
|
>● {{ p }}%</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="tcenter">
|
||||||
|
<span class="pill gold">🪙 {{ store.state.player.gold }}</span>
|
||||||
|
<span
|
||||||
|
v-if="store.state.player.streak !== 0"
|
||||||
|
class="pill streak"
|
||||||
|
:class="store.state.player.streak > 0 ? 'hot' : 'cold'"
|
||||||
|
>
|
||||||
|
<span class="flame">🔥</span> {{ Math.abs(store.state.player.streak) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="tright">
|
||||||
|
<button
|
||||||
|
class="lock"
|
||||||
|
:class="{ locked: store.state.shop_locked }"
|
||||||
|
:title="store.state.shop_locked ? 'Shop entsperren' : 'Shop sperren'"
|
||||||
|
@click="store.act({ action: 'lock' })"
|
||||||
|
>
|
||||||
|
{{ store.state.shop_locked ? '🔒' : '🔓' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="shoprow">
|
<div class="shoprow">
|
||||||
@@ -47,7 +76,7 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
|||||||
>
|
>
|
||||||
<template v-if="s">
|
<template v-if="s">
|
||||||
<div class="art" :style="{ backgroundImage: `url(${s.icon})` }" />
|
<div class="art" :style="{ backgroundImage: `url(${s.icon})` }" />
|
||||||
<div class="shine" />
|
<div v-if="owned(s.api_name)" class="shine" />
|
||||||
<div v-if="s.upgrade" class="starhint">
|
<div v-if="s.upgrade" class="starhint">
|
||||||
<span v-for="n in s.upgrade" :key="n">★</span>
|
<span v-for="n in s.upgrade" :key="n">★</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,15 +92,6 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
class="lock"
|
|
||||||
:class="{ locked: store.state.shop_locked }"
|
|
||||||
:title="store.state.shop_locked ? 'Shop entsperren' : 'Shop sperren'"
|
|
||||||
@click="store.act({ action: 'lock' })"
|
|
||||||
>
|
|
||||||
{{ store.state.shop_locked ? '🔒' : '🔓' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -79,7 +99,13 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.shopwrap { display: flex; flex-direction: column; gap: 6px; }
|
.shopwrap { display: flex; flex-direction: column; gap: 6px; }
|
||||||
|
|
||||||
.topbar { display: flex; justify-content: center; gap: 10px; }
|
.topbar { display: flex; align-items: center; gap: 10px; }
|
||||||
|
.tleft { flex: 1; display: flex; align-items: baseline; gap: 8px; }
|
||||||
|
.tcenter { display: flex; gap: 10px; }
|
||||||
|
.tright { flex: 1; display: flex; justify-content: flex-end; }
|
||||||
|
.lvl { font-weight: 800; font-size: 15px; }
|
||||||
|
.xp { color: #8a92a8; font-size: 12px; }
|
||||||
|
.odds { display: flex; gap: 8px; font-size: 12px; font-weight: 600; margin-left: 8px; }
|
||||||
.pill {
|
.pill {
|
||||||
background: #161b29;
|
background: #161b29;
|
||||||
border: 1px solid #3a4460;
|
border: 1px solid #3a4460;
|
||||||
@@ -175,8 +201,8 @@ const costColors = ['#8a8f9d', '#4caf6e', '#4a90d9', '#b153d8', '#e5a531']
|
|||||||
.ccost { color: #ffd75e; font-weight: 700; }
|
.ccost { color: #ffd75e; font-weight: 700; }
|
||||||
|
|
||||||
.lock {
|
.lock {
|
||||||
width: 44px;
|
font-size: 15px;
|
||||||
font-size: 17px;
|
padding: 3px 10px;
|
||||||
background: #141927;
|
background: #141927;
|
||||||
}
|
}
|
||||||
.lock.locked { border-color: #ffd75e; background: #2a2618; }
|
.lock.locked { border-color: #ffd75e; background: #2a2618; }
|
||||||
|
|||||||
Reference in New Issue
Block a user