diff options
Diffstat (limited to 'go/ui/mapView.go')
| -rw-r--r-- | go/ui/mapView.go | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/go/ui/mapView.go b/go/ui/mapView.go new file mode 100644 index 00000000..8c83bd41 --- /dev/null +++ b/go/ui/mapView.go @@ -0,0 +1,271 @@ +package ui + +import ( + "fmt" + "log" + "math" + "strings" + "unicode" + + "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 (mv *MapView) Height() int { + return len(mv.gameState.Map.Tiles) * TILE_HEIGHT +} + +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: x, Y: 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 getPermanentSymbol(p game.Permanent, i int) *ebiten.Image { + permanentSymbol := assets.GetSymbol(strings.ToLower(p.Card().Name)) + if permanentSymbol == nil { + symbol := fmt.Sprintf("%c%d", unicode.ToUpper(rune(p.Card().Type.String()[0])), i) + permanentSymbol = assets.GetGenericSymbol(symbol) + if permanentSymbol == nil { + log.Fatalf("Failed to generate generic symbol %s", symbol) + } + } + + return permanentSymbol +} + +func (vw *MapView) drawPermanentsLayer(screen *ebiten.Image) { + if vw.permanentsLayer == nil { + vw.permanentsLayer = vw.newLayerImage() + + for i, p := range vw.gameState.Permanents { + permanentSymbol := getPermanentSymbol(p, i) + t := p.Tile() + x_px := t.Position.X*TILE_WIDTH + (TILE_WIDTH-PERMANENT_WIDTH)/2 + y_px := t.Position.Y*TILE_HEIGHT + (TILE_HEIGHT-PERMANENT_HEIGHT)/2 + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(float64(x_px), float64(y_px)) + op.ColorM.ScaleWithColor(p.Controller().Color) + + for _, h := range vw.permanentsHighlights { + if p == h { + op.ColorM.Scale(255, 0.5, 0.5, 1) + break + } + } + + 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 isSymbolTransparentAt(p game.Permanent, relativeX, relativeY int) bool { + permanentSymbol := getPermanentSymbol(p, 0) + _, _, _, alpha := permanentSymbol.At(relativeX, relativeY).RGBA() + return alpha == 0 +} + +func (vw *MapView) FindObjectAt(screenX, screenY int) interface{} { + x := screenX / TILE_WIDTH + y := screenY / TILE_HEIGHT + + if x < 0 || y < 0 { + return nil + } + + relativeX := screenX % TILE_WIDTH + relativeY := screenY % TILE_HEIGHT + xMargin := (TILE_WIDTH - PERMANENT_WIDTH) / 2 + yMargin := (TILE_HEIGHT - PERMANENT_HEIGHT) / 2 + // detect if a permanent or the containing tile was selected + if relativeX >= xMargin && relativeX <= TILE_WIDTH-xMargin && + relativeY >= yMargin && relativeY <= TILE_HEIGHT-yMargin { + + for _, p := range vw.gameState.Permanents { + pos := p.Tile().Position + if pos.X == x && pos.Y == y { + if isSymbolTransparentAt(p, relativeX, relativeY) { + break + } + + return p + } + } + } + + 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.tileHighlights = pos + vw.ForceRedraw() +} + +func (vw *MapView) AddHighlightPosition(pos game.Position) { + vw.HighlightPositions(append(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) AddHighlightTile(t *game.Tile) { + vw.AddHighlightPosition(t.Position) +} + +func (vw *MapView) ClearTileHighlights() { + vw.HighlightTiles([]*game.Tile{}) +} + +func (vw *MapView) HighlightPermanents(permanents []game.Permanent) { + vw.permanentsHighlights = permanents + vw.ForceRedraw() +} + +func (vw *MapView) HighlightPermanent(p game.Permanent) { + vw.HighlightPermanents([]game.Permanent{p}) +} + +func (vw *MapView) ClearPermanentsHighlights() { + vw.HighlightPermanents([]game.Permanent{}) +} + +func (*MapView) Render(Widget) *ebiten.Image { + return nil +} |
