-
Notifications
You must be signed in to change notification settings - Fork 2
/
battle.go
447 lines (396 loc) · 10.6 KB
/
battle.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
package pokemonbattlelib
import (
"encoding/json"
"fmt"
"math/rand"
)
// A Pokemon battle. Enforces rules of the battle, and queries `Agent`s for turns.
type Battle struct {
Weather Weather // one of the 6 in-battle weather conditions
ShiftSet bool // shift or set battle style for NPC trainer battles
State BattleState
Type BattleType
rng RNG
ruleset BattleRule
parties []*battleParty // All parties participating in the battle
metadata map[BattleMeta]interface{} // Metadata to be tracked during a battle
tQueue []Transaction
tProcessed []Transaction
results BattleResults
}
type BattleRule int
const (
BattleRuleFaint BattleRule = 1 << iota
BattleRuleStruggle
)
const BattleRuleSetDefault = BattleRuleFaint | BattleRuleStruggle
type BattleMeta int
const (
MetaWeatherTurns BattleMeta = iota
)
type BattleState int
const (
BattleBeforeStart BattleState = iota
BattleInProgress
BattleEnd
)
type BattleType int
const (
BattleTypeSingle BattleType = iota
BattleTypeDouble
)
func (t BattleType) GetMaxActivePokemon() uint {
switch t {
case BattleTypeDouble:
return 2
default:
return 1
}
}
// Creates a new battle instance, setting initial conditions
func NewBattle() *Battle {
rng := LCRNG(rand.Uint32())
b := Battle{
State: BattleBeforeStart,
rng: RNG(&rng),
ruleset: BattleRuleSetDefault,
metadata: map[BattleMeta]interface{}{
MetaWeatherTurns: 0,
},
}
return &b
}
// Creates a new battle instance, setting initial conditions
func NewBattleOfType(t BattleType) *Battle {
rng := LCRNG(rand.Uint32())
b := Battle{
State: BattleBeforeStart,
Type: t,
rng: RNG(&rng),
ruleset: BattleRuleSetDefault,
metadata: map[BattleMeta]interface{}{
MetaWeatherTurns: 0,
},
}
return &b
}
// Sets the seed of the underlying random number generator used for the battle.
func (b *Battle) SetSeed(seed uint) {
b.rng.SetSeed(seed)
}
// Add a party to the battle, controlled by an agent. This method is preferred over `AddBattleParty`.
func (b *Battle) AddParty(p *Party, a *Agent, team int) {
b.AddBattleParty(&battleParty{
Party: p,
Agent: a,
activePokemon: make(map[uint]uint),
team: team,
})
}
// Adds one or more parties to a team in the battle
func (b *Battle) AddBattleParty(p ...*battleParty) {
b.parties = append(b.parties, p...)
}
// Gets the battle party for a given target
func (b *Battle) GetParty(t target) *battleParty {
return b.parties[t.party]
}
// Gets a reference to a Pokemon from a target
func (b *Battle) GetPokemon(t target) *Pokemon {
if t.party >= uint(len(b.parties)) {
panic(ErrorPartyIndex)
}
party := b.parties[t.party]
pokemon := party.pokemon()
if t.slot >= uint(len(pokemon)) {
panic(ErrorPartyIndex)
}
slot := t.slot
if party.IsActivePokemon(t.slot) {
slot = party.activePokemon[t.slot]
}
return pokemon[slot]
}
// Gets all the active Pokemon (targets) in the battle
func (b *Battle) AllTargets() []target {
targets := make([]target, 0)
for party, p := range b.parties {
for slot := range p.activePokemon {
t := target{
party: uint(party),
slot: slot,
}
targets = append(targets, t)
}
}
return targets
}
// Gets all active ally Pokemon for a party
func (b *Battle) getAllies(p *battleParty) []target {
allies := make([]target, 0)
targets := b.AllTargets()
for _, target := range targets {
party := b.parties[target.party]
if party.team == p.team {
allies = append(allies, target)
}
}
return allies
}
// Gets all active opponent Pokemon for a party
func (b *Battle) getOpponents(p *battleParty) []target {
opponents := make([]target, 0)
targets := b.AllTargets()
for _, target := range targets {
party := b.parties[target.party]
if party.team != p.team {
opponents = append(opponents, target)
}
}
return opponents
}
// Start the battle.
func (b *Battle) Start() error {
// validate team count
teams := map[int]int{}
for i, party := range b.parties {
if len(party.pokemon()) == 0 {
return fmt.Errorf("Party (index: %d) has no pokemon.", i)
}
teams[party.team]++
}
if len(teams) != 2 {
if b.Type == BattleTypeSingle {
return fmt.Errorf("Parties have invalid teams for single battle. There should be 2 teams with 1 party each, got %d teams.", len(teams))
} else if b.Type == BattleTypeDouble {
return fmt.Errorf("Parties have invalid teams for double battle. There should be 2 teams with 1-2 parties each, got %d teams.", len(teams))
}
}
// Initiate the battle! Send out the first pokemon in the parties.
b.State = BattleInProgress
for i := 0; i < 2; i++ { // assume 2 teams
parties := b.GetPartiesOnTeam(i)
pkmnPerParty := b.Type.GetMaxActivePokemon() / uint(len(parties))
for _, party := range parties {
toSendOut := pkmnPerParty
if toSendOut > uint(len(party.pokemon())) {
toSendOut = uint(len(party.pokemon()))
}
for j := uint(0); j < toSendOut; j++ {
party.SetActive(j)
}
}
}
return nil
}
// Get the parties that are in this battle.
func (b *Battle) Parties() []*Party {
parties := make([]*Party, len(b.parties))
// don't use range to avoid a shallow copy
for i := 0; i < len(b.parties); i++ {
parties[i] = b.parties[i].Party
}
return parties
}
func (b *Battle) GetPartiesOnTeam(team int) []*battleParty {
parties := make([]*battleParty, 0)
for _, party := range b.parties {
if party.team == team {
parties = append(parties, party)
}
}
return parties
}
// Targets act as a composite key to determine which Pokemon is being referenced.
// It composes of the party (index into the battle's parties),
// and the slot of an active Pokemon within that party.
type target struct {
party uint // Identifier for a party (index in battle parties, or "party ID")
slot uint // The slot of the active Pokemon
}
func (t target) MarshalJSON() ([]byte, error) {
type alias target
return json.Marshal(&struct {
Party uint
Slot uint
*alias
}{
Party: t.party,
Slot: t.slot,
alias: (*alias)(&t),
})
}
func (t *target) UnmarshalJSON(data []byte) error {
type alias target
aux := &struct {
Party uint
Slot uint
*alias
}{
alias: (*alias)(t),
}
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
t.party = aux.Party
t.slot = aux.Slot
return nil
}
func (t target) String() string {
return fmt.Sprintf("target{%d, %d}", t.party, t.slot)
}
type AgentTarget struct {
target // Inherit party/slot from `target`
Team int // The team that the Pokemon belongs to
Pokemon Pokemon // Copy of Pokemon for Agents to use
}
func (t AgentTarget) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Party uint
Slot uint
Team int
Pokemon Pokemon
}{
Party: t.party,
Slot: t.slot,
Team: t.Team,
Pokemon: t.Pokemon,
})
}
func (t *AgentTarget) UnmarshalJSON(data []byte) error {
aux := &struct {
Party uint
Slot uint
}{
Party: t.party,
Slot: t.slot,
}
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
t.party = aux.Party
t.slot = aux.Slot
return nil
}
type BattleContext struct {
Battle Battle // A copy of the current Battle, including weather, state, etc.
Team int // The team of the acting Pokemon
target target // The party/slot of the acting Pokemon
}
func (b *Battle) GetBattleContext(t target) *BattleContext {
p := b.parties[t.party]
return &BattleContext{
Battle: *b,
Team: p.team,
target: t,
}
}
func (bc *BattleContext) Self() AgentTarget {
return AgentTarget{
target: bc.target,
Team: bc.Team,
Pokemon: *bc.Battle.GetPokemon(bc.target), // Make this a copy!
}
}
func (bc *BattleContext) Allies() []AgentTarget {
b := bc.Battle
p := b.parties[bc.target.party]
targets := make([]AgentTarget, 0)
for _, t := range b.getAllies(p) {
targets = append(targets, AgentTarget{
target: t,
Team: b.parties[t.party].team,
Pokemon: *b.GetPokemon(t),
})
}
return targets
}
func (bc *BattleContext) Opponents() []AgentTarget {
b := bc.Battle
p := b.parties[bc.target.party]
targets := make([]AgentTarget, 0)
for _, t := range b.getOpponents(p) {
targets = append(targets, AgentTarget{
target: t,
Team: b.parties[t.party].team,
Pokemon: *b.GetPokemon(t),
})
}
return targets
}
func (bc *BattleContext) Targets() []AgentTarget {
b := bc.Battle
targets := make([]AgentTarget, 0)
for _, t := range b.AllTargets() {
targets = append(targets, AgentTarget{
target: t,
Team: b.parties[t.party].team,
Pokemon: *b.GetPokemon(t),
})
}
return targets
}
// Get the results of the battle. The battle must be in the `BattleEnd` state.
func (b *Battle) GetResults() BattleResults {
if b.State != BattleEnd {
blog.Panic("Unable to get results of a battle that has not ended.")
}
return b.results
}
// Custom JSON marshalling for battle context
func (bc BattleContext) MarshalJSON() ([]byte, error) {
type alias BattleContext // required to not enter infinite recursive loop
return json.Marshal(&struct {
Self AgentTarget
Allies []AgentTarget
Opponents []AgentTarget
Targets []AgentTarget
*alias
}{
Self: bc.Self(),
Allies: bc.Allies(),
Opponents: bc.Opponents(),
Targets: bc.Targets(),
alias: (*alias)(&bc),
})
}
// Results for a Battle.
type BattleResults struct {
Winner int // The team that won the battle.
Parties []*Party
}
// An abstraction over all possible actions an `Agent` can make in one round. Each Pokemon gets one turn.
type Turn interface {
Priority() int // Gets the turn's priority. Higher values go first. Not to be confused with Move priority.
}
// Wrapper used to determine turn order in a battle
type TurnContext struct {
User target // The pokemon that made this turn.
Turn Turn // A copy of the turn that a Pokemon made using an Agent
}
// A turn to represent a Pokemon using a Move.
type FightTurn struct {
Move int // Denotes the index (0-3) of the pokemon's which of the pokemon's moves to use.
Target AgentTarget // Info containing data determining the target of
}
func (turn FightTurn) Priority() int {
return 0
}
// A turn to represent using an item from the Party's inventory. An item turn has the a higher priority than any move.
type ItemTurn struct {
Move int // Denotes the index (0-3) of the pokemon's which of the pokemon's moves to use.
Target AgentTarget // Info containing data determining the target of
Item Item // Which item is being consumed
}
func (turn ItemTurn) Priority() int {
return 1
}
// A turn to represent switching an active Pokemon for a different, inactive Pokemon in battle.
type SwitchTurn struct {
Target AgentTarget // The target to swap to
}
func (turn SwitchTurn) Priority() int {
return 2
}