Skip to content

Commit 33e7792

Browse files
committed
player make delivery (draft)
1 parent 72387f3 commit 33e7792

9 files changed

Lines changed: 267 additions & 30 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<template>
2+
<div class="mt-4">
3+
<div v-for="floor in floors" :key="floor.number" class="floor mb-3">
4+
<h5>{{t('playerMakeDelivery.floor', {floor: floor.number})}}</h5>
5+
<div class="d-flex flex-wrap gap-3">
6+
<div v-for="window in floor.windows" :key="window.selection" class="window-box border rounded p-2">
7+
<div v-if="floor.windows.length > 1" class="fw-bold small mb-1">{{window.label}}</div>
8+
<div v-if="getDisplayFlowers(window.selection, floor.number).length > 0"
9+
class="d-flex flex-wrap gap-1 align-items-center">
10+
<FlowerIcon v-for="(flower, i) in getDisplayFlowers(window.selection, floor.number)"
11+
:key="'f'+i" :flower="flower"/>
12+
</div>
13+
<div v-if="getDisplayDeliveries(window.selection).length > 0"
14+
class="d-flex flex-wrap gap-1 align-items-center mt-1">
15+
<PlayerColorIcon v-for="(player, i) in getDisplayDeliveries(window.selection)"
16+
:key="'d'+i" :player="player"/>
17+
</div>
18+
<div v-if="canDeliver(window.selection)" class="mt-2">
19+
<template v-if="selectingWindow === window.selection">
20+
<p class="small mb-1" v-html="t('playerMakeDelivery.selectFlowers', {count: floor.number})"/>
21+
<FlowerSelection v-model="selectedFlowers" :marketPrices="marketPrices" :max="floor.number"/>
22+
<button v-if="selectedFlowers.length === floor.number"
23+
class="btn btn-success btn-sm mt-2" @click="confirmDelivery(window.selection, floor.number)">
24+
{{t('playerMakeDelivery.confirm')}}
25+
</button>
26+
</template>
27+
<button v-else class="btn btn-outline-primary btn-sm" @click="deliverHere(window.selection, floor.number)">
28+
{{t('playerMakeDelivery.deliverHere')}}
29+
</button>
30+
</div>
31+
</div>
32+
</div>
33+
</div>
34+
</div>
35+
</template>
36+
37+
<script lang="ts">
38+
import { defineComponent, type PropType, ref } from 'vue'
39+
import { useI18n } from 'vue-i18n'
40+
import WindowStates from '@/services/WindowStates'
41+
import WindowSelection from '@/services/enum/WindowSelection'
42+
import Flower from '@/services/enum/Flower'
43+
import Player from '@/services/enum/Player'
44+
import MarketPrices from '@/services/MarketPrices'
45+
import FlowerIcon from '@/components/structure/FlowerIcon.vue'
46+
import PlayerColorIcon from '@/components/structure/PlayerColorIcon.vue'
47+
import FlowerSelection from '@/components/structure/FlowerSelection.vue'
48+
import getAllEnumValues from '@brdgm/brdgm-commons/src/util/enum/getAllEnumValues'
49+
50+
interface FloorInfo {
51+
number: number
52+
windows: { selection: WindowSelection, label: string }[]
53+
}
54+
55+
export default defineComponent({
56+
name: 'PlayerMakeDelivery',
57+
components: {
58+
FlowerIcon,
59+
PlayerColorIcon,
60+
FlowerSelection
61+
},
62+
emits: ['deliver'],
63+
props: {
64+
windowStates: {
65+
type: Object as PropType<WindowStates>,
66+
required: true
67+
},
68+
marketPrices: {
69+
type: Object as PropType<MarketPrices>,
70+
required: true
71+
}
72+
},
73+
setup() {
74+
const { t } = useI18n()
75+
const selectingWindow = ref<WindowSelection|null>(null)
76+
const selectedFlowers = ref<Flower[]>([])
77+
return { t, selectingWindow, selectedFlowers }
78+
},
79+
computed: {
80+
floors() : FloorInfo[] {
81+
return [
82+
{ number: 5, windows: [
83+
{ selection: WindowSelection.WINDOW_5, label: '' }
84+
]},
85+
{ number: 4, windows: [
86+
{ selection: WindowSelection.WINDOW_4L, label: this.t('playerMakeDelivery.windowLeft') },
87+
{ selection: WindowSelection.WINDOW_4R, label: this.t('playerMakeDelivery.windowRight') }
88+
]},
89+
{ number: 3, windows: [
90+
{ selection: WindowSelection.WINDOW_3L, label: this.t('playerMakeDelivery.windowLeft') },
91+
{ selection: WindowSelection.WINDOW_3R, label: this.t('playerMakeDelivery.windowRight') }
92+
]},
93+
{ number: 2, windows: [
94+
{ selection: WindowSelection.WINDOW_2L, label: this.t('playerMakeDelivery.windowLeft') },
95+
{ selection: WindowSelection.WINDOW_2R, label: this.t('playerMakeDelivery.windowRight') }
96+
]},
97+
{ number: 1, windows: [
98+
{ selection: WindowSelection.WINDOW_1L, label: this.t('playerMakeDelivery.windowLeft') },
99+
{ selection: WindowSelection.WINDOW_1R, label: this.t('playerMakeDelivery.windowRight') }
100+
]}
101+
]
102+
}
103+
},
104+
methods: {
105+
getDisplayFlowers(windowSelection: WindowSelection, floorNumber: number) : Flower[] {
106+
const state = this.windowStates.getWindowState(windowSelection)
107+
if (state) return state.flowers
108+
if (floorNumber === 5) return getAllEnumValues(Flower)
109+
return []
110+
},
111+
getDisplayDeliveries(windowSelection: WindowSelection) : Player[] {
112+
const state = this.windowStates.getWindowState(windowSelection)
113+
return state?.deliveries ?? []
114+
},
115+
canDeliver(windowSelection: WindowSelection) : boolean {
116+
const state = this.windowStates.getWindowState(windowSelection)
117+
if (!state) return true
118+
return state.deliveries.length < 4
119+
},
120+
deliverHere(windowSelection: WindowSelection, floorNumber: number) : void {
121+
const state = this.windowStates.getWindowState(windowSelection)
122+
if (state) {
123+
this.windowStates.addDelivery(windowSelection, Player.PLAYER)
124+
this.$emit('deliver', floorNumber)
125+
}
126+
else if (floorNumber === 5) {
127+
this.windowStates.setWindowState(windowSelection, getAllEnumValues(Flower), [Player.PLAYER])
128+
this.$emit('deliver', floorNumber)
129+
}
130+
else {
131+
this.selectingWindow = windowSelection
132+
this.selectedFlowers = []
133+
}
134+
},
135+
confirmDelivery(windowSelection: WindowSelection, floorNumber: number) : void {
136+
this.windowStates.setWindowState(windowSelection, [...this.selectedFlowers], [Player.PLAYER])
137+
this.selectingWindow = null
138+
this.$emit('deliver', floorNumber)
139+
}
140+
}
141+
})
142+
</script>
143+
144+
<style lang="scss" scoped>
145+
.window-box {
146+
min-width: 8rem;
147+
}
148+
</style>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<svg width="28" height="20" viewBox="0 0 28 20">
3+
<rect x="0.5" y="0.5" width="27" height="19" rx="3" :fill="color" stroke="#aaa" stroke-width="0.5"/>
4+
</svg>
5+
</template>
6+
7+
<script lang="ts">
8+
import { defineComponent } from 'vue'
9+
import Player from '@/services/enum/Player'
10+
import getPlayerColorCode from '@/util/getPlayerColorCode'
11+
import { useStateStore } from '@/store/state'
12+
13+
export default defineComponent({
14+
name: 'PlayerColorIcon',
15+
props: {
16+
player: {
17+
type: String as () => Player,
18+
required: true
19+
}
20+
},
21+
computed: {
22+
color() : string {
23+
const state = useStateStore()
24+
const playerColor = this.player === Player.PLAYER
25+
? state.setup.playerColor
26+
: state.setup.botColor
27+
return getPlayerColorCode(playerColor)
28+
}
29+
}
30+
})
31+
</script>

src/locales/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@
8080
"bonusActions": "Bonus Actions",
8181
"actionsNotReady": "Please complete the actions."
8282
},
83+
"playerMakeDelivery": {
84+
"floor": "Floor {floor}",
85+
"windowLeft": "Left",
86+
"windowRight": "Right",
87+
"deliverHere": "Deliver here",
88+
"selectFlowers": "Select {count} flower(s) for this window:",
89+
"confirm": "Confirm Delivery"
90+
},
8391
"botClaimMilestones": {
8492
"botClaim": "Lady Pei claims a milestone:",
8593
"fifthFloor": "Fifth Floor"

src/services/WindowStates.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ export default class WindowStates {
3434
}
3535
}
3636

37+
public addDelivery(windowSelection: WindowSelection, player: Player) : void {
38+
const existing = this._windowStates.value.find(dw => dw.windowSelection === windowSelection)
39+
if (!existing) {
40+
throw new Error(`Window state for ${windowSelection} not found.`)
41+
}
42+
existing.deliveries.push(player)
43+
}
44+
3745
public removeWindowState(windowSelection: WindowSelection) : void {
3846
this._windowStates.value = this._windowStates.value.filter(dw => dw.windowSelection !== windowSelection)
3947
}

src/services/enum/WindowSelection.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ enum WindowSelection {
77
WINDOW_2L = '2L',
88
WINDOW_2R = '2R',
99
WINDOW_3L = '3L',
10-
WINDOW_3R = '3R'
10+
WINDOW_3R = '3R',
11+
WINDOW_4L = '4L',
12+
WINDOW_4R = '4R',
13+
WINDOW_5 = '5'
1114
}
1215
export default WindowSelection

src/util/getWindowSelectionFloorCount.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@ export default function getWindowSelectionFloorCount(windowSelection: WindowSele
1414
case WindowSelection.WINDOW_3L:
1515
case WindowSelection.WINDOW_3R:
1616
return 3
17+
case WindowSelection.WINDOW_4L:
18+
case WindowSelection.WINDOW_4R:
19+
return 4
20+
case WindowSelection.WINDOW_5:
21+
return 5
1722
}
1823
}

src/views/RoundTurnPlayer.vue

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,34 @@
44

55
<BotClaimMilestones :milestones="navigationState.botClaimMilestones" v-if="navigationState.botClaimMilestones.length"/>
66

7-
<p class="mt-4" v-html="t('roundTurnPlayer.selectAction')"/>
7+
<template v-if="!showDeliveryActions">
8+
<p class="mt-4" v-html="t('roundTurnPlayer.selectAction')"/>
89

9-
<div class="actions">
10-
<button class="btn btn-primary btn-lg" v-if="!showDeliveryActions" @click="showDeliveryActions = true">
11-
{{t('roundTurnPlayer.makeDelivery')}}
12-
</button>
13-
<div class="deliveryActions" v-if="showDeliveryActions">
14-
<button class="btn btn-primary btn-lg" v-for="floor of floors" :key="floor" @click="deliverToFloor(floor)">
15-
{{t('roundTurnPlayer.deliveredToFloor', {floor})}}
10+
<div class="actions">
11+
<button class="btn btn-primary btn-lg" @click="showDeliveryActions = true">
12+
{{t('roundTurnPlayer.makeDelivery')}}
1613
</button>
17-
</div>
18-
<button class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#buyFlowerModal">
19-
{{t('roundTurnPlayer.buyFlower.title')}}
20-
</button>
21-
<button class="btn btn-primary btn-lg" @click="next">
22-
{{t('roundTurnPlayer.otherAction')}}
23-
</button>
24-
<div>
25-
<button class="btn btn-outline-danger btn-lg passButton" data-bs-toggle="modal" data-bs-target="#passModal">
26-
{{t('action.pass')}}<SoloBoardPassActionInfo :soloBoardPassAction="soloBoardPassAction"/>
14+
<button class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#buyFlowerModal">
15+
{{t('roundTurnPlayer.buyFlower.title')}}
16+
</button>
17+
<button class="btn btn-primary btn-lg" @click="next">
18+
{{t('roundTurnPlayer.otherAction')}}
2719
</button>
28-
<div class="small mt-1 fst-italic passNextTurnInfo">
29-
<span>{{t('roundTurnPlayer.passInfo.nextTurn')}}</span><SoloBoardPassActionInfo :soloBoardPassAction="soloBoardPassActionNextTurn" :small="true"/>
20+
<div>
21+
<button class="btn btn-outline-danger btn-lg passButton" data-bs-toggle="modal" data-bs-target="#passModal">
22+
{{t('action.pass')}}<SoloBoardPassActionInfo :soloBoardPassAction="soloBoardPassAction"/>
23+
</button>
24+
<div class="small mt-1 fst-italic passNextTurnInfo">
25+
<span>{{t('roundTurnPlayer.passInfo.nextTurn')}}</span><SoloBoardPassActionInfo :soloBoardPassAction="soloBoardPassActionNextTurn" :small="true"/>
26+
</div>
3027
</div>
3128
</div>
32-
</div>
29+
</template>
30+
31+
<PlayerMakeDelivery v-if="showDeliveryActions"
32+
:windowStates="navigationState.botPersistence.windowStates"
33+
:marketPrices="navigationState.marketPrices"
34+
@deliver="deliverToFloor"/>
3335

3436
<PlayerBuyFlowerModal :marketPrices="navigationState.marketPrices" @next="next"/>
3537
<PlayerPassSellFlowerModal :marketPrices="navigationState.marketPrices" :soloBoardPassAction="soloBoardPassAction" @pass="pass"/>
@@ -54,6 +56,7 @@ import getSoloBoardPassAction, { SoloBoardPassAction } from '@/util/getSoloBoard
5456
import SoloBoardPassActionInfo from '@/components/round/SoloBoardPassActionInfo.vue'
5557
import Player from '@/services/enum/Player'
5658
import BotClaimMilestones from '@/components/round/BotClaimMilestones.vue'
59+
import PlayerMakeDelivery from '@/components/round/PlayerMakeDelivery.vue'
5760
5861
export default defineComponent({
5962
name: 'RoundTurnPlayer',
@@ -64,6 +67,7 @@ export default defineComponent({
6467
PlayerBuyFlowerModal,
6568
PlayerPassSellFlowerModal,
6669
BotClaimMilestones,
70+
PlayerMakeDelivery,
6771
DebugInfo
6872
},
6973
setup() {
@@ -87,9 +91,6 @@ export default defineComponent({
8791
}
8892
return ''
8993
},
90-
floors() : number[] {
91-
return [5,4,3,2,1]
92-
},
9394
soloBoardPassAction() : SoloBoardPassAction {
9495
return this.navigationState.soloBoardPassAction
9596
},
@@ -140,11 +141,6 @@ export default defineComponent({
140141
gap: 1rem;
141142
max-width: 18rem;
142143
}
143-
.deliveryActions {
144-
display: flex;
145-
flex-direction: column;
146-
gap: 0.25rem;
147-
}
148144
.passButton {
149145
display: flex;
150146
justify-content: center;

tests/unit/services/WindowStates.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,30 @@ describe('services/WindowStates', () => {
9696

9797
expect(dw.windowStates.length).to.eq(0)
9898
})
99+
100+
it('addDelivery', () => {
101+
const dw = WindowStates.new()
102+
dw.setWindowState(WindowSelection.WINDOW_2L, [Flower.RED, Flower.BLUE], [Player.BOT])
103+
104+
dw.addDelivery(WindowSelection.WINDOW_2L, Player.PLAYER)
105+
106+
expect(dw.getWindowState(WindowSelection.WINDOW_2L)?.deliveries).to.deep.eq([Player.BOT, Player.PLAYER])
107+
})
108+
109+
it('addDelivery-multiple', () => {
110+
const dw = WindowStates.new()
111+
dw.setWindowState(WindowSelection.WINDOW_3R, [Flower.PURPLE, Flower.ORANGE, Flower.YELLOW], [])
112+
113+
dw.addDelivery(WindowSelection.WINDOW_3R, Player.PLAYER)
114+
dw.addDelivery(WindowSelection.WINDOW_3R, Player.BOT)
115+
dw.addDelivery(WindowSelection.WINDOW_3R, Player.PLAYER)
116+
117+
expect(dw.getWindowState(WindowSelection.WINDOW_3R)?.deliveries).to.deep.eq([Player.PLAYER, Player.BOT, Player.PLAYER])
118+
})
119+
120+
it('addDelivery-nonExisting', () => {
121+
const dw = WindowStates.new()
122+
123+
expect(() => dw.addDelivery(WindowSelection.WINDOW_1L, Player.PLAYER)).to.throw('Window state for 1L not found.')
124+
})
99125
})

tests/unit/util/getWindowSelectionFloorCount.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,16 @@ describe('util/getWindowSelectionFloorCount', () => {
2626
it('WINDOW_3R', () => {
2727
expect(getWindowSelectionFloorCount(WindowSelection.WINDOW_3R)).to.eq(3)
2828
})
29+
30+
it('WINDOW_4L', () => {
31+
expect(getWindowSelectionFloorCount(WindowSelection.WINDOW_4L)).to.eq(4)
32+
})
33+
34+
it('WINDOW_4R', () => {
35+
expect(getWindowSelectionFloorCount(WindowSelection.WINDOW_4R)).to.eq(4)
36+
})
37+
38+
it('WINDOW_5', () => {
39+
expect(getWindowSelectionFloorCount(WindowSelection.WINDOW_5)).to.eq(5)
40+
})
2941
})

0 commit comments

Comments
 (0)