diff options
Diffstat (limited to 'go/ui/mapView.go')
| -rw-r--r-- | go/ui/mapView.go | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/go/ui/mapView.go b/go/ui/mapView.go new file mode 100644 index 00000000..10929f68 --- /dev/null +++ b/go/ui/mapView.go @@ -0,0 +1,212 @@ +package ui + +import ( + "log" + "math" + "strings" + + "golang.org/x/exp/slices" + + "github.com/hajimehoshi/ebiten/v2" + "muhq.space/muhqs-game/go/assets" + "muhq.space/muhqs-game/go/game" +) + +const ( + PERMANENT_WIDTH int = 40 + PERMANENT_HEIGHT int = 40 + + TILE_WIDTH int = 50 + TILE_HEIGHT int = 50 +) + +type MapView struct { + gameState *game.State + mapLayer *ebiten.Image + permanentsLayer *ebiten.Image + tileHighlights []game.Position + permanentsHighlights []*game.Permanent +} + +func NewMapView(g *game.State) *MapView { + vw := MapView{g, nil, nil, []game.Position{}, []*game.Permanent{}} + return &vw +} + +func rotateStreetImg(x, y int, radian float64, op *ebiten.DrawImageOptions) { + // Move the image's center to the screen's upper-left corner. + // This is a preparation for rotating. When geometry matrices are applied, + // the origin point is the upper-left corner. + op.GeoM.Translate(-float64(TILE_WIDTH)/2, -float64(TILE_HEIGHT)/2) + + // Rotate the image. As a result, the anchor point of this rotate is + // the center of the image. + op.GeoM.Rotate(radian) + + // Reset translation + op.GeoM.Translate(float64(TILE_WIDTH)/2, float64(TILE_HEIGHT)/2) +} + +func (vw *MapView) rotateStreet(x, y int, op *ebiten.DrawImageOptions) { + connections, left, right, above, below := vw.gameState.Map.FindStreetConnections(x, y) + + if connections == 0 { + // This street is not connected to another street -> + // check any other non neutral tiles + connections, left, right, above, below = vw.gameState.Map.FindAnyConnections(x, y) + } + + // This street is not connected to anything. Seams odd! + if connections == 0 { + log.Printf("Street at (%d, %d) is not connected", x, y) + } else if (connections == 1 || + (connections == 2 && ((left && right) || (above && below)))) && + (above || below) { + rotateStreetImg(x, y, math.Pi/2, op) + } else if connections == 2 { + // normal orientation above and right + if right && below { + rotateStreetImg(x, y, 3*math.Pi/2, op) + } else if left && below { + rotateStreetImg(x, y, math.Pi, op) + } else if above && left { + rotateStreetImg(x, y, math.Pi/2, op) + } + } else if connections == 3 { + // normal orientation left above right + if above && right && below { + rotateStreetImg(x, y, 3*math.Pi/2, op) + } else if left && below && right { + rotateStreetImg(x, y, math.Pi, op) + } else if below && left && above { + rotateStreetImg(x, y, math.Pi/2, op) + } + } +} + +func (vw *MapView) newLayerImage() *ebiten.Image { + // TODO: support non symetric maps + maxWidth := len(vw.gameState.Map.Tiles[0]) * TILE_WIDTH + maxHeight := len(vw.gameState.Map.Tiles) * TILE_HEIGHT + return ebiten.NewImage(maxWidth, maxHeight) +} + +func (vw *MapView) drawMapLayer(screen *ebiten.Image) { + if vw.mapLayer == nil { + vw.mapLayer = vw.newLayerImage() + + x_px, y_px := 0.0, 0.0 + for y := 0; y < len(vw.gameState.Map.Tiles); y++ { + for x := 0; x < len(vw.gameState.Map.Tiles[y]); x++ { + pos := game.Position{x, y} + tile := vw.gameState.Map.TileAt(pos) + tileImg := assets.GetTile(tile.Raw) + + op := &ebiten.DrawImageOptions{} + // Rotate Street + if tile.Type == game.TileTypes.Street { + vw.rotateStreet(x, y, op) + } + + op.GeoM.Translate(x_px, y_px) + + if slices.Contains(vw.tileHighlights, pos) { + op.ColorM.Scale(1, 0, 0, 0.5) + } + + vw.mapLayer.DrawImage(tileImg, op) + + x_px += float64(TILE_WIDTH) + } + x_px = 0 + y_px += float64(TILE_HEIGHT) + } + } + + screen.DrawImage(vw.mapLayer, &ebiten.DrawImageOptions{}) +} + +func (vw *MapView) drawPermanentsLayer(screen *ebiten.Image) { + if vw.permanentsLayer == nil { + vw.permanentsLayer = vw.newLayerImage() + + for _, p := range vw.gameState.Permanents { + permanentSymbol := assets.GetSymbol(strings.ToLower(p.Card.Name)) + // TODO: Implement generic symbols + if permanentSymbol == nil { + continue + } + + op := &ebiten.DrawImageOptions{} + x_px := p.Tile.Position.X * TILE_WIDTH + (TILE_WIDTH - PERMANENT_WIDTH) / 2 + y_px := p.Tile.Position.Y * TILE_HEIGHT + (TILE_HEIGHT - PERMANENT_HEIGHT) / 2 + op.GeoM.Translate(float64(x_px), float64(y_px)) + vw.permanentsLayer.DrawImage(permanentSymbol, op) + } + } + + screen.DrawImage(vw.permanentsLayer, &ebiten.DrawImageOptions{}) +} + +func (vw *MapView) Draw(screen *ebiten.Image) { + vw.drawMapLayer(screen) + vw.drawPermanentsLayer(screen) +} + +func (vw *MapView) ForceRedraw() { + vw.mapLayer = nil + vw.permanentsLayer = nil +} + +func (vw *MapView) FindObjectAt(x_px, y_px int) interface{} { + x := x_px / TILE_WIDTH + y := y_px / TILE_HEIGHT + + // TODO: detect if a permanent or the containing tile was selected + for _, u := range vw.gameState.Units { + pos := u.Tile.Position + if pos.X == x && pos.Y == y { + return u + } + } + + if y < len(vw.gameState.Map.Tiles) && x < len(vw.gameState.Map.Tiles[y]) { + return &vw.gameState.Map.Tiles[y][x] + } + + return nil +} + +func (vw *MapView) HighlightPositions(pos []game.Position) { + vw.ForceRedraw() + vw.tileHighlights = pos +} + +func (vw *MapView) HighlightTiles(tiles []*game.Tile) { + pos := make([]game.Position, 0, len(tiles)) + for _, t := range tiles { + pos = append(pos, t.Position) + } + vw.HighlightPositions(pos) +} + +func (vw *MapView) HighlightTile(t *game.Tile) { + vw.HighlightTiles([]*game.Tile{t}) +} + +func (vw *MapView) ClearTileHighlights() { + vw.HighlightTiles([]*game.Tile{}) +} + +func (vw *MapView) HighlightPermanents(permanents []*game.Permanent) { + vw.ForceRedraw() + vw.permanentsHighlights = permanents +} + +func (vw *MapView) HighlightPermanent(p *game.Permanent) { + vw.HighlightPermanents([]*game.Permanent{p}) +} + +func (vw *MapView) ClearPermanentsHighlights() { + vw.HighlightPermanents([]*game.Permanent{}) +} |
