diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-01-24 00:27:50 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-01-27 16:43:44 +0100 |
| commit | 73d40903aecad3efcf7c6055c133f0db86558203 (patch) | |
| tree | e9c8f00fb186e270da92f273f570579f2d0b0d95 /go/game | |
| parent | fbba48698d8f8b83dfd7d914b0e37338e0d4e2ef (diff) | |
| download | muhqs-game-73d40903aecad3efcf7c6055c133f0db86558203.tar.gz muhqs-game-73d40903aecad3efcf7c6055c133f0db86558203.zip | |
intermediate commit
Diffstat (limited to 'go/game')
| -rw-r--r-- | go/game/action.go | 111 | ||||
| -rw-r--r-- | go/game/artifact.go | 5 | ||||
| -rw-r--r-- | go/game/card.go | 47 | ||||
| -rw-r--r-- | go/game/deck.go | 22 | ||||
| -rw-r--r-- | go/game/discardPiles.go | 4 | ||||
| -rw-r--r-- | go/game/hand.go | 29 | ||||
| -rw-r--r-- | go/game/kraken.go | 57 | ||||
| -rw-r--r-- | go/game/map.go | 23 | ||||
| -rw-r--r-- | go/game/pileOfCards.go | 59 | ||||
| -rw-r--r-- | go/game/player.go | 51 | ||||
| -rw-r--r-- | go/game/playerControl.go | 66 | ||||
| -rw-r--r-- | go/game/range.go | 5 | ||||
| -rw-r--r-- | go/game/stack.go | 10 | ||||
| -rw-r--r-- | go/game/state.go | 187 | ||||
| -rw-r--r-- | go/game/store.go | 7 | ||||
| -rw-r--r-- | go/game/tile.go | 17 | ||||
| -rw-r--r-- | go/game/unit.go | 25 | ||||
| -rw-r--r-- | go/game/winCondition.go | 36 |
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 +} |
