aboutsummaryrefslogtreecommitdiff
path: root/go/game/map.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/game/map.go')
-rw-r--r--go/game/map.go303
1 files changed, 303 insertions, 0 deletions
diff --git a/go/game/map.go b/go/game/map.go
new file mode 100644
index 00000000..cab18f26
--- /dev/null
+++ b/go/game/map.go
@@ -0,0 +1,303 @@
+package game
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "math/rand"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+
+ "gopkg.in/yaml.v3"
+)
+
+const (
+ PROTOCOL string = "http://"
+ BASE_URL string = "localhost:8000/"
+ MAP_URL_PART string = "html/build/maps"
+
+ DEFAULT_RESOURCE_GAIN int = 5
+ DEFAULT_START_DECK string = "3 misc/farmer"
+)
+
+type MapYml struct {
+ Map string `yaml:"map"`
+ Symbols map[string]string `yaml:"symbols"`
+ StartDeckList string `yaml:"start_deck_list"`
+}
+
+func DummyWinCondition(*State) []*Player {
+ return []*Player{}
+}
+
+type Map struct {
+ Tiles [][]Tile
+ symbols map[string]string
+ ResourceGain int
+ StartDeckList string
+ WinCondition func(*State) []*Player
+ Stores map[Position]*Store
+}
+
+func readMap(r io.Reader) (*Map, error) {
+ data, err := io.ReadAll(r)
+ if err != nil {
+ return nil, err
+ }
+
+ return ParseMap(data)
+}
+
+func ParseMap(data []byte) (*Map, error) {
+ mapYml := MapYml{}
+ err := yaml.Unmarshal(data, &mapYml)
+ if err != nil {
+ return nil, err
+ }
+
+ mapYml.Symbols[" "] = "neutral"
+
+ m := &Map{
+ Tiles: [][]Tile{},
+ symbols: mapYml.Symbols,
+ ResourceGain: DEFAULT_RESOURCE_GAIN,
+ StartDeckList: DEFAULT_START_DECK,
+ WinCondition: DummyWinCondition,
+ Stores: make(map[Position]*Store),
+ }
+
+ if mapYml.StartDeckList != "" {
+ m.StartDeckList = mapYml.StartDeckList
+ }
+
+ rows := strings.Split(mapYml.Map, "\n")
+ for y := 0; y < len(rows); y++ {
+ row := rows[y]
+ m.Tiles = append(m.Tiles, []Tile{})
+ for x := 0; x < len(rows[y]); x++ {
+ pos := Position{x, y}
+ s := string(row[x])
+ tile, err := NewTileFromString(mapYml.Symbols[s], pos)
+ if err != nil {
+ return nil, err
+ }
+
+ if tile.Type == TileTypes.Store {
+ m.Stores[pos] = NewStore()
+ }
+
+ m.Tiles[y] = append(m.Tiles[y], tile)
+ }
+ }
+
+ m.selectStreets()
+
+ return m, nil
+}
+
+func (m *Map) selectStreets() {
+ for y := 0; y < len(m.Tiles); y++ {
+ for x := 0; x < len(m.Tiles[y]); x++ {
+ tile := &m.Tiles[y][x]
+ if tile.Type != TileTypes.Street {
+ continue
+ }
+ connections, left, right, above, below := m.FindStreetConnections(x, y)
+
+ // Decide if curve or straight
+ if connections == 2 && (left || right) && (above || below) {
+ tile.Raw = "street 2"
+ }
+
+ if connections > 2 {
+ tile.Raw = fmt.Sprintf("street %d", connections)
+ }
+ }
+ }
+}
+
+func (m *Map) FindNeighbours(x, y int) (left, right, above, below *Tile) {
+ left, right, above, below = nil, nil, nil, nil
+ if y > 0 {
+ above = &m.Tiles[y-1][x]
+ }
+
+ if y < len(m.Tiles)-1 {
+ below = &m.Tiles[y+1][x]
+ }
+
+ if x > 0 {
+ left = &m.Tiles[y][x-1]
+ }
+
+ if x < len(m.Tiles[y])-1 {
+ right = &m.Tiles[y][x+1]
+ }
+
+ return left, right, above, below
+}
+
+func (m *Map) FindAnyConnections(x, y int) (connections int, left, right, above, below bool) {
+ leftT, rightT, aboveT, belowT := m.FindNeighbours(x, y)
+ connections = 0
+
+ if leftT != nil && leftT.Type != TileTypes.Neutral {
+ left = true
+ connections += 1
+ }
+ if rightT != nil && rightT.Type != TileTypes.Neutral {
+ right = true
+ connections += 1
+ }
+ if aboveT != nil && aboveT.Type != TileTypes.Neutral {
+ above = true
+ connections += 1
+ }
+ if belowT != nil && belowT.Type != TileTypes.Neutral {
+ below = true
+ connections += 1
+ }
+
+ return
+}
+
+func (m *Map) FindStreetConnections(x int, y int) (connections int, left, right, above, below bool) {
+ leftT, rightT, aboveT, belowT := m.FindNeighbours(x, y)
+ connections = 0
+
+ if leftT != nil && leftT.Type == TileTypes.Street {
+ left = true
+ connections += 1
+ }
+ if rightT != nil && rightT.Type == TileTypes.Street {
+ right = true
+ connections += 1
+ }
+ if aboveT != nil && aboveT.Type == TileTypes.Street {
+ above = true
+ connections += 1
+ }
+ if belowT != nil && belowT.Type == TileTypes.Street {
+ below = true
+ connections += 1
+ }
+
+ return
+}
+
+func GetMap(name string) (*Map, error) {
+ url := path.Join(BASE_URL, MAP_URL_PART, name+".yml")
+ log.Printf("loading map from %s\n", url)
+ resp, err := http.Get(PROTOCOL + url)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer resp.Body.Close()
+ return readMap(resp.Body)
+}
+
+func LoadMap(file string) (*Map, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ log.Fatal("Opening map ", file, " failed: ", err)
+ }
+ defer f.Close()
+
+ return readMap(f)
+}
+
+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]
+}
+
+func (m *Map) RandomTile(r *rand.Rand, types []TileType) Position {
+ candidates := []Position{}
+ for y := 0; y < len(m.Tiles); y++ {
+ for x := 0; x < len(m.Tiles[y]); x++ {
+ candidate := m.Tiles[y][x]
+ for _, t := range types {
+ if candidate.Type == t {
+ candidates = append(candidates, candidate.Position)
+ break
+ }
+ }
+ }
+ }
+
+ randIdx := r.Intn(len(candidates))
+ return candidates[randIdx]
+}
+
+func (m *Map) RandomTileFromSymbols(r *rand.Rand, symbols []string) (Position, error) {
+ types := make([]TileType, 0, len(symbols))
+ for _, symbol := range symbols {
+ tileString, ok := m.symbols[symbol]
+ if !ok {
+ return INVALID_POSITION(), fmt.Errorf(fmt.Sprintf("symbol %s not in the map symbols %v", symbol, m.symbols))
+ }
+
+ tileType, ok := TileNames[tileString]
+ if !ok {
+ return INVALID_POSITION(), errors.New("tile type unknown")
+ }
+
+ types = append(types, tileType)
+ }
+ return m.RandomTile(r, 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
+}
+
+func (m *Map) AllTiles() []*Tile {
+ return m.FilterTiles(func(*Tile) bool { return true })
+}
+
+func (m *Map) distributeStoreCards(cards PileOfCards, rand *rand.Rand) {
+ nStores := len(m.Stores)
+ stores := make([]*Store, 0, nStores)
+ for _, store := range m.Stores {
+ stores = append(stores, store)
+ }
+
+ d := NewDeckFrom(cards)
+ d.Shuffle(rand)
+ nCards := d.Size()
+
+ for i := 0; i < nCards; i++ {
+ c := d.DrawOne()
+ stores[i%nStores].AddCard(c)
+ }
+}
+
+func (m *Map) redistributeStoreCards(cards PileOfCards, rand *rand.Rand) {
+ poc := NewPileOfCards()
+ for _, store := range m.Stores {
+ store.MoveInto(poc)
+ }
+ m.distributeStoreCards(poc, rand)
+}
+
+func (m *Map) HasStores() bool {
+ return len(m.Stores) > 0
+}
+
+func (m *Map) StoreOn(p Position) *Store {
+ return m.Stores[p]
+}