aboutsummaryrefslogtreecommitdiff
path: root/go/ui/mapView.go
diff options
context:
space:
mode:
Diffstat (limited to 'go/ui/mapView.go')
-rw-r--r--go/ui/mapView.go271
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
+}