aboutsummaryrefslogtreecommitdiff
path: root/go/game
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-01-24 00:27:50 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:44 +0100
commit73d40903aecad3efcf7c6055c133f0db86558203 (patch)
treee9c8f00fb186e270da92f273f570579f2d0b0d95 /go/game
parentfbba48698d8f8b83dfd7d914b0e37338e0d4e2ef (diff)
downloadmuhqs-game-73d40903aecad3efcf7c6055c133f0db86558203.tar.gz
muhqs-game-73d40903aecad3efcf7c6055c133f0db86558203.zip
intermediate commit
Diffstat (limited to 'go/game')
-rw-r--r--go/game/action.go111
-rw-r--r--go/game/artifact.go5
-rw-r--r--go/game/card.go47
-rw-r--r--go/game/deck.go22
-rw-r--r--go/game/discardPiles.go4
-rw-r--r--go/game/hand.go29
-rw-r--r--go/game/kraken.go57
-rw-r--r--go/game/map.go23
-rw-r--r--go/game/pileOfCards.go59
-rw-r--r--go/game/player.go51
-rw-r--r--go/game/playerControl.go66
-rw-r--r--go/game/range.go5
-rw-r--r--go/game/stack.go10
-rw-r--r--go/game/state.go187
-rw-r--r--go/game/store.go7
-rw-r--r--go/game/tile.go17
-rw-r--r--go/game/unit.go25
-rw-r--r--go/game/winCondition.go36
18 files changed, 632 insertions, 129 deletions
diff --git a/go/game/action.go b/go/game/action.go
index 58eea8da..a705a0fe 100644
--- a/go/game/action.go
+++ b/go/game/action.go
@@ -11,39 +11,81 @@ type ActionPrototype interface {
}
type Action interface {
- Declare(s *State)
Resolve(s *State)
+ PayCosts(s *State) bool
String() string
}
+type PassPriorityAction struct{ Source *Player }
+
+func (*PassPriorityAction) Declare(*State) {}
+func (*PassPriorityAction) Resolve(*State) {}
+func (*PassPriorityAction) PayCosts(*State) bool{ return true }
+func (*PassPriorityAction) String() string { return "pass" }
+func NewPassPriorityAction(p *Player) Action { return &PassPriorityAction{p} }
+
type FastAction interface {
Action
- FastDeclare(s *State)
}
type ActionBase struct {
Source interface{}
Card *Card
resolveFunc func(s *State)
+ costFunc func(s *State) bool
desc string
}
-func (a *ActionBase) Declare(s *State) {
- if !s.Stack.IsEmpty() {
- log.Fatalf("Slow action can not be declared while other actions are on the stack")
- }
- s.Stack.Push(a)
-}
-
func (a *ActionBase) Resolve(s *State) {
a.resolveFunc(s)
}
+func (a *ActionBase) PayCosts(s *State) bool {
+ return a.costFunc(s)
+}
+
func (a *ActionBase) String() string {
- if p, ok := a.Source.(Permanent); ok {
+ switch source := a.Source.(type) {
+ case Permanent:
+ p := source
return fmt.Sprintf("%s: %s %s", p.GetController().Name, p.GetTile().Position.String(), a.desc)
+ case *Player:
+ return fmt.Sprintf("%s %s", source.Name, a.desc)
+ default:
+ return fmt.Sprintf("%v %s", source, a.desc)
+ }
+}
+
+type PlayAction struct {
+ ActionBase
+ targets interface{}
+}
+
+func NewPlayAction(p *Player, c *Card, targets interface{}) Action {
+ p.Hand.RemoveCard(c)
+
+ resolveFunc := func(s *State) {
+ s.ResolvePlay(p, c, targets)
+ }
+
+ costFunc := func(*State) bool {
+ var cost int
+ switch c.Type {
+ case UnitType:
+ cost = c.Values["upkeep"].(int)
+ default:
+ cost = c.Values["play"].(int)
+ }
+ if p.Ressource < cost {
+ log.Println("Cant pay", cost)
+ return false
+ }
+ p.Ressource -= cost
+ return true
}
- return fmt.Sprintf("%s %s", a.Source, a.desc)
+
+ desc := fmt.Sprintf("p %s@%v", c.Name, targets)
+ return &ActionBase{p, c, resolveFunc, costFunc, desc}
}
type MoveActionPrototype struct {
@@ -65,8 +107,17 @@ func (proto *MoveActionPrototype) Specialize(args ...interface{}) Action {
resolveFunc := func(s *State) {
u.move(s, target)
}
+
+ costFunc := func(*State) bool {
+ if u.AvailMoveActions < 1 {
+ return false
+ }
+ u.AvailMoveActions--
+ return true
+ }
+
desc := fmt.Sprintf("-> %s", target.Position.String())
- return &ActionBase{u, u.GetCard(), resolveFunc, desc}
+ return &ActionBase{u, u.GetCard(), resolveFunc, costFunc, desc}
}
type AttackActionPrototype struct {
@@ -84,15 +135,25 @@ func (proto *AttackActionPrototype) String() string {
func (proto *AttackActionPrototype) Specialize(args ...interface{}) Action {
target := args[0].(Permanent)
u := proto.Unit
+
resolveFunc := func(s *State) {
s.Fight(u, target)
}
+
+ costFunc := func(*State) bool {
+ if u.AvailAttackActions < 1 {
+ return false
+ }
+ u.AvailAttackActions--
+ return true
+ }
+
desc := fmt.Sprintf("x %s", target.GetTile().Position.String())
- return &ActionBase{u, u.GetCard(), resolveFunc, desc}
+ return &ActionBase{u, u.GetCard(), resolveFunc, costFunc, desc}
}
type FullActionPrototype struct {
- Permanent Permanent
+ Unit *Unit
}
func (proto *FullActionPrototype) String() string {
@@ -100,10 +161,18 @@ func (proto *FullActionPrototype) String() string {
}
func (proto *FullActionPrototype) Specialize(args ...interface{}) Action {
- p := proto.Permanent
+ u := proto.Unit
resolveFunc := args[0].(func(s *State))
+ costFunc := func(*State) bool {
+ if u.AvailAttackActions < 1 && u.AvailMoveActions < 1{
+ return false
+ }
+ u.AvailAttackActions--
+ u.AvailMoveActions--
+ return true
+ }
desc := "full_action"
- return &ActionBase{p, p.GetCard(), resolveFunc, desc}
+ return &ActionBase{u, u.GetCard(), resolveFunc, costFunc, desc}
}
type FreeActionPrototype struct {
@@ -117,17 +186,11 @@ func (proto *FreeActionPrototype) String() string {
func (proto *FreeActionPrototype) Specialize(args ...interface{}) Action {
p := proto.Permanent
resolveFunc := args[0].(func(s *State))
+ costFunc := args[1].(func(s *State) bool)
desc := "free_action"
- return &FreeAction{ActionBase{p, p.GetCard(), resolveFunc, desc}}
+ return &FreeAction{ActionBase{p, p.GetCard(), resolveFunc, costFunc, desc}}
}
type FreeAction struct {
ActionBase
}
-
-func (a *FreeAction) FastDeclare(s *State) {
- s.Stack.Push(a)
-}
-
-func (a *FreeAction) Resolve(s *State) {
-}
diff --git a/go/game/artifact.go b/go/game/artifact.go
index 452d5a6a..92a0361a 100644
--- a/go/game/artifact.go
+++ b/go/game/artifact.go
@@ -11,9 +11,12 @@ type Artifact struct {
Solid int
}
-func NewArtifact(cardPath string, tile *Tile, owner *Player) *Artifact {
+func NewArtifactFromPath(cardPath string, tile *Tile, owner *Player) *Artifact {
card := NewCard(cardPath)
+ return NewArtifact(card, tile, owner)
+}
+func NewArtifact(card *Card, tile *Tile, owner *Player) *Artifact {
a := &Artifact{NewPermanentBase(card, tile, owner), 0}
tile.Permanent = a
diff --git a/go/game/card.go b/go/game/card.go
index 8576a29e..87bbe723 100644
--- a/go/game/card.go
+++ b/go/game/card.go
@@ -5,6 +5,7 @@ import (
"log"
"net/http"
"path"
+ "strings"
"gopkg.in/yaml.v3"
)
@@ -18,7 +19,7 @@ const (
ArtifactType
EquipmentType
PotionType
- ItentionType
+ IntentionType
)
func (ct CardType) String() string {
@@ -35,8 +36,8 @@ func (ct CardType) String() string {
return "equipment"
case PotionType:
return "potion"
- case ItentionType:
- return "itention"
+ case IntentionType:
+ return "intention"
}
log.Panicf("Switch not exhausting %d", ct)
return ""
@@ -48,14 +49,14 @@ var CardTypes = struct {
Artifact CardType
Equipment CardType
Potion CardType
- Itention CardType
+ Intention CardType
}{
Unit: UnitType,
Spell: SpellType,
Artifact: ArtifactType,
Equipment: EquipmentType,
Potion: PotionType,
- Itention: ItentionType,
+ Intention: IntentionType,
}
var CardTypeNames = map[string]CardType{
@@ -64,18 +65,20 @@ var CardTypeNames = map[string]CardType{
"artifact": ArtifactType,
"equipment": EquipmentType,
"potion": PotionType,
- "itention": ItentionType,
+ "intention": IntentionType,
}
const (
CARD_DATA_URL string = "data/cards"
+ NOT_BUYABLE int = -1
)
type Card struct {
- Name string
- Set SetIdentifier
- Type CardType
- Values map[string]interface{}
+ Name string
+ Set SetIdentifier
+ Type CardType
+ BuyCost int
+ Values map[string]interface{}
}
var cards = map[string]map[string][]byte{}
@@ -103,8 +106,8 @@ func (card *Card) parseDefinition(definition []byte) {
log.Fatalf("parsing %s failed with: %v\n", definition, err)
}
- name, ok := data["name"]
- if !ok {
+ name, found := data["name"]
+ if !found {
log.Fatalf("no name in card yaml %s\n", definition)
}
delete(data, "name")
@@ -119,14 +122,20 @@ func (card *Card) parseDefinition(definition []byte) {
log.Fatalf("Unexpected type %T of yml card field 'Name'\n", name)
}
- t, ok := data["type"]
- if !ok {
+ t, found := data["type"]
+ if !found {
log.Fatalf("no type in card yaml %s\n", definition)
}
delete(data, "type")
card.Type = CardTypeNames[t.(string)]
+ bc, found := data["buy"]
+ if found {
+ card.BuyCost = bc.(int)
+ }
+ delete(data, "buy")
+
for k, v := range data {
card.Values[k.(string)] = v
}
@@ -165,7 +174,15 @@ func NewCard(cardPath string) *Card {
cards[set][cardName] = cardDefinition
}
- c := Card{"", SetIdentifierNames[set], UndefinedCardType, map[string]interface{}{}}
+ c := Card{"", SetIdentifierNames[set], UndefinedCardType, -1, map[string]interface{}{}}
c.parseDefinition(cardDefinition)
return &c
}
+
+func (c *Card) Path() string {
+ return path.Join(c.Set.String(), strings.ToLower(c.Name))
+}
+
+func (c *Card) IsPermanent() bool {
+ return c.Type != UndefinedCardType && c.Type != SpellType && c.Type != IntentionType
+}
diff --git a/go/game/deck.go b/go/game/deck.go
index f4290322..58a08985 100644
--- a/go/game/deck.go
+++ b/go/game/deck.go
@@ -9,36 +9,24 @@ import (
)
type Deck struct {
- Cards []*Card
+ PileOfCardsBase
}
func NewDeck() *Deck {
- d := Deck{[]*Card{}}
+ d := Deck{PileOfCardsBase{[]*Card{}}}
return &d
}
-func (d *Deck) AddCards(cards []*Card) {
- d.Cards = append(d.Cards, cards...)
-}
-
-func (d *Deck) AddCard(card *Card) {
- d.Cards = append(d.Cards, card)
-}
-
-func (d *Deck) Size() int {
- return len(d.Cards)
-}
-
func (d *Deck) Shuffle() {
rand.Seed(time.Now().UnixNano())
rand.Shuffle(d.Size(), func(i, j int) {
- d.Cards[i], d.Cards[j] = d.Cards[j], d.Cards[i]
+ d.cards[i], d.cards[j] = d.cards[j], d.cards[i]
})
}
func (d *Deck) Draw(amount int) []*Card {
- drawn := d.Cards[-amount:]
- d.Cards = d.Cards[:amount]
+ drawn := d.cards[0:amount]
+ d.cards = d.cards[:amount]
return drawn
}
diff --git a/go/game/discardPiles.go b/go/game/discardPiles.go
index f7cf8994..c39a9646 100644
--- a/go/game/discardPiles.go
+++ b/go/game/discardPiles.go
@@ -1,10 +1,10 @@
package game
type DiscardPile struct {
- Cards []*Card
+ PileOfCardsBase
}
func NewDiscardPile() *DiscardPile {
- dp := DiscardPile{[]*Card{}}
+ dp := DiscardPile{PileOfCardsBase{[]*Card{}}}
return &dp
}
diff --git a/go/game/hand.go b/go/game/hand.go
index 169c8cb3..2cbbe5b6 100644
--- a/go/game/hand.go
+++ b/go/game/hand.go
@@ -1,35 +1,10 @@
package game
type Hand struct {
- Cards []*Card
+ PileOfCardsBase
}
func NewHand() *Hand {
- h := Hand{[]*Card{}}
+ h := Hand{PileOfCardsBase{[]*Card{}}}
return &h
}
-
-func (h *Hand) Size() int {
- return len(h.Cards)
-}
-
-func (h *Hand) AddCards(cards []*Card) {
- h.Cards = append(h.Cards, cards...)
-}
-
-func (h *Hand) AddCard(card *Card) {
- h.Cards = append(h.Cards, card)
-}
-
-func (h *Hand) RemoveCard(card *Card) {
- ncards := len(h.Cards)
- for i, c := range h.Cards {
- if c != card {
- continue
- }
-
- h.Cards[i] = h.Cards[ncards-1]
- h.Cards = h.Cards[:ncards-1]
- return
- }
-}
diff --git a/go/game/kraken.go b/go/game/kraken.go
new file mode 100644
index 00000000..d88d87a3
--- /dev/null
+++ b/go/game/kraken.go
@@ -0,0 +1,57 @@
+package game
+
+import (
+ "image/color"
+)
+
+var KrakenDecklist = `angry_squid
+chachalot
+clownfish
+deja_vu!
+devour_the_poor!
+dolphin
+drowned_sailor
+flying_dutchmen
+frost_pylon
+gigantic_hail
+ice_berg
+sailfish
+sold_out!
+suprise!
+tentacle_slap
+the_kraken
+tides_change!
+unholy_cannonball`
+
+func NewKraken(id int, s *State, c color.Color) *Player {
+ d := NewDeckFromDeckList(KrakenDecklist)
+ kraken := NewPlayer(id, "The Kraken", d, NewStore(), s, c)
+ kraken.DrawPerTurn = 0
+ kraken.Ctrl = &KrakenControl{kraken}
+ return kraken
+}
+
+type KrakenControl struct {
+ kraken *Player
+}
+
+func (ctrl *KrakenControl) RecvAction() Action {
+ if ctrl.kraken.gameState.ActivePlayerId == ctrl.kraken.Id {
+ return NewPassPriorityAction(ctrl.kraken)
+ }
+
+ // TODO: implement AI
+ return NewPassPriorityAction(ctrl.kraken)
+}
+
+func (*KrakenControl) SendNotification(PlayerNotification) {
+}
+
+func (s *State) FindKraken() *Player {
+ for _, p := range s.Players {
+ if p.Name == "The Kraken" {
+ return p
+ }
+ }
+ return nil
+}
diff --git a/go/game/map.go b/go/game/map.go
index c84dc519..5783c9dd 100644
--- a/go/game/map.go
+++ b/go/game/map.go
@@ -29,11 +29,16 @@ type MapYml struct {
Symbols map[string]string `yaml:"symbols"`
}
+func DummyWinCondition(*State) []*Player {
+ return []*Player{}
+}
+
type Map struct {
Tiles [][]Tile
symbols map[string]string
RessourceGain int
StartDeckList string
+ WinCondition func(*State) []*Player
}
func readMap(r io.Reader) (*Map, error) {
@@ -54,7 +59,7 @@ func ParseMap(data []byte) (*Map, error) {
mapYml.Symbols[" "] = "neutral"
- m := &Map{[][]Tile{}, mapYml.Symbols, DEFAULT_RESSOURCE_GAIN, DEFAULT_START_DECK}
+ m := &Map{[][]Tile{}, mapYml.Symbols, DEFAULT_RESSOURCE_GAIN, DEFAULT_START_DECK, DummyWinCondition}
rows := strings.Split(mapYml.Map, "\n")
for y := 0; y < len(rows); y++ {
@@ -187,6 +192,9 @@ func LoadMap(file string) (*Map, error) {
}
func (m *Map) TileAt(p Position) *Tile {
+ if p.Y >= len(m.Tiles) || p.X >= len(m.Tiles[p.Y]) {
+ return nil
+ }
return &m.Tiles[p.Y][p.X]
}
@@ -226,3 +234,16 @@ func (m *Map) RandomTileFromSymbols(symbols []string) (Position, error) {
}
return m.RandomTile(types), nil
}
+
+func (m *Map) FilterTiles(filter func(t *Tile) bool) []*Tile {
+ tiles := []*Tile{}
+ for y := 0; y < len(m.Tiles); y++ {
+ for x := 0; x < len(m.Tiles[y]); x++ {
+ tile := &m.Tiles[y][x]
+ if filter(tile) {
+ tiles = append(tiles, tile)
+ }
+ }
+ }
+ return tiles
+}
diff --git a/go/game/pileOfCards.go b/go/game/pileOfCards.go
new file mode 100644
index 00000000..807235a5
--- /dev/null
+++ b/go/game/pileOfCards.go
@@ -0,0 +1,59 @@
+package game
+
+type PileOfCards interface {
+ Size() int
+ IsEmpty() bool
+ GetCards() []*Card
+ AddCards(cards []*Card)
+ AddCard(card *Card)
+ RemoveCard(card *Card)
+ MoveCard(card *Card, into PileOfCards)
+ MoveInto(into PileOfCards)
+}
+
+type PileOfCardsBase struct {
+ cards []*Card
+}
+
+func (poc *PileOfCardsBase) Size() int {
+ return len(poc.cards)
+}
+
+func (poc *PileOfCardsBase) IsEmpty() bool {
+ return poc.Size() == 0
+}
+
+func (poc *PileOfCardsBase) GetCards() []*Card {
+ return poc.cards
+}
+
+func (poc *PileOfCardsBase) AddCards(cards []*Card) {
+ poc.cards = append(poc.cards, cards...)
+}
+
+func (poc *PileOfCardsBase) AddCard(card *Card) {
+ poc.cards = append(poc.cards, card)
+}
+
+func (poc *PileOfCardsBase) RemoveCard(card *Card) {
+ ncards := poc.Size()
+ for i, c := range poc.cards {
+ if c != card {
+ continue
+ }
+
+ poc.cards[i] = poc.cards[ncards-1]
+ poc.cards = poc.cards[:ncards-1]
+ return
+ }
+}
+
+func (poc *PileOfCardsBase) MoveCard(c *Card, into PileOfCards) {
+ poc.RemoveCard(c)
+ into.AddCard(c)
+}
+
+func (poc *PileOfCardsBase) MoveInto(into PileOfCards) {
+ into.AddCards(poc.cards)
+ poc.cards = []*Card{}
+}
diff --git a/go/game/player.go b/go/game/player.go
index 78ba69a7..1e8c32f9 100644
--- a/go/game/player.go
+++ b/go/game/player.go
@@ -11,21 +11,66 @@ const (
type Player struct {
Id int
Name string
+ Turn int
+ DrawPerTurn int
Hand *Hand
DiscardPile *DiscardPile
Deck *Deck
+ Store *Store
Ressource int
gameState *State
Color color.Color
+ Ctrl PlayerControl
}
-func NewPlayer(id int, name string, deck *Deck, gameState *State, color color.Color) *Player {
- p := Player{id, name, NewHand(), NewDiscardPile(), deck, 0, gameState, color}
+func NewPlayer(id int, name string, deck *Deck, store *Store, gameState *State, color color.Color) *Player {
+ p := Player{id, name, 0, MAX_DRAW, NewHand(), NewDiscardPile(), deck, store, 0, gameState, color, nil}
return &p
}
+func (p *Player) Upkeep() {
+ p.Ressource += p.gameState.Map.RessourceGain
+
+ // TODO: handle upkeep triggers
+
+ for _, unit := range p.gameState.Units {
+ if unit.Controller != p {
+ continue
+ }
+
+ // TODO: let players decide if they want to pay the upkeep
+ p.Ressource -= unit.Upkeep
+ }
+}
+
+func (p *Player) ActionPhase() {
+ a := PromptAction(p.Ctrl)
+ for _, ok := a.(*PassPriorityAction); !ok; {
+ p.gameState.DeclareAction(a)
+ p.gameState.Stack.Resolve()
+ a = PromptAction(p.Ctrl)
+ }
+}
+
+func (p *Player) buy(c *Card) {
+ p.Ressource -= c.BuyCost
+ p.DiscardPile.AddCard(c)
+}
+
+func (p *Player) BuyStep() {
+ // TODO: select card to buy
+}
+
+func (p *Player) DiscardCard(c *Card) {
+ p.Hand.MoveCard(c, p.DiscardPile)
+}
+
+func (p *Player) DiscardStep() {
+ // TODO: select cards to discard
+}
+
func (p *Player) Draw() {
- toDraw := MAX_DRAW - p.Hand.Size()
+ toDraw := p.DrawPerTurn - p.Hand.Size()
if toDraw < 1 {
return
}
diff --git a/go/game/playerControl.go b/go/game/playerControl.go
new file mode 100644
index 00000000..2491dab7
--- /dev/null
+++ b/go/game/playerControl.go
@@ -0,0 +1,66 @@
+package game
+import "log"
+
+type PlayerNotificationType int
+
+const (
+ DeclaredActionNotification = iota
+ ResolvedActionNotification
+ PriorityNotification
+)
+
+type PlayerNotification struct {
+ Notification PlayerNotificationType
+ Action Action
+}
+
+func NewPriorityNotification() PlayerNotification {
+ return PlayerNotification{PriorityNotification, nil}
+}
+
+func NewDeclaredActionNotification(a Action) PlayerNotification {
+ return PlayerNotification{DeclaredActionNotification, a}
+}
+
+func NewResolvedActionNotification(a Action) PlayerNotification {
+ return PlayerNotification{ResolvedActionNotification, a}
+}
+
+type PlayerControl interface {
+ RecvAction() Action
+ SendNotification(PlayerNotification)
+}
+
+type DummyPlayerControl struct{ P *Player }
+
+func (c *DummyPlayerControl) RecvAction() Action { return NewPassPriorityAction(c.P) }
+func (*DummyPlayerControl) SendNotification(PlayerNotification) { return }
+
+type ChanPlayerControl struct {
+ Actions chan Action
+ Notifications chan PlayerNotification
+}
+
+func (c *ChanPlayerControl) SendAction(a Action) { c.Actions <- a }
+func (c *ChanPlayerControl) RecvAction() Action { return <-c.Actions }
+func (c *ChanPlayerControl) SendNotification(n PlayerNotification) { c.Notifications <- n }
+func (c *ChanPlayerControl) RecvNotification() *PlayerNotification {
+ select {
+ case n := <-c.Notifications:
+ return &n
+ default:
+ return nil
+ }
+}
+
+func NewChanPlayerControl() *ChanPlayerControl {
+ a := make(chan Action)
+ n := make(chan PlayerNotification)
+ return &ChanPlayerControl{a, n}
+}
+
+func PromptAction(ctrl PlayerControl) Action {
+ log.Println("PromptAction")
+ ctrl.SendNotification(NewPriorityNotification())
+ return ctrl.RecvAction()
+}
diff --git a/go/game/range.go b/go/game/range.go
index e12e1e67..1aa50f8a 100644
--- a/go/game/range.go
+++ b/go/game/range.go
@@ -41,7 +41,10 @@ func PositionsInRange(origin Position, r int, includeOrigin bool) []Position {
func TilesInRange(m *Map, p Permanent, r int) []*Tile {
tiles := []*Tile{}
for _, pos := range PositionsInRange(p.GetTile().Position, r, false) {
- tiles = append(tiles, m.TileAt(pos))
+ tile := m.TileAt(pos)
+ if tile != nil {
+ tiles = append(tiles, tile)
+ }
}
return tiles
}
diff --git a/go/game/stack.go b/go/game/stack.go
index 22c20fa3..45ba4072 100644
--- a/go/game/stack.go
+++ b/go/game/stack.go
@@ -18,6 +18,7 @@ func (s *Stack) IsEmpty() bool {
}
func (s *Stack) Push(a Action) {
+ s.gameState.broadcastNotification(NewDeclaredActionNotification(a))
s.Actions = append(s.Actions, a)
}
@@ -29,4 +30,13 @@ func (s *Stack) Pop() {
a := s.Actions[l-1]
s.Actions = s.Actions[:l-1]
a.Resolve(s.gameState)
+ s.gameState.broadcastNotification(NewResolvedActionNotification(a))
+}
+
+func (s *Stack) Resolve() {
+ for !s.IsEmpty() {
+ log.Println("Resolve Stack")
+ s.gameState.AllPassing()
+ s.Pop()
+ }
}
diff --git a/go/game/state.go b/go/game/state.go
index 5cab9458..1a6ae093 100644
--- a/go/game/state.go
+++ b/go/game/state.go
@@ -2,21 +2,25 @@ package game
import (
"image/color"
+ "log"
"math/rand"
+ "strconv"
+ "strings"
)
type State struct {
- Decks []*Deck
- Stores []*Store
- Map *Map
- Stack *Stack
- Hands []*Hand
- DiscardPiles []*DiscardPile
- Players []*Player
- Permanents []Permanent
- Units []*Unit
- Artifacts []*Artifact
- Rand rand.Rand
+ Decks []*Deck
+ Stores []*Store
+ Map *Map
+ Stack *Stack
+ Hands []*Hand
+ DiscardPiles []*DiscardPile
+ Players []*Player
+ ActivePlayerId int
+ Permanents []Permanent
+ Units []*Unit
+ Artifacts []*Artifact
+ Rand rand.Rand
}
func NewState() *State {
@@ -25,15 +29,138 @@ func NewState() *State {
return s
}
-func (s *State) AddNewPlayer(name string, deck *Deck, color color.Color) {
- p := NewPlayer(len(s.Players), name, deck, s, color)
+func (s *State) AddNewPlayer(name string, deck *Deck, store *Store, color color.Color) {
+ p := NewPlayer(len(s.Players)+1, name, deck, store, s, color)
s.Players = append(s.Players, p)
}
-func (s *State) AddNewUnit(card string, pos Position, owner *Player) {
- unit := NewUnit(card, s.Map.TileAt(pos), owner)
- s.Permanents = append(s.Permanents, unit)
- s.Units = append(s.Units, unit)
+func (s *State) Loop() []*Player {
+ winners := []*Player{}
+ for len(winners) == 0 {
+ for i, p := range s.Players {
+ log.Printf("Starting %s turn %d\n", p.Name, p.Turn)
+ s.ActivePlayerId = i
+ p.Turn++
+
+ if p.Deck.IsEmpty() {
+ log.Printf("Shuffle %s pile into the deck\n", p.Name)
+ p.DiscardPile.MoveInto(p.Deck)
+ p.Deck.Shuffle()
+ }
+
+ log.Printf("%s draw\n", p.Name)
+ p.Draw()
+
+ log.Printf("%s Upkeep\n", p.Name)
+ p.Upkeep()
+ s.PhaseChange(i)
+
+ log.Printf("%s Actions\n", p.Name)
+ p.ActionPhase()
+ s.PhaseChange(i)
+
+ log.Printf("%s Buystep\n", p.Name)
+ p.BuyStep()
+ s.PhaseChange(i)
+
+ log.Printf("%s Discard\n", p.Name)
+ p.DiscardStep()
+
+ winners = s.Map.WinCondition(s)
+ }
+ }
+
+ return winners
+}
+
+func (s *State) PushAction(a Action) {
+ if !a.PayCosts(s) {
+ log.Fatalf("Cost for a could not be paid")
+ }
+ s.Stack.Push(a)
+}
+
+func (s *State) DeclareAction(a Action) {
+ if !s.Stack.IsEmpty() {
+ log.Fatalf("Slow action can not be declared while other actions are on the stack")
+ }
+ s.PushAction(a)
+}
+
+func (s *State) DeclareFastAction(a Action) {
+ s.PushAction(a)
+}
+
+func (s *State) AllPassing() bool {
+ nPlayers := len(s.Players)
+ for i := 0; i < nPlayers; i++ {
+ p := s.Players[(i+s.ActivePlayerId)%nPlayers]
+ p.Ctrl.SendNotification(NewPriorityNotification())
+ a := p.Ctrl.RecvAction()
+ switch a.(type) {
+ case *PassPriorityAction:
+ log.Printf("%s passed %d/%d\n", p.Name, i, nPlayers)
+ default:
+ s.Stack.Push(a)
+ return false
+ }
+ }
+ return true
+}
+
+func (s *State) PhaseChange(activePlaerId int) {
+ log.Println("Phase change")
+ for !s.AllPassing() {
+ s.Stack.Resolve()
+ }
+}
+
+func (s *State) broadcastNotification(n PlayerNotification) {
+ log.Printf("broadcasting %v\n", n)
+ for _, p := range s.Players {
+ p.Ctrl.SendNotification(n)
+ }
+}
+
+func (s *State) Upkeep(p *Player) {
+ p.Ressource += s.Map.RessourceGain
+ // TODO: handle upkeep trigger
+ for _, unit := range s.Units {
+ if unit.Controller != p {
+ continue
+ }
+
+ // TODO: let players decide if they want to pay the upkeep
+ p.Ressource -= unit.Upkeep
+ }
+}
+
+func (s *State) AddPermanent(perm Permanent) {
+ s.Permanents = append(s.Permanents, perm)
+ switch p := perm.(type) {
+ case *Unit:
+ s.Units = append(s.Units, p)
+ case *Artifact:
+ s.Artifacts = append(s.Artifacts, p)
+ }
+}
+
+func (s *State) AddNewUnit(card *Card, pos Position, owner *Player) {
+ s.AddPermanent(NewUnit(card, s.Map.TileAt(pos), owner))
+}
+
+func (s *State) AddNewArtifact(card *Card, pos Position, owner *Player) {
+ s.AddPermanent(NewArtifact(card, s.Map.TileAt(pos), owner))
+}
+
+func (s *State) AddNewPermFromPath(cardPath string, pos Position, owner *Player) {
+ card := NewCard(cardPath)
+ switch card.Type {
+ case UnitType:
+ s.AddNewUnit(card, pos, owner)
+ case ArtifactType:
+ s.AddNewArtifact(card, pos, owner)
+ }
}
func (s *State) removePermanent(p Permanent) {
@@ -66,12 +193,6 @@ func (s *State) DestroyPermanent(p Permanent) {
}
}
-func (s *State) AddNewArtifact(card string, pos Position, owner *Player) {
- artifact := NewArtifact(card, s.Map.TileAt(pos), owner)
- s.Permanents = append(s.Permanents, artifact)
- s.Artifacts = append(s.Artifacts, artifact)
-}
-
func (s *State) Fight(p1, p2 Permanent) {
p1.Fight(p2)
p2.Fight(p1)
@@ -97,3 +218,23 @@ func (s *State) EnemyUnits(player *Player) []*Unit {
}
return enemyUnits
}
+
+func (s *State) ResolvePlay(p *Player, c *Card, targets interface{}) {
+ switch c.Type {
+ case UnitType:
+ s.AddNewUnit(c, targets.(Position), p)
+ }
+ p.DiscardPile.AddCard(c)
+}
+
+func (s *State) SpawnTiles(p *Player) []*Tile {
+ spawns := s.Map.FilterTiles(func(t *Tile) bool {
+ if t.Type != TileTypes.Spawn {
+ return false
+ }
+
+ return strings.HasSuffix(t.Raw, strconv.Itoa(p.Id)) || t.Raw == "player spawn"
+ })
+
+ return spawns
+}
diff --git a/go/game/store.go b/go/game/store.go
index 3f5793fb..46c136e0 100644
--- a/go/game/store.go
+++ b/go/game/store.go
@@ -1,5 +1,10 @@
package game
type Store struct {
- Cards []Card
+ PileOfCardsBase
+}
+
+func NewStore() *Store {
+ s := &Store{PileOfCardsBase{[]*Card{}}}
+ return s
}
diff --git a/go/game/tile.go b/go/game/tile.go
index f66b4fb0..cbceb6f7 100644
--- a/go/game/tile.go
+++ b/go/game/tile.go
@@ -64,18 +64,25 @@ type Tile struct {
Position Position
Permanent Permanent
Type TileType
+ Water bool
Raw string
}
func INVALID_TILE() Tile {
- return Tile{INVALID_POSITION(), nil, 0, ""}
+ return Tile{INVALID_POSITION(), nil, 0, false, ""}
}
-func NewTileFromString(tile string, pos Position) (Tile, error) {
+func NewTileFromString(t string, pos Position) (Tile, error) {
+ tile := strings.ToLower(t)
tokens := strings.Split(tile, " ")
- tileType := TileNames[strings.ToLower(tokens[0])]
- t := Tile{pos, nil, tileType, tile}
- return t, nil
+ tileType, found:= TileNames[tokens[0]]
+ if !found {
+ if strings.Contains(tile, "spawn") {
+ tileType = spawn
+ }
+ }
+ water := strings.Contains(tile, "water")
+ return Tile{pos, nil, tileType, water, t}, nil
}
func (t *Tile) IsFree() bool {
diff --git a/go/game/unit.go b/go/game/unit.go
index 028ff274..02ba9830 100644
--- a/go/game/unit.go
+++ b/go/game/unit.go
@@ -31,18 +31,23 @@ func (a *Attack) String() string {
return fmt.Sprintf("%d range %d", a.Damage, a.Range)
}
-func parseAttack(attack string) Attack {
- tokens := strings.Split(attack, " ")
+func parseAttack(attack interface{}) Attack {
+ if damage, ok := attack.(int); ok {
+ return Attack{damage, 1}
+ }
+
+ attackStr := attack.(string)
+ tokens := strings.Split(attackStr, " ")
a, err := strconv.Atoi(tokens[0])
if err != nil {
- log.Fatalf("Attack %s has no valid Damage value\n", attack)
+ log.Fatalf("Attack %s has no valid Damage value\n", attackStr)
}
r := 1
if len(tokens) == 3 {
r, err = strconv.Atoi(tokens[2])
if err != nil {
- log.Fatalf("Attack %s has no valid Range value\n", attack)
+ log.Fatalf("Attack %s has no valid Range value\n", attackStr)
}
}
return Attack{a, r}
@@ -62,14 +67,12 @@ type Unit struct {
AvailAttackActions int
}
-func NewUnit(cardPath string, tile *Tile, owner *Player) *Unit {
- card := NewCard(cardPath)
-
+func NewUnit(card *Card, tile *Tile, owner *Player) *Unit {
u := &Unit{
NewPermanentBase(card, tile, owner),
card.Values["health"].(int),
card.Values["movement"].(int),
- parseAttack(card.Values["attack"].(string)),
+ parseAttack(card.Values["attack"]),
card.Values["upkeep"].(int),
[]*FullActionPrototype{},
[]*FreeActionPrototype{},
@@ -82,6 +85,10 @@ func NewUnit(cardPath string, tile *Tile, owner *Player) *Unit {
return u
}
+func NewUnitFromPath(cardPath string, tile *Tile, owner *Player) *Unit {
+ return NewUnit(NewCard(cardPath), tile, owner)
+}
+
func (u *Unit) move(s *State, tile *Tile) {
if !slices.Contains(u.MoveRangeTiles(s.Map), tile) {
log.Panicf("Unit %s at %v can not move to %v", u.Card.Name, u.Tile.Position, tile.Position)
@@ -98,7 +105,7 @@ func (u *Unit) MoveRangeTiles(m *Map) []*Tile {
tiles := []*Tile{}
for _, pos := range PositionsInRange(u.Tile.Position, u.Movement, false) {
tile := m.TileAt(pos)
- if u.IsAvailableTile(tile) {
+ if tile != nil && u.IsAvailableTile(tile) {
tiles = append(tiles, tile)
}
}
diff --git a/go/game/winCondition.go b/go/game/winCondition.go
new file mode 100644
index 00000000..fa07257f
--- /dev/null
+++ b/go/game/winCondition.go
@@ -0,0 +1,36 @@
+package game
+
+import (
+ "golang.org/x/exp/slices"
+)
+
+func KingGame(s *State) []*Player {
+ foundKings := map[*Player]struct{}{}
+ for _, u := range s.Units {
+ if u.Card.Name != "King" {
+ continue
+ }
+
+ foundKings[u.Owner] = struct{}{}
+ }
+
+ if len(foundKings) == len(s.Players) {
+ return []*Player{}
+ }
+
+ loosers := make([]*Player, 0, len(s.Players))
+ copy(loosers, s.Players)
+ for p := range foundKings {
+ i := slices.Index(loosers, p)
+ loosers[i] = loosers[len(loosers)-1]
+ loosers = loosers[:len(loosers)-1]
+ }
+
+ winners := []*Player{}
+ for _, p := range s.Players {
+ if !slices.Contains(loosers, p) {
+ winners = append(winners, p)
+ }
+ }
+ return winners
+}