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] }