aboutsummaryrefslogtreecommitdiff
path: root/go/ui
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-01-26 22:16:10 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2025-01-27 16:43:44 +0100
commit926ca5cdd20a39e3e05aaa3486cb166718dab5ca (patch)
tree3d7bc8a0e1e0be1ce8d92d5503bdede878e718e9 /go/ui
parent8720ebf9c74d9e11001772b5c00c293fa3a8b9e5 (diff)
downloadmuhqs-game-926ca5cdd20a39e3e05aaa3486cb166718dab5ca.tar.gz
muhqs-game-926ca5cdd20a39e3e05aaa3486cb166718dab5ca.zip
intermeditae commit
Diffstat (limited to 'go/ui')
-rw-r--r--go/ui/buffer.go4
-rw-r--r--go/ui/button.go14
-rw-r--r--go/ui/cardView.go46
-rw-r--r--go/ui/hand.go53
-rw-r--r--go/ui/mapView.go57
-rw-r--r--go/ui/stateBar.go40
-rw-r--r--go/ui/textBox.go133
-rw-r--r--go/ui/widget.go1
8 files changed, 289 insertions, 59 deletions
diff --git a/go/ui/buffer.go b/go/ui/buffer.go
index e0e6b001..c451d7fc 100644
--- a/go/ui/buffer.go
+++ b/go/ui/buffer.go
@@ -31,12 +31,12 @@ func (b *Buffer) render() *ebiten.Image {
img.Fill(BUFFER_BACKGROUND)
y := 0
for _, line := range b.lines[b.pos:] {
- bounds := text.BoundString(font.Font, line)
+ bounds := text.BoundString(font.Font18, line)
y += bounds.Dy()
if y > b.Height {
break
}
- text.Draw(img, line, font.Font, 0, y, BUFFER_FOREGROUND)
+ text.Draw(img, line, font.Font18, 0, y, BUFFER_FOREGROUND)
}
return img
}
diff --git a/go/ui/button.go b/go/ui/button.go
index 97dbad22..6454ccac 100644
--- a/go/ui/button.go
+++ b/go/ui/button.go
@@ -32,9 +32,19 @@ func NewSimpleButton(x, y, w, h int, label string, onClick func(b *SimpleButton)
func (b *SimpleButton) render() *ebiten.Image {
img := ebiten.NewImage(b.Width, b.Height)
- // b := text.BoundString(f, label)
ebitenutil.DrawRect(img, float64(0), float64(0), float64(b.Width), float64(b.Height), b.BG)
- text.Draw(img, b.label, font.Font, 20, 25, b.FG)
+
+ bounds := text.BoundString(font.Font24, b.label)
+ x := (b.Width - bounds.Dx()) / 2
+ if x < 0 {
+ x = 0
+ }
+
+ y := b.Height - bounds.Dy()/2
+ if y < 0 {
+ y = 0
+ }
+ text.Draw(img, b.label, font.Font24, x, y, b.FG)
return img
}
diff --git a/go/ui/cardView.go b/go/ui/cardView.go
new file mode 100644
index 00000000..a1c004c5
--- /dev/null
+++ b/go/ui/cardView.go
@@ -0,0 +1,46 @@
+package ui
+
+import (
+ "github.com/hajimehoshi/ebiten/v2"
+ "muhq.space/muhqs-game/go/assets"
+)
+
+type CardView struct {
+ WidgetBase
+ cardPath string
+}
+
+func NewScaledCardView(x, y int, width, height int, cardPath string) *CardView {
+ cv := &CardView{NewWidgetBase(x, y, width, height), cardPath}
+ cv.renderImpl = func() *ebiten.Image { return cv.render() }
+ return cv
+}
+
+func NewAutoCardView(x, y int, cardPath string) *CardView {
+ cv := &CardView{NewWidgetBase(x, y, -1, -1), cardPath}
+ cv.renderImpl = func() *ebiten.Image { return cv.render() }
+ return cv
+}
+
+func (cv *CardView) render() *ebiten.Image {
+ card := assets.GetCard(cv.cardPath, "en")
+ op := &ebiten.DrawImageOptions{}
+
+ b := card.Bounds()
+ if cv.Width == -1 && cv.Height == -1 {
+ cv.Width = b.Dx()
+ cv.Height = b.Dy()
+ } else {
+ x_scale := float64(cv.Width) / float64(b.Dx())
+ y_scale := float64(cv.Height) / float64(b.Dy())
+ scale := x_scale
+ if y_scale < x_scale {
+ scale = y_scale
+ }
+ op.GeoM.Scale(scale, scale)
+ }
+
+ img := ebiten.NewImage(cv.Width, cv.Height)
+ img.DrawImage(card, op)
+ return img
+}
diff --git a/go/ui/hand.go b/go/ui/hand.go
index b07d177b..5421f6fb 100644
--- a/go/ui/hand.go
+++ b/go/ui/hand.go
@@ -6,6 +6,8 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"muhq.space/muhqs-game/go/assets"
"muhq.space/muhqs-game/go/game"
+
+ "golang.org/x/exp/slices"
)
type HandCard struct {
@@ -14,13 +16,24 @@ type HandCard struct {
type HandView struct {
WidgetBase
- hand *game.Hand
- cardWidth float64
- scale float64
+ hand *game.Hand
+ cardWidth float64
+ cardHeight float64
+ scale float64
+ bottomAligned bool
+ highlights []*game.Card
}
-func NewHandView(x, y, width, height int, h *game.Hand) *HandView {
- hv := &HandView{NewWidgetBase(x, y, width, height), h, 0, 0}
+func NewHandView(x, y, width, height int, h *game.Hand, bottomAligned bool) *HandView {
+ hv := &HandView{
+ WidgetBase: NewWidgetBase(x, y, width, height),
+ hand: h,
+ cardWidth: 0,
+ cardHeight: 0,
+ scale: 0,
+ bottomAligned: bottomAligned,
+ highlights: []*game.Card{},
+ }
hv.renderImpl = func() *ebiten.Image { return hv.render() }
return hv
}
@@ -44,11 +57,22 @@ func (hv *HandView) render() *ebiten.Image {
hv.scale = y_scale
}
hv.cardWidth = float64(b.Dx()) * hv.scale
+ hv.cardHeight = float64(b.Dy()) * hv.scale
+ }
+
+ yOffset := 0.0
+ if hv.bottomAligned {
+ yOffset = float64(hv.Height) - hv.cardHeight
}
op := &ebiten.DrawImageOptions{}
op.GeoM.Scale(hv.scale, hv.scale)
- op.GeoM.Translate(float64(i)*hv.cardWidth, 0)
+ op.GeoM.Translate(float64(i)*hv.cardWidth, yOffset)
+
+ if slices.Contains(hv.highlights, card) {
+ op.ColorM.Scale(1, 0, 0, 0.5)
+ }
+
img.DrawImage(cardImg, op)
}
return img
@@ -59,6 +83,10 @@ func (hv *HandView) FindObjectAt(x, y int) interface{} {
return nil
}
+ if hv.bottomAligned && float64(y-hv.Y) < float64(hv.Height)-hv.cardHeight {
+ return nil
+ }
+
i := int(float64(x) / hv.cardWidth)
if i >= hv.hand.Size() {
return nil
@@ -66,3 +94,16 @@ func (hv *HandView) FindObjectAt(x, y int) interface{} {
return HandCard{hv.hand.GetCards()[i]}
}
+
+func (hv *HandView) setHighlights(cards []*game.Card) {
+ hv.highlights = cards
+ hv.ForceRedraw()
+}
+
+func (hv *HandView) HighlightCard(card *game.Card) {
+ hv.setHighlights([]*game.Card{card})
+}
+
+func (hv *HandView) ClearHighlights() {
+ hv.setHighlights([]*game.Card{})
+}
diff --git a/go/ui/mapView.go b/go/ui/mapView.go
index 6c887920..c966f1a8 100644
--- a/go/ui/mapView.go
+++ b/go/ui/mapView.go
@@ -132,20 +132,25 @@ func (vw *MapView) drawMapLayer(screen *ebiten.Image) {
screen.DrawImage(vw.mapLayer, &ebiten.DrawImageOptions{})
}
+func getPermanentSymbol(p game.Permanent, i int) *ebiten.Image {
+ permanentSymbol := assets.GetSymbol(strings.ToLower(p.GetCard().Name))
+ if permanentSymbol == nil {
+ symbol := fmt.Sprintf("%c%d", unicode.ToUpper(rune(p.GetCard().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 := assets.GetSymbol(strings.ToLower(p.GetCard().Name))
- if permanentSymbol == nil {
- symbol := fmt.Sprintf("%c%d", unicode.ToUpper(rune(p.GetCard().Type.String()[0])), i)
- permanentSymbol = assets.GetGenericSymbol(symbol)
- if permanentSymbol == nil {
- log.Fatalf("Failed to generate generic symbol %s", symbol)
- }
- }
-
+ permanentSymbol := getPermanentSymbol(p, i)
t := p.GetTile()
x_px := t.Position.X*TILE_WIDTH + (TILE_WIDTH-PERMANENT_WIDTH)/2
y_px := t.Position.Y*TILE_HEIGHT + (TILE_HEIGHT-PERMANENT_HEIGHT)/2
@@ -178,19 +183,37 @@ func (vw *MapView) ForceRedraw() {
vw.permanentsLayer = nil
}
-func (vw *MapView) FindObjectAt(x_px, y_px int) interface{} {
- x := x_px / TILE_WIDTH
- y := y_px / TILE_HEIGHT
+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
}
- // TODO: detect if a permanent or the containing tile was selected
- for _, u := range vw.gameState.Permanents {
- pos := u.GetTile().Position
- if pos.X == x && pos.Y == y {
- return u
+ 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.GetTile().Position
+ if pos.X == x && pos.Y == y {
+ if isSymbolTransparentAt(p, relativeX, relativeY) {
+ break
+ }
+
+ return p
+ }
}
}
diff --git a/go/ui/stateBar.go b/go/ui/stateBar.go
index d7d86ea9..6914de6e 100644
--- a/go/ui/stateBar.go
+++ b/go/ui/stateBar.go
@@ -7,16 +7,12 @@ import (
"muhq.space/muhqs-game/go/game"
)
-const (
- PHASE_VIEW_WIDTH = 250
-)
-
type StateBar struct {
X, Y int
Width, Height int
gameState *game.State
activePlayer *game.Player
- ressourceView *TextBox
+ resourceView *TextBox
phaseView *TextBox
}
@@ -25,25 +21,25 @@ func NewStateBar(x, y int, width, height int, s *game.State, p *game.Player) *St
return sb
}
-func (sb *StateBar) ressourceLabel() string {
- return fmt.Sprintf("%d", sb.activePlayer.Ressource)
+func (sb *StateBar) resourceLabel() string {
+ return fmt.Sprintf("%d", sb.activePlayer.Resource)
}
func (sb *StateBar) phaseLabel() string {
- return fmt.Sprintf("%s: %s", sb.gameState.Players[sb.gameState.ActivePlayerId].Name,
+ return fmt.Sprintf("%s: %s", sb.gameState.ActivePlayer.Name,
sb.gameState.ActivePhase.String())
}
func (sb *StateBar) Draw(screen *ebiten.Image) {
- if sb.ressourceView == nil {
- sb.ressourceView = NewFixedTextBox(sb.X, sb.Y, sb.Height, sb.Height,
- sb.ressourceLabel())
+ if sb.resourceView == nil {
+ sb.resourceView = NewCenteringFixedTextBox(sb.X, sb.Y, sb.Height, sb.Height,
+ sb.resourceLabel())
}
- sb.ressourceView.Draw(screen)
+ sb.resourceView.Draw(screen)
if sb.phaseView == nil {
- sb.phaseView = NewFixedTextBox(sb.X+sb.ressourceView.Width, sb.Y,
- PHASE_VIEW_WIDTH, sb.Height, sb.phaseLabel())
+ sb.phaseView = NewFixedTextBox(sb.X+sb.resourceView.Width, sb.Y,
+ sb.Width-sb.resourceView.Width, sb.Height, sb.phaseLabel(), false)
}
sb.phaseView.Draw(screen)
}
@@ -53,9 +49,17 @@ func (sb *StateBar) FindObjectAt(x, y int) interface{} {
}
func (sb *StateBar) ForceRedraw() {
- sb.ressourceView.Text = sb.ressourceLabel()
- sb.ressourceView = nil
+ if sb.resourceView != nil {
+ sb.resourceView.Text = sb.resourceLabel()
+ sb.resourceView.ForceRedraw()
+ }
+
+ if sb.phaseView != nil {
+ sb.phaseView.Text = sb.phaseLabel()
+ sb.phaseView.ForceRedraw()
+ }
+}
- sb.phaseView.Text = sb.phaseLabel()
- sb.phaseView = nil
+func (sb *StateBar) Contains(x, y int) bool {
+ return sb.resourceView.Contains(x, y) || sb.phaseView.Contains(x, y)
}
diff --git a/go/ui/textBox.go b/go/ui/textBox.go
index 0cd09294..dd10e8e2 100644
--- a/go/ui/textBox.go
+++ b/go/ui/textBox.go
@@ -11,30 +11,60 @@ import (
)
var (
- TEXTBOX_BACKGROUND = gray
- TEXTBOX_FOREGROUND = color.White
+ TEXTBOX_DEFAULT_BACKGROUND = gray
+ TEXTBOX_DEFAULT_FOREGROUND = color.White
- TEXTBOX_AUTO_BORDER = 3
+ TEXTBOX_DEFAULT_MARGIN = 3
)
type TextBox struct {
WidgetBase
- Text string
+ Text string
+ Fg color.Color
+ Bg color.Color
+ XMargin int
+ YMargin int
+ Center bool
}
-func NewFixedTextBox(x, y int, width, height int, t string) *TextBox {
- tb := &TextBox{NewWidgetBase(x, y, width, height), t}
+func NewFixedTextBox(x, y int, width, height int, t string, center bool) *TextBox {
+ tb := &TextBox{
+ WidgetBase: NewWidgetBase(x, y, width, height),
+ Text: t,
+ Fg: TEXTBOX_DEFAULT_FOREGROUND,
+ Bg: TEXTBOX_DEFAULT_BACKGROUND,
+ XMargin: TEXTBOX_DEFAULT_MARGIN,
+ YMargin: TEXTBOX_DEFAULT_MARGIN,
+ Center: center,
+ }
tb.renderImpl = func() *ebiten.Image { return tb.render() }
return tb
}
+func NewCenteringFixedTextBox(x, y int, width, height int, t string) *TextBox {
+ return NewFixedTextBox(x, y, width, height, t, true)
+}
+
func NewAutoTextBox(x, y int, t string) *TextBox {
- return NewFixedTextBox(x, y, -1, -1, t)
+ return NewFixedTextBox(x, y, -1, -1, t, false)
+}
+
+func NewCenteringAutoTextBox(x, y int, t string) *TextBox {
+ return NewFixedTextBox(x, y, -1, -1, t, true)
}
func NewUnitInfo(x, y int, u *game.Unit) *TextBox {
- info := fmt.Sprintf("%s\nDamage: %d\nHealth: %d\nMovement: %s\nAttack %s\nUpkeep: %d",
- u.String(), u.GetDamage(), u.Health, u.Movement.String(), u.Attack.String(), u.Upkeep)
+ info := fmt.Sprintf("%s\nDamage: %d\nHealth: %d\nUpkeep: %d",
+ u.String(), u.GetDamage(), u.Health, u.Upkeep)
+
+ if u.Movement != game.INVALID_MOVEMENT() {
+ info = fmt.Sprintf("%s\nMovement: %s", info, u.Movement.String())
+ }
+
+ if u.Attack != game.INVALID_ATTACK() {
+ info = fmt.Sprintf("%s\nAttack: %s", info, u.Attack.String())
+ }
+
return NewAutoTextBox(x, y, info)
}
@@ -45,13 +75,88 @@ func NewPermInfo(x, y int, p game.Permanent) *TextBox {
}
func (tb *TextBox) render() *ebiten.Image {
- b := text.BoundString(font.Font, tb.Text)
+ var x, y int
+ b := text.BoundString(font.Font24, tb.Text)
if tb.Width == -1 || tb.Height == -1 {
- tb.Width = b.Dx() + 2*TEXTBOX_AUTO_BORDER
- tb.Height = b.Dy() + 2*TEXTBOX_AUTO_BORDER
+ tb.Width = b.Dx() + 2*tb.XMargin
+ tb.Height = b.Dy() + 2*tb.YMargin
+ x = tb.XMargin + b.Min.X
+ y = tb.YMargin - b.Min.Y
+ } else {
+ if !tb.Center {
+ x = tb.XMargin + b.Min.X
+ } else {
+ x = (tb.Width - b.Dx()) / 2
+ if x < 0 {
+ x = 0
+ }
+ }
+
+ y = tb.Height - b.Dy()/2
+ if y < 0 {
+ y = 0
+ }
}
+
img := ebiten.NewImage(tb.Width, tb.Height)
- img.Fill(TEXTBOX_BACKGROUND)
- text.Draw(img, tb.Text, font.Font, TEXTBOX_AUTO_BORDER+b.Min.X, TEXTBOX_AUTO_BORDER-b.Min.Y, TEXTBOX_FOREGROUND)
+ img.Fill(tb.Bg)
+ text.Draw(img, tb.Text, font.Font24, x, y, tb.Fg)
return img
}
+
+type PocList struct {
+ TextBox
+ centerX int
+ bottomY int
+ poc game.PileOfCards
+}
+
+func NewPocList(centerX, bottomY int, poc game.PileOfCards) *PocList {
+ w := &PocList{
+ TextBox: TextBox{
+ WidgetBase: NewWidgetBase(-1, -1, -1, -1),
+ Text: "",
+ Fg: TEXTBOX_DEFAULT_FOREGROUND,
+ Bg: TEXTBOX_DEFAULT_BACKGROUND,
+ XMargin: TEXTBOX_DEFAULT_MARGIN,
+ YMargin: TEXTBOX_DEFAULT_MARGIN,
+ Center: true,
+ },
+ centerX: centerX,
+ bottomY: bottomY,
+ poc: poc,
+ }
+ w.renderImpl = func() *ebiten.Image {
+ w.setText()
+ return w.render()
+ }
+ return w
+}
+
+func (w *PocList) setText() {
+ t := ""
+ for _, c := range w.poc.GetCards() {
+ t = fmt.Sprintf("%s%s\n", t, c.Name)
+ }
+
+ if t != "" {
+ w.Text = t[:len(t)-1]
+ } else {
+ w.Text = "No cards"
+ }
+
+ b := text.BoundString(font.Font24, w.Text)
+ w.X = w.centerX - b.Dx()/2 - w.XMargin
+ w.Y = w.bottomY - b.Dy() - w.YMargin
+}
+
+func (w *PocList) FindObjectAt(x, y int) interface{} {
+ cards := w.poc.GetCards()
+ if len(cards) == 0 {
+ return nil
+ }
+
+ // b := text.BoundString(font.Font, w.Text)
+ // i := b.Dy() / w.poc.Size()
+ return cards[0]
+}
diff --git a/go/ui/widget.go b/go/ui/widget.go
index d783eb36..cfcdd766 100644
--- a/go/ui/widget.go
+++ b/go/ui/widget.go
@@ -7,6 +7,7 @@ import (
type Widget interface {
Draw(*ebiten.Image)
FindObjectAt(int, int) interface{}
+ Contains(int, int) bool
}
type WidgetBase struct {