diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-02-16 18:57:15 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:50:10 +0200 |
| commit | 63ad22242562b1e66353694f2a17df6d1c64a8fd (patch) | |
| tree | f91a3a98e554e58a30d5bb0a34b021c7fb206311 /go/client | |
| parent | c0ab1110429be3a2a1664084469952ea082961c1 (diff) | |
| download | muhqs-game-63ad22242562b1e66353694f2a17df6d1c64a8fd.tar.gz muhqs-game-63ad22242562b1e66353694f2a17df6d1c64a8fd.zip | |
move more ui logic into Collection
* Generalize hover detection and move it into ui.Collection
* Implement transparencu for the prompt banner
Diffstat (limited to 'go/client')
| -rw-r--r-- | go/client/game.go | 228 | ||||
| -rw-r--r-- | go/client/main.go | 2 | ||||
| -rw-r--r-- | go/client/sealed.go | 45 | ||||
| -rw-r--r-- | go/client/startMenu.go | 23 |
4 files changed, 97 insertions, 201 deletions
diff --git a/go/client/game.go b/go/client/game.go index abd68378..8447e452 100644 --- a/go/client/game.go +++ b/go/client/game.go @@ -19,9 +19,7 @@ const ( RESOLVE_BUTTON_X = 500 STACK_BUFFER_WIDTH = 300 - HOVER_THRESHOLD = 60 - HOVER_CARD_WIDTH = 500 - HOVER_CARD_HEIGHT = 700 + HOVER_THRESHOLD = 60 HAND_VIEW_WIDTH = 500 @@ -30,45 +28,8 @@ const ( POC_BUTTON_WIDTH = 150 ) -// A simple structure to detect if the cursor has not moved for HOVER_THRESHOLD -// ticks. -// hoverDetector.update must be called during each Update invocation to track the -// frame and the current cursor position. -type hoverDetector struct { - last_x, last_y int - ticks int - // ResetFunc is called when the cursor has moved. - // If resetFunc returns true the hoverDetector is reset - // otherwise the reset is skipped. - resetFunc func() bool -} - -func (h *hoverDetector) update(x, y int) { - if x == h.last_x && y == h.last_y { - h.ticks++ - } else { - h.last_x, h.last_y = x, y - h.reset() - } -} - -func (h *hoverDetector) reset() { - if h.resetFunc != nil && !h.resetFunc() { - return - } - h.ticks = 0 - h.resetFunc = nil -} - -func (h *hoverDetector) startedHovering() bool { - return h.ticks == HOVER_THRESHOLD -} - type Game struct { - width, height int - selectedObject interface{} - hoverDetector hoverDetector handLayer *ui.HandView stateBar *ui.StateBar @@ -81,7 +42,7 @@ type Game struct { storeButton *ui.SimpleButton stackBuffer *ui.Buffer prompt *ui.Prompt - widgets []ui.Widget + ui.Collection activePlayer *game.Player @@ -94,15 +55,16 @@ type Game struct { func newGame(app *app) *Game { g := &Game{ - width: app.windowWidth, - height: app.windowHeight, + Collection: ui.Collection{ + Width: app.windowWidth, + Height: app.windowHeight, + }, } - g.widgets = []ui.Widget{} g.gameState = game.NewState() - g.passButton = ui.NewSimpleButton(g.width-PASS_BUTTON_WIDTH, - g.height-DEFAULT_BUTTON_HEIGHT, + g.passButton = ui.NewSimpleButton(g.Width-PASS_BUTTON_WIDTH, + g.Height-DEFAULT_BUTTON_HEIGHT, PASS_BUTTON_WIDTH, DEFAULT_BUTTON_HEIGHT, "Pass", @@ -110,7 +72,7 @@ func newGame(app *app) *Game { if g.prompt != nil { a := g.prompt.Confirm() g.declareAction(a) - g.removeWidget(g.prompt) + g.RemoveWidget(g.prompt) g.prompt = nil } else if g.hasPriority { @@ -118,7 +80,7 @@ func newGame(app *app) *Game { } }) - g.stackBuffer = ui.NewBuffer(g.width-STACK_BUFFER_WIDTH, g.passButton.Y-400, + g.stackBuffer = ui.NewBuffer(g.Width-STACK_BUFFER_WIDTH, g.passButton.Y-400, STACK_BUFFER_WIDTH, 400) return g } @@ -130,6 +92,7 @@ func (g *Game) loadMap(mapName string) *Game { } g.gameState.Map = m g.mapView = ui.NewMapView(g.gameState) + g.AddWidget(g.mapView) storeTiles := m.FilterTiles(func(t *game.Tile) bool { return t.Type == game.TileTypes.Store }) g.storesOnMap = len(storeTiles) > 0 @@ -163,56 +126,56 @@ func (g *Game) initPlayerUi(player *game.Player) *Game { g.handLayer = ui.NewHandView(0, g.mapView.Height(), HAND_VIEW_WIDTH, - g.height-g.mapView.Height()-DEFAULT_BUTTON_HEIGHT, + g.Height-g.mapView.Height()-DEFAULT_BUTTON_HEIGHT, player.Hand, true) - g.addWidget(g.handLayer) + g.AddWidget(g.handLayer) g.stateBar = ui.NewStateBar(0, - g.height-DEFAULT_BUTTON_HEIGHT, + g.Height-DEFAULT_BUTTON_HEIGHT, STATE_BAR_WIDTH, DEFAULT_BUTTON_HEIGHT, g.gameState, player) - g.addWidget(g.stateBar) + g.AddWidget(g.stateBar) g.discardPileButton = ui.NewSimpleButton(g.stateBar.Width, - g.height-DEFAULT_BUTTON_HEIGHT, + g.Height-DEFAULT_BUTTON_HEIGHT, POC_BUTTON_WIDTH, DEFAULT_BUTTON_HEIGHT, "DiscardPile", func(*ui.SimpleButton) { - if idx := g.findWidget(g.discardPileView); idx == -1 { + if idx := g.FindWidget(g.discardPileView); idx == -1 { g.discardPileView.ForceRedraw() - g.addWidget(g.discardPileView) + g.AddWidget(g.discardPileView) } else { - g.removeWidget(g.discardPileView) + g.RemoveWidget(g.discardPileView) } }) - g.addWidget(g.discardPileButton) + g.AddWidget(g.discardPileButton) x = g.discardPileButton.X + g.discardPileButton.Width/2 - y = g.height - DEFAULT_BUTTON_HEIGHT + y = g.Height - DEFAULT_BUTTON_HEIGHT g.discardPileView = ui.NewPocList(x, y, g.activePlayer.DiscardPile) if !g.storesOnMap { x = g.stateBar.Width + g.discardPileButton.Width - y = g.height - DEFAULT_BUTTON_HEIGHT + y = g.Height - DEFAULT_BUTTON_HEIGHT g.storeButton = ui.NewSimpleButton(x, y, POC_BUTTON_WIDTH, DEFAULT_BUTTON_HEIGHT, "Store", func(*ui.SimpleButton) { - if idx := g.findWidget(g.storeView); idx == -1 { + if idx := g.FindWidget(g.storeView); idx == -1 { g.showStore() } else { g.hideStore() } }) - g.addWidget(g.storeButton) + g.AddWidget(g.storeButton) x = g.storeButton.X + g.storeButton.Width/2 - y = g.height - DEFAULT_BUTTON_HEIGHT + y = g.Height - DEFAULT_BUTTON_HEIGHT g.storeView = ui.NewPocList(x, y, g.activePlayer.Store) } @@ -221,38 +184,11 @@ func (g *Game) initPlayerUi(player *game.Player) *Game { func (g *Game) showStore() { g.storeView.ForceRedraw() - g.addWidget(g.storeView) + g.AddWidget(g.storeView) } func (g *Game) hideStore() { - g.removeWidget(g.storeView) -} - -func (g *Game) addWidget(w ui.Widget) { - g.widgets = append(g.widgets, w) -} - -func (g *Game) findWidget(widget ui.Widget) int { - for i, w := range g.widgets { - if w != widget { - continue - } - return i - } - return -1 -} - -func (g *Game) removeWidget(widget ui.Widget) { - idx := g.findWidget(widget) - if idx != -1 { - g.removeWidgetAt(idx) - } -} - -func (g *Game) removeWidgetAt(idx int) { - nwidgets := len(g.widgets) - g.widgets[idx] = g.widgets[nwidgets-1] - g.widgets = g.widgets[:nwidgets-1] + g.RemoveWidget(g.storeView) } func (g *Game) addPermActionChoice(perm game.Permanent, x, y int) { @@ -276,7 +212,7 @@ func (g *Game) addPermActionChoice(perm game.Permanent, x, y int) { g.addActionChoice(p, x, y) } g.choice = ui.NewPermChoice(x, y, perms, onClick) - g.addWidget(g.choice) + g.AddWidget(g.choice) } else { g.selectedObject = perms[0] g.addActionChoice(perms[0], x, y) @@ -297,12 +233,12 @@ func (g *Game) addActionChoice(perm game.Permanent, x, y int) { } } g.choice = ui.NewActionChoice(x, y, actions, onClick) - g.addWidget(g.choice) + g.AddWidget(g.choice) } } func (g *Game) removeChoice() { - g.removeWidget(g.choice) + g.RemoveWidget(g.choice) g.choice = nil } @@ -337,11 +273,11 @@ func (g *Game) declareAction(a game.Action) { } func (g *Game) showPassButton() { - g.addWidget(g.passButton) + g.AddWidget(g.passButton) } func (g *Game) hidePassButton() { - g.removeWidget(g.passButton) + g.RemoveWidget(g.passButton) } func (g *Game) passPriority() { @@ -363,10 +299,16 @@ func (g *Game) handlePlayerNotifications() { } a := n.Context.(game.Action) g.stackBuffer.AddLine(a.String()) + if !g.gameState.Stack.IsEmpty() && g.FindWidget(g.stackBuffer) == -1 { + g.AddWidget(g.stackBuffer) + } case game.ResolvedActionNotification: g.clearMapHighlights() g.stackBuffer.RemoveLast() + if g.gameState.Stack.IsEmpty() { + g.RemoveWidget(g.stackBuffer) + } g.mapView.ForceRedraw() case game.TargetSelectionPrompt: @@ -377,8 +319,8 @@ func (g *Game) handlePlayerNotifications() { } } - g.prompt = ui.NewPrompt(g.height/2, g.width, ctx.Action, ctx.Prompt) - g.addWidget(g.prompt) + g.prompt = ui.NewPrompt(g.Height/2, g.Width, ctx.Action, ctx.Prompt) + g.AddWidget(g.prompt) g.showPassButton() } @@ -392,8 +334,9 @@ func (g *Game) handlePlayerNotifications() { func (g *Game) findObjectAt(x, y int) interface{} { // Iterate the widget in reverse order to ensure that objects are found // in the newer widget possibly drawn over older ones - for i := len(g.widgets) - 1; i >= 0; i-- { - w := g.widgets[i] + widgets := g.Widgets() + for i := len(widgets) - 1; i >= 0; i-- { + w := widgets[i] if obj := w.FindObjectAt(x, y); obj != nil { return obj } @@ -428,9 +371,6 @@ func (g *Game) handleSelection(obj interface{}, x, y int) { g.handleTargetSelection(obj) } else { switch obj := obj.(type) { - case ui.Button: - obj.Click(x, y) - case *game.Tile: if g.storesOnMap && obj.Type == game.TileTypes.Store && g.activePlayer.KnowsStore(obj.Position) { if g.storeView != nil { @@ -467,72 +407,17 @@ func (g *Game) highlightTargets() { } } -func (g *Game) handleHover(obj interface{}, x, y int) { - var hoverHint ui.Widget - switch obj := obj.(type) { - case *game.Card: - wx, wy := x, y - if x+HOVER_CARD_WIDTH > g.width || y+HOVER_CARD_HEIGHT > g.height { - wx = x - HOVER_CARD_WIDTH - wy = y - HOVER_CARD_HEIGHT - } - if wx < 0 { - wx = 0 - } - if wy < 0 { - wy = 0 - } - hoverHint = ui.NewScaledCardView(wx, wy, HOVER_CARD_WIDTH, HOVER_CARD_HEIGHT, obj.Path()) - case *game.Unit: - hoverHint = ui.NewUnitInfo(x+ui.TILE_WIDTH, y, obj) - case game.Permanent: - hoverHint = ui.NewPermInfo(x+ui.TILE_WIDTH, y, obj) - // case *game.Tile: - // if g.storesOnMap && obj.Type == game.TileTypes.Store { - // poc := g.gameState.Map.StoreOn(obj.Position) - // hoverHint = ui.NewPocList(x, y, poc) - // // Set a special hoverDetector resetFunc keeping the hovering store - // // around and allow card selection until the cursor leaves the store widget. - // g.hoverDetector.resetFunc = func() bool { - // x, y := ebiten.CursorPosition() - // if hoverHint.Contains(x, y) { - // return false - // } - // g.removeWidget(hoverHint) - // return true - // } - // } - } - - if hoverHint != nil { - g.addWidget(hoverHint) - // Set the default reset function removing the hoverHint - if g.hoverDetector.resetFunc == nil { - g.hoverDetector.resetFunc = func() bool { - g.removeWidget(hoverHint) - return true - } - } - } -} - func (g *Game) Update() error { g.handlePlayerNotifications() + if err := g.Collection.Update(); err != nil { + return nil + } + x, y := ebiten.CursorPosition() - g.hoverDetector.update(x, y) obj := g.findObjectAt(x, y) - scrollX, scrollY := ebiten.Wheel() - if scrollX != 0 || scrollY != 0 { - for _, w := range g.widgets { - if scrollable, ok := w.(ui.Scrollable); ok && w.Contains(x, y) { - scrollable.Scroll(int(scrollX), int(scrollY)) - } - } - } - g.highlightTargets() if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { @@ -546,36 +431,31 @@ func (g *Game) Update() error { } else { g.handleSelection(obj, x, y) } - } else if g.hoverDetector.startedHovering() && g.selectedObject == nil && obj != nil { - g.handleHover(obj, x, y) } else if inpututil.IsKeyJustPressed(ebiten.KeySpace) { g.passButton.Click(0, 0) } else if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { g.removeChoice() g.selectedObject = nil - g.hoverDetector.reset() + g.ResetHover() g.clearMapHighlights() g.handLayer.ClearHighlights() g.hideStore() - g.removeWidget(g.discardPileView) + g.RemoveWidget(g.discardPileView) + if g.prompt != nil { + g.prompt.ClearSelections() + } } return nil } func (g *Game) Draw(screen *ebiten.Image) { - g.mapView.Draw(screen) - if !g.gameState.Stack.IsEmpty() { - g.stackBuffer.Draw(screen) - } - for _, b := range g.widgets { - b.Draw(screen) - } + g.Collection.Draw(screen) ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f FPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())) } func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { - return g.width, g.height + return g.Width, g.Height } func (g *Game) Start() *Game { diff --git a/go/client/main.go b/go/client/main.go index 2b7496ad..82b60a40 100644 --- a/go/client/main.go +++ b/go/client/main.go @@ -70,7 +70,7 @@ func main() { ebiten.SetWindowSize(app.windowWidth, app.windowHeight) ebiten.SetWindowTitle("Muhq's Game") - startMenu.width, startMenu.height = app.windowWidth, app.windowHeight + startMenu.Width, startMenu.Height = app.windowWidth, app.windowHeight if startDeckPath != "" { file, err := os.Open(startDeckPath) diff --git a/go/client/sealed.go b/go/client/sealed.go index ab0f0296..c219dbf6 100644 --- a/go/client/sealed.go +++ b/go/client/sealed.go @@ -3,6 +3,9 @@ package main import ( "strings" + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "muhq.space/muhqs-game/go/game" "muhq.space/muhqs-game/go/ui" ) @@ -13,27 +16,29 @@ const ( type Sealed struct { ui.Collection - app *app - width, height int + app *app pool game.PileOfCards deck game.PileOfCards } func NewSealed(width, height int, app *app) *Sealed { - s := &Sealed{app: app, width: width, height: height} + s := &Sealed{ + app: app, + Collection: ui.Collection{Width: width, Height: height}, + } s.AddWidget(ui.NewFixedTextBox( - (s.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, - (s.height-START_BUTTON_HEIGHT)/2-START_BUTTON_HEIGHT, + (s.Width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, + (s.Height-START_BUTTON_HEIGHT)/2-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "sets:", ).Centering(true)) setInput := ui.NewTextInput( - (s.width-START_BUTTON_WIDTH)/2, - (s.height-START_BUTTON_HEIGHT)/2-START_BUTTON_HEIGHT, + (s.Width-START_BUTTON_WIDTH)/2, + (s.Height-START_BUTTON_HEIGHT)/2-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "set list", @@ -41,8 +46,8 @@ func NewSealed(width, height int, app *app) *Sealed { s.AddWidget(setInput) s.AddWidget(ui.NewSimpleButton( - (s.width-START_BUTTON_WIDTH)/2, - (s.height-START_BUTTON_HEIGHT)/2, + (s.Width-START_BUTTON_WIDTH)/2, + (s.Height-START_BUTTON_HEIGHT)/2, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "Start", @@ -53,8 +58,20 @@ func NewSealed(width, height int, app *app) *Sealed { return s } +func (s *Sealed) Update() error { + if err := s.Collection.Update(); err != nil { + return err + } + + if inpututil.IsKeyJustPressed(ebiten.KeyEscape) { + s.app.popActivity() + } + + return nil +} + func (s *Sealed) Layout(width, height int) (int, int) { - return s.width, s.height + return s.Width, s.Height } func (s *Sealed) startSealed(_sets string) { @@ -75,11 +92,11 @@ func (s *Sealed) startSealed(_sets string) { setDeck.MoveInto(s.pool) } - deckList := ui.NewPocList(s.width-SIDEBARD_WIDTH/2, s.height-START_BUTTON_HEIGHT-20, s.deck) + deckList := ui.NewPocList(s.Width-SIDEBARD_WIDTH/2, s.Height-START_BUTTON_HEIGHT-20, s.deck) deckList.Bg(ui.Gray) s.AddWidget(deckList) - grid := ui.NewCardGrid(0, 0, s.width-SIDEBARD_WIDTH, s.height, 0.5, s.pool).Columns(3) + grid := ui.NewCardGrid(0, 0, s.Width-SIDEBARD_WIDTH, s.Height, 0.5, s.pool).Columns(3) selectCard := func(x, y int) { _card := grid.FindObjectAt(x, y) if _card == nil { @@ -94,8 +111,8 @@ func (s *Sealed) startSealed(_sets string) { s.AddWidget(ui.NewClickWrapper(grid, selectCard)) s.AddWidget(ui.NewSimpleButton( - s.width-SIDEBARD_WIDTH/2-START_BUTTON_WIDTH/2, - s.height-START_BUTTON_HEIGHT, + s.Width-SIDEBARD_WIDTH/2-START_BUTTON_WIDTH/2, + s.Height-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "Done", diff --git a/go/client/startMenu.go b/go/client/startMenu.go index 29a4a18c..94ec3aef 100644 --- a/go/client/startMenu.go +++ b/go/client/startMenu.go @@ -17,8 +17,7 @@ const ( type StartMenu struct { ui.Collection - app *app - width, height int + app *app deckInput *ui.TextInput @@ -36,8 +35,8 @@ func NewStartMenu(app *app) *StartMenu { func (m *StartMenu) build() { deckInput := ui.NewTextInput( - (m.width-DECK_LIST_WIDTH)/2, - (m.height-DECK_LIST_HEIGHT)/2, + (m.Width-DECK_LIST_WIDTH)/2, + (m.Height-DECK_LIST_HEIGHT)/2, DECK_LIST_WIDTH, DECK_LIST_HEIGHT, "deck", @@ -52,7 +51,7 @@ func (m *StartMenu) build() { m.AddWidget(deckInput) m.AddWidget(ui.NewFixedTextBox( - (m.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, + (m.Width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, deckInput.Y-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, @@ -60,7 +59,7 @@ func (m *StartMenu) build() { ).Centering(true)) mapInput := ui.NewTextInput( - (m.width-START_BUTTON_WIDTH)/2, + (m.Width-START_BUTTON_WIDTH)/2, deckInput.Y-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, @@ -74,7 +73,7 @@ func (m *StartMenu) build() { m.AddWidget(mapInput) m.AddWidget(ui.NewFixedTextBox( - (m.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, + (m.Width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, mapInput.Y-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, @@ -82,7 +81,7 @@ func (m *StartMenu) build() { ).Centering(true)) playerInput := ui.NewTextInput( - (m.width-START_BUTTON_WIDTH)/2, + (m.Width-START_BUTTON_WIDTH)/2, mapInput.Y-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, @@ -96,18 +95,18 @@ func (m *StartMenu) build() { m.AddWidget(playerInput) m.AddWidget(ui.NewSimpleButton( - (m.width-START_BUTTON_WIDTH)/2+START_BUTTON_WIDTH/2, + (m.Width-START_BUTTON_WIDTH)/2+START_BUTTON_WIDTH/2, deckInput.Y+deckInput.Height, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "Sealed", func(*ui.SimpleButton) { - sealed := NewSealed(m.width, m.height, m.app) + sealed := NewSealed(m.Width, m.Height, m.app) m.app.pushActivity(sealed) })) m.AddWidget(ui.NewSimpleButton( - (m.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH/2, + (m.Width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH/2, deckInput.Y+deckInput.Height, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, @@ -129,7 +128,7 @@ func (m *StartMenu) Update() error { } func (m *StartMenu) Layout(width, height int) (int, int) { - return m.width, m.height + return m.Width, m.Height } func (m *StartMenu) startGame() { |
