Skip to content

Commit 15e9f2a

Browse files
committed
deliver, window box actions
1 parent 5e3fade commit 15e9f2a

4 files changed

Lines changed: 722 additions & 0 deletions

File tree

src/services/BotActions.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import WindowStates from './WindowStates'
1010
import WindowSelection from './enum/WindowSelection'
1111
import PriceSelection from './enum/PriceSelection'
1212
import { cloneDeep } from 'lodash'
13+
import Player from './enum/Player'
1314

1415
/**
1516
* Collects the bot's actions and manages the automatic actions.
@@ -78,6 +79,20 @@ export default class BotActions {
7879
case Action.PRICE:
7980
this.processPrice(botAction)
8081
break
82+
case Action.DELIVERY:
83+
if (!this.processDelivery(botAction)) {
84+
// switch to window box if delivery is not possible
85+
botAction.action = Action.WINDOW_BOX
86+
this.processWindowBox(botAction)
87+
}
88+
break
89+
case Action.WINDOW_BOX:
90+
if (!this.processWindowBox(botAction)) {
91+
// switch to delivery if defining window box is not possible
92+
botAction.action = Action.DELIVERY
93+
this.processDelivery(botAction)
94+
}
95+
break
8196
}
8297
return botAction
8398
}
@@ -131,6 +146,38 @@ export default class BotActions {
131146
}
132147
}
133148

149+
/**
150+
* Executes bot delivery.
151+
* @return true If the delivery was successful
152+
*/
153+
private processDelivery(botAction: BotAction): boolean {
154+
const currentSeasonFlowers = this.getFlowersOfCurrentSeason()
155+
const window = this._windowStates.getBestMatchingDeliveryWindow(currentSeasonFlowers)
156+
if (window) {
157+
botAction.floor = window.floor
158+
botAction.windowSelection = window.windowSelection
159+
this._windowStates.addDelivery(window.floor, window.windowSelection, Player.BOT)
160+
return true
161+
}
162+
return false
163+
}
164+
165+
/**
166+
* Executes bot defining a new window box.
167+
* @return true If defining the window box was successful
168+
*/
169+
private processWindowBox(botAction: BotAction): boolean {
170+
const currentSeasonFlowers = this.getFlowersOfCurrentSeason()
171+
const window = this._windowStates.getBestMatchingUndefinedWindow(currentSeasonFlowers, this._marketPrices)
172+
if (window) {
173+
botAction.floor = window.floor
174+
botAction.windowSelection = window.windowSelection
175+
this._windowStates.setWindowState(window.floor, window.windowSelection, window.flowers, [Player.BOT])
176+
return true
177+
}
178+
return false
179+
}
180+
134181
private getFlowersOfCurrentSeason(): Flower[] {
135182
return this._botGarden.seasons.find(s => s.season === this._season)?.flowers || []
136183
}

src/services/WindowStates.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import WindowSelection from './enum/WindowSelection'
55
import Flower from './enum/Flower'
66
import Player from './enum/Player'
77
import getAllEnumValues from '@brdgm/brdgm-commons/src/util/enum/getAllEnumValues'
8+
import MarketPrices from './MarketPrices'
89

910
/**
1011
* Stores flowers of already defined windows.
@@ -49,6 +50,51 @@ export default class WindowStates {
4950
this._windowStates.value = this._windowStates.value.filter(dw => dw.floor !== floor || dw.windowSelection !== windowSelection)
5051
}
5152

53+
/**
54+
* Get the best matching delivery window for the given flowers, based on the bot's defined windows and their priority.
55+
*/
56+
public getBestMatchingDeliveryWindow(flowers: Flower[]) : WindowState|undefined {
57+
// get all defined windows that are not fully delivered yet, already ordered in bot's priority
58+
const windows = this._windowStates.value.filter(w => w.deliveries.length < 4)
59+
.toSorted((a, b) => {
60+
const matchA = getMatchCount(a.flowers, flowers)
61+
const matchB = getMatchCount(b.flowers, flowers)
62+
// sort windows by number of matching flowers, higher match has higher priority
63+
if (matchB !== matchA) return matchB - matchA
64+
// Sort windows by fewest missing flowers, as tie breaker. This ensures that if there are multiple windows with the same match count, the one with fewer missing flowers is prioritized.
65+
return (a.flowers.length - matchA) - (b.flowers.length - matchB)
66+
})
67+
return windows[0]
68+
}
69+
70+
/**
71+
* Gets the best-matching undefined window for the given flowers.
72+
* The flowers are also selected for the window, prioritizing more expensive flowers (based on the given market prices) in case there are more flowers than the window can hold.
73+
*/
74+
public getBestMatchingUndefinedWindow(flowers: Flower[], marketPrices: MarketPrices) : WindowState|undefined {
75+
// list all undefined windows, ordered top-to-bottom, left-to-right
76+
const windows : WindowState[] = []
77+
for (let floor = 4; floor >= 1; floor--) {
78+
for (const windowSelection of [WindowSelection.LEFT, WindowSelection.RIGHT]) {
79+
const existing = this._windowStates.value.find(dw => dw.floor === floor && dw.windowSelection === windowSelection)
80+
if (!existing) {
81+
windows.push({ floor, windowSelection, flowers: [], deliveries: [] })
82+
}
83+
}
84+
}
85+
// get the best-matching undefined window, skipping all windows larger then the given flowers
86+
const bestMatch = windows.filter(w => w.floor <= flowers.length)[0]
87+
if (bestMatch) {
88+
// sort the given flowers by price, higher price first, to prioritize matching more expensive flowers (otherwise keeping the order by bot flower priority)
89+
const flowersSorted = [...flowers].sort((a, b) => marketPrices.getPrice(b) - marketPrices.getPrice(a))
90+
bestMatch.flowers = flowersSorted.slice(0, bestMatch.floor)
91+
}
92+
return bestMatch
93+
}
94+
95+
/**
96+
* Sorts the windows by floor (descending) and window position (left, right).
97+
*/
5298
private sortFloorsWindows() : void {
5399
const selectionOrder = [WindowSelection.LEFT, WindowSelection.RIGHT]
54100
this._windowStates.value.sort((a, b) =>
@@ -81,3 +127,22 @@ export default class WindowStates {
81127
}
82128

83129
}
130+
131+
/**
132+
* Counts the number of matching flowers, taking duplicates in the flowers to match into account as well.
133+
* @param flowers Given flowers
134+
* @param flowersToMatch Flowers to match against
135+
* @returns number of matching flowers
136+
*/
137+
function getMatchCount(flowers: Flower[], flowersToMatch: Flower[]) : number {
138+
const flowersToMatchCopy = cloneDeep(flowersToMatch)
139+
let matchCount = 0
140+
for (const flower of flowers) {
141+
const index = flowersToMatchCopy.indexOf(flower)
142+
if (index >= 0) {
143+
matchCount++
144+
flowersToMatchCopy.splice(index, 1)
145+
}
146+
}
147+
return matchCount
148+
}

0 commit comments

Comments
 (0)