diff options
Diffstat (limited to 'go/game/map.go')
| -rw-r--r-- | go/game/map.go | 303 |
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] +} |
