diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-08-21 17:26:35 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-21 17:26:35 +0200 |
| commit | af168066174ee2e7172bd3acb16e316265c1189b (patch) | |
| tree | 38dd563a5e4d15a9896af2774e0760aa9d4f0a50 | |
| parent | 14b14369c10abab2f0de2f7109735f95f1a4c94a (diff) | |
| download | muhqs-game-af168066174ee2e7172bd3acb16e316265c1189b.tar.gz muhqs-game-af168066174ee2e7172bd3acb16e316265c1189b.zip | |
add random map generation to the random challenge
| -rw-r--r-- | go/game/challenge.go | 27 | ||||
| -rw-r--r-- | go/game/map.go | 180 | ||||
| -rw-r--r-- | go/ui/mapView.go | 8 |
3 files changed, 201 insertions, 14 deletions
diff --git a/go/game/challenge.go b/go/game/challenge.go index 3b5b35cd..e12459fb 100644 --- a/go/game/challenge.go +++ b/go/game/challenge.go @@ -1,17 +1,14 @@ package game import ( - "log" "math/rand" + + "muhq.space/muhqs-game/go/log" ) -func prepState(playerName, mapName string) *LocalState { +func prepState(playerName string, m *Map) *LocalState { s := NewLocalState() - if m, err := GetMap(mapName); err != nil { - log.Panic(err) - } else { - s.SetMap(m) - } + s.SetMap(m) // Prepare the players s.AddNewPlayer(playerName, NewDeck()) @@ -24,7 +21,17 @@ func prepState(playerName, mapName string) *LocalState { func NewRandomChallenge(name string) *LocalState { maps := []string{"2P-ring-street", "2P-river-king"} - m := maps[rand.Intn(len(maps))] + var m *Map + n := rand.Intn(len(maps) + 1) + switch n { + case 0, 1: + m, _ = GetMap(maps[rand.Intn(len(maps))]) + case 2: + seed := rand.Int63() + log.Info("create random map", "seed", seed) + m = RandomMap(seed, 2) + } + s := prepState(name, m) switch rand.Intn(3) { @@ -107,7 +114,7 @@ func prepCavArcherChallenge(s *LocalState) *LocalState { func NewCavArcherChallenge(playerName string) *LocalState { maps := []string{"2P-ring-street", "2P-river-king"} - m := maps[rand.Intn(len(maps))] + m, _ := GetMap(maps[rand.Intn(len(maps))]) return prepCavArcherChallenge(prepState(playerName, m)) } @@ -154,7 +161,7 @@ func prepMissionaryChallenge(s *LocalState) *LocalState { func NewMissionaryChallenge(playerName string) *LocalState { maps := []string{"2P-ring-street", "2P-river-king"} - m := maps[rand.Intn(len(maps))] + m, _ := GetMap(maps[rand.Intn(len(maps))]) return prepMissionaryChallenge(prepState(playerName, m)) } diff --git a/go/game/map.go b/go/game/map.go index f2cf1c53..a83f4984 100644 --- a/go/game/map.go +++ b/go/game/map.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "path" + "strconv" "strings" "golang.org/x/exp/slices" @@ -315,3 +316,182 @@ func (m *Map) HasStores() bool { func (m *Map) StoreOn(p Position) *Store { return m.Stores[p] } + +type randomMapDef struct { + types []TileType + stores int + farms int + players int + x, y int +} + +var mapGens = []func(*rand.Rand, randomMapDef) *Map{ + randomMapGen, + dotSymRandomMapGen, +} + +func randomMapGen(r *rand.Rand, def randomMapDef) *Map { + log.Debug("random map gen", "def", def) + m := &Map{ + Tiles: make([][]Tile, 0, def.y), + ResourceGain: DEFAULT_RESOURCE_GAIN, + StartDeckList: DEFAULT_START_DECK, + WinCondition: KingGame, + Stores: make(map[Position]*Store), + } + m.Prepare = func(s *LocalState) { + for _, t := range m.AllTiles() { + if t.Type != spawn { + continue + } + card := NewCard("misc/king") + tokens := strings.Split(t.Raw, " ") + id, _ := strconv.Atoi(tokens[len(tokens)-1]) + owner := s.PlayerById(id) + // TODO: Fix position index base. + s.addNewUnit(card, t.Position, owner) + } + } + + for y := range def.y { + m.Tiles = append(m.Tiles, make([]Tile, 0, def.x)) + for x := range def.x { + s := def.types[rand.Intn(len(def.types))].String() + t, _ := NewTileFromString(s, Position{x, y}) + m.Tiles[y] = append(m.Tiles[y], t) + } + } + + spawns := []Position{ + Position{0, 0}, + Position{def.x - 1, def.y - 1}, + Position{0, def.y - 1}, + Position{def.x - 1, 0}, + } + + // Prepare spawn tiles + for n := range def.players { + pos := spawns[n] + log.Debug("add spawn", "pos", pos) + t, _ := NewTileFromString(fmt.Sprintf("spawn player %d", n+1), pos) + m.Tiles[pos.Y][pos.X] = t + } + + // Place stores on non spawn tiles + if def.stores > 0 { + for range def.stores { + var pos Position + for { + pos = Position{r.Intn(def.x), r.Intn(def.y)} + if m.TileAt(pos).Type != spawn { + break + } + } + log.Debug("place store at", "pos", pos) + t, _ := NewTileFromString("store", pos) + m.Tiles[pos.Y][pos.X] = t + m.Stores[pos] = NewStore() + } + } + + // Place stores on non spawn tiles + if def.farms > 0 { + for range def.farms { + var pos Position + for { + pos = Position{r.Intn(def.x), r.Intn(def.y)} + tt := m.TileAt(pos).Type + if tt != spawn && tt != store { + break + } + } + log.Debug("place farm at", "pos", pos) + t, _ := NewTileFromString("farm", pos) + m.Tiles[pos.Y][pos.X] = t + } + } + + return m +} + +func dotSymRandomMapGen(r *rand.Rand, def randomMapDef) *Map { + def.x = def.x / 2 + // We place the stores symmetrically + def.stores = 0 + m := randomMapGen(r, def) + log.Debug("random map has dimensions", "x", len(m.Tiles[0]), "y", len(m.Tiles)) + + // point reflection + // max tiles to reflect in this row + mY := len(m.Tiles) - 1 + mX := len(m.Tiles[0]) - 1 + for y := mY; y >= 0; y = y - 1 { + for x := mX; x >= 0; x = x - 1 { + t := m.Tiles[y][x] + // Remove spawns at edge of reflection + if x == mX && t.Type == spawn { + t, _ = NewTileFromString("neutral", t.Position) + m.Tiles[y][x] = t + } + + rY := mY - y + rRow := m.Tiles[rY] + + rX := len(rRow) + rp := Position{rX, rY} + + log.Debug("reflect", "o", t.Position, "r", rp) + rt, _ := NewTileFromString(t.Raw, rp) + m.Tiles[rY] = append(rRow, rt) + } + } + + // prepare spawns + spawns := []Position{ + Position{len(m.Tiles[0]) - 1, def.y - 1}, + Position{len(m.Tiles[0]) - 1, 0}, + } + + for i := 2; i <= def.players; i = i + 2 { + pos := spawns[i/2-1] + spawn, _ := NewTileFromString(fmt.Sprintf("spawn player %d", i), pos) + m.Tiles[pos.Y][pos.X] = spawn + } + + // TODO: prepare stores + // centerX := def.x + + return m +} + +func RandomMapFromDef(seed int64, def randomMapDef) *Map { + r := rand.New(rand.NewSource(seed)) + n := rand.Intn(len(mapGens)) + return mapGens[n](r, def) +} + +func RandomMap(seed int64, players int) *Map { + n := rand.Intn(10) + 3 + types := []TileType{neutral, neutral} + exclude := []TileType{spawn, docks, gate, wall, tower, farm} + for range n { + for { + t := TileType(rand.Intn(9) + 1) + if !slices.Contains(exclude, t) { + types = append(types, t) + break + } + } + } + + def := randomMapDef{ + types: types, + players: players, + stores: rand.Intn(2), + farms: rand.Intn(2) * players, + x: rand.Intn(6) + 6, + y: rand.Intn(6) + 6, + } + + return RandomMapFromDef(seed, def) +} diff --git a/go/ui/mapView.go b/go/ui/mapView.go index 36455d8c..29b1ddd3 100644 --- a/go/ui/mapView.go +++ b/go/ui/mapView.go @@ -4,7 +4,6 @@ import ( "fmt" "image" "image/color" - "log" "math" "strings" "unicode" @@ -13,6 +12,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/vector" "muhq.space/muhqs-game/go/assets" "muhq.space/muhqs-game/go/game" + "muhq.space/muhqs-game/go/log" ) var ( @@ -101,7 +101,7 @@ func (vw *MapView) handleStreet(x, y int, op *ebiten.DrawImageOptions) *ebiten.I // This street is not connected to anything. Seams odd! if connections == 0 { - log.Printf("Street at (%d, %d) is not connected", x, y) + log.Warn(fmt.Sprintf("Street at (%d, %d) is not connected", x, y)) img = assets.GetTile("street") } else if connections == 1 || (connections == 2 && ((left && right) || (above && below))) { @@ -149,7 +149,7 @@ func (vw *MapView) handleWall(x, y int, op *ebiten.DrawImageOptions) *ebiten.Ima // This wall is not connected to anything. Seams odd! if connections == 0 { - log.Printf("Wall at (%d, %d) is not connected", x, y) + log.Warn(fmt.Sprintf("Wall at (%d, %d) is not connected", x, y)) img = assets.GetTile("wall") } else if (connections == 1 || (connections == 2 && ((left && right) || (above && below)))) && @@ -259,7 +259,7 @@ func (vw *MapView) drawMapLayer(screen *ebiten.Image) { tileImg = assets.GetTile(tile.Raw) } if tileImg == nil { - log.Panic("failed to load tile", tile.Raw) + log.Panic("failed to load tile", "tile", tile.Raw) } op.GeoM.Translate(x_px, y_px) |
