aboutsummaryrefslogtreecommitdiff
path: root/go
diff options
context:
space:
mode:
Diffstat (limited to 'go')
-rw-r--r--go/game/action.go6
-rw-r--r--go/game/cardImplementations.go16
-rw-r--r--go/game/cardImplementations_test.go48
-rw-r--r--go/game/events.go5
-rw-r--r--go/game/player.go20
-rw-r--r--go/game/state.go3
-rw-r--r--go/game/trigger.go24
7 files changed, 112 insertions, 10 deletions
diff --git a/go/game/action.go b/go/game/action.go
index d82bf56a..0cc0bd0a 100644
--- a/go/game/action.go
+++ b/go/game/action.go
@@ -108,10 +108,8 @@ func (a *FreeDiscardAction) PayCosts(s *LocalState) bool {
if a.targets.CheckTargets(s) != nil {
return false
}
- for _, sel := range a.Target().Selection() {
- card, _ := sel.(*Card)
- a.player.DiscardCard(card)
- }
+ cards := utils.InterfaceSliceToTypedSlice[*Card](a.Target().Selection())
+ a.player.discardCards(cards)
return true
}
diff --git a/go/game/cardImplementations.go b/go/game/cardImplementations.go
index 1f7af599..ea07ccf4 100644
--- a/go/game/cardImplementations.go
+++ b/go/game/cardImplementations.go
@@ -816,6 +816,21 @@ func (*recycleImpl) onPlay(a *PlayAction) {
}
}
+type reformerImpl struct{ cardImplementationBase }
+
+func (*reformerImpl) fullActions(u *Unit) []*FullAction {
+ fa := newFullAction(u, func(Action) ActionResolveFunc {
+ return func(s *LocalState) {
+ s.addTrigger(newOwnDiscardTrigger(u, false, true, func(s *LocalState, e Event) ActionResolveFunc {
+ return func(*LocalState) {
+ u.Controller().DrawN(len(e.affected))
+ }
+ }, "reformer draw"))
+ }
+ }, "draw for discard")
+ return []*FullAction{fa}
+}
+
func init() {
cardImplementations = map[string]cardImplementation{
"base/advisor": &advisorImpl{},
@@ -869,5 +884,6 @@ func init() {
"exp1/patrician": &patricianImpl{},
"exp1/power_to_the_people!": &powerToThePeopleImpl{},
"exp1/recycle!": &recycleImpl{},
+ "exp1/reformer": &reformerImpl{},
}
}
diff --git a/go/game/cardImplementations_test.go b/go/game/cardImplementations_test.go
index b3ac5811..51aab02f 100644
--- a/go/game/cardImplementations_test.go
+++ b/go/game/cardImplementations_test.go
@@ -346,3 +346,51 @@ func TestPowerToThePeople(t *testing.T) {
t.Fatal("Not gained 4 resource")
}
}
+
+func TestReformer(t *testing.T) {
+ s, _, player, _ := newMockState()
+
+ r := s.addNewUnit(NewCard("exp1/reformer"), Position{0, 0}, player)
+
+ c1 := NewCard("base/sword")
+ player.Hand.AddCard(c1)
+
+ ra := r.FullActions[0]
+ s.ResolveAction(ra)
+ s.handleTriggers()
+
+ if len(s.triggers) != 1 {
+ t.Fatal("No active reformer trigger")
+ }
+
+ player.discardCard(c1)
+ if len(s.events) != 1 {
+ t.Fatal("No discard event fired")
+ }
+
+ s.handleTriggers()
+ if s.stack.IsEmpty() {
+ t.Fatal("reformer draw not triggered")
+ }
+
+ if len(s.triggers) != 1 {
+ t.Fatal("Reformer trigger removed")
+ }
+
+ s.stack.pop()
+ if player.Hand.IsEmpty() {
+ t.Fatal("no card drawn")
+ }
+
+ player.DiscardPile.MoveCard(c1, player.Hand)
+ da := NewFreeDiscardAction(player)
+ da.Target().AddSelection(player.Hand.Cards()[0])
+ da.Target().AddSelection(player.Hand.Cards()[1])
+ s.declareAction(da)
+ s.stack.pop()
+ s.handleTriggers()
+ s.stack.pop()
+ if player.Hand.Size() != 2 {
+ t.Fatal("not two cards drawn")
+ }
+}
diff --git a/go/game/events.go b/go/game/events.go
index efe5cdb5..4e2479a8 100644
--- a/go/game/events.go
+++ b/go/game/events.go
@@ -20,6 +20,7 @@ const (
shuffle
declaredAction
resolvedAction
+ discard
)
var EventTypes = struct {
@@ -33,6 +34,7 @@ var EventTypes = struct {
Shuffle eventType
DeclaredAction eventType
ResolvedAction eventType
+ Discard eventType
}{
Destruction: destruction,
Eot: eot,
@@ -44,6 +46,7 @@ var EventTypes = struct {
Shuffle: shuffle,
DeclaredAction: declaredAction,
ResolvedAction: resolvedAction,
+ Discard: discard,
}
func (e eventType) String() string {
@@ -68,6 +71,8 @@ func (e eventType) String() string {
return "declaredAction"
case resolvedAction:
return "resolvedAction"
+ case discard:
+ return "discard"
}
log.Panicf("Unknown eventType: %d", e)
diff --git a/go/game/player.go b/go/game/player.go
index a3b5c44b..a8372040 100644
--- a/go/game/player.go
+++ b/go/game/player.go
@@ -198,14 +198,24 @@ func (p *Player) buyPhase() []*Player {
return nil
}
-func (p *Player) discardHand() {
- for _, c := range p.Hand.Cards() {
- p.DiscardCard(c)
+func (p *Player) _discardCard(c *Card) {
+ p.Hand.MoveCard(c, p.DiscardPile)
+}
+
+func (p *Player) discardCard(c *Card) {
+ p._discardCard(c)
+ p.gameState.fireEvent(Event{discard, []any{p}, []any{c}})
+}
+
+func (p *Player) discardCards(cards []*Card) {
+ for _, c := range cards {
+ p._discardCard(c)
}
+ p.gameState.fireEvent(Event{discard, []any{p}, utils.TypedSliceToInterfaceSlice(cards)})
}
-func (p *Player) DiscardCard(c *Card) {
- p.Hand.MoveCard(c, p.DiscardPile)
+func (p *Player) discardHand() {
+ p.discardCards(p.Hand.Cards())
}
func (p *Player) discardStep() {
diff --git a/go/game/state.go b/go/game/state.go
index 9a76a945..9baaea5f 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -411,6 +411,7 @@ func (s *LocalState) declareAction(a Action) {
}
func (s *LocalState) orderTriggeredActions(triggeredActions []*TriggeredAction) []Action {
+ // Sort triggers by their controller
playerActions := make(map[*Player][]*TriggeredAction)
for _, action := range triggeredActions {
p := action.Controller()
@@ -444,7 +445,7 @@ func (s *LocalState) orderTriggeredActions(triggeredActions []*TriggeredAction)
pActions = append(pActions, a)
}
orderedActions = append(orderedActions, pActions...)
- case *PassPriority: // Support kraken AI always sending PassPriority
+ case nil, *PassPriority: // Support kraken AI always sending PassPriority and mockPlayerControl
for _, a := range actions { // Simply use the order the actions were triggered
pActions = append(pActions, a)
}
diff --git a/go/game/trigger.go b/go/game/trigger.go
index e5b00a9c..6ab42856 100644
--- a/go/game/trigger.go
+++ b/go/game/trigger.go
@@ -56,6 +56,30 @@ func newTargetedTrigger(p Permanent, singleshot bool, resolveFunc func(*LocalSta
return &t
}
+func newDiscardTrigger(p Permanent, singleshot, singleturn bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, cond func(Event) bool, desc string) Trigger {
+ t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc}
+ t.condition = func(_ *LocalState, event Event) (bool, bool) {
+ if event.eventType == EventTypes.Eot && singleturn {
+ return false, true
+ }
+
+ if event.eventType != EventTypes.Discard {
+ return false, false
+ }
+
+ c := cond(event)
+ remove := singleshot && c
+ return c, remove
+ }
+ return &t
+}
+
+func newOwnDiscardTrigger(p Permanent, singleshot, singleturn bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, desc string) Trigger {
+ return newDiscardTrigger(p, singleshot, singleturn, resolveFunc, func(e Event) bool {
+ return e.sources[0] == p.Controller()
+ }, desc)
+}
+
func newDeathTrigger(p Permanent, singleshot bool, resolveFunc func(*LocalState, Event) ActionResolveFunc, permCond func(Permanent) bool, desc string) Trigger {
t := triggerBase{source: p, resolveFunc: resolveFunc, desc: desc}
t.condition = func(_ *LocalState, event Event) (bool, bool) {