diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-01-26 22:16:10 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2023-01-26 22:17:17 +0100 |
| commit | 147bfc12107971e585b39bd3b75898e5f2eb4f55 (patch) | |
| tree | 026c2b6bc6da16bccc0ec370c65e0be14fef1b88 /go/client | |
| parent | d5e078ec59002f519a040fb88c36ea76ce6ce207 (diff) | |
| download | muhqs-game-147bfc12107971e585b39bd3b75898e5f2eb4f55.tar.gz muhqs-game-147bfc12107971e585b39bd3b75898e5f2eb4f55.zip | |
intermeditae commit
Diffstat (limited to 'go/client')
| -rw-r--r-- | go/client/main.go | 385 |
1 files changed, 271 insertions, 114 deletions
diff --git a/go/client/main.go b/go/client/main.go index a3dbeeec..b8f1bf7a 100644 --- a/go/client/main.go +++ b/go/client/main.go @@ -19,6 +19,7 @@ const ( screenHeight = 1024 DEFAULT_BUTTON_HEIGHT = 40 + PASS_BUTTON_WIDTH = 100 RESOLVE_BUTTON_X = 500 STACK_BUFFER_WIDTH = 300 @@ -27,6 +28,8 @@ const ( HAND_VIEW_HEIGHT = 500 HAND_VIEW_WIDTH = 500 + + STATE_BAR_WIDTH = 300 ) var playerColors = []color.RGBA{ @@ -34,10 +37,17 @@ var playerColors = []color.RGBA{ {1, 1, 0xff, 0xff}, } +// 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 func() + // 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) { @@ -50,11 +60,11 @@ func (h *hoverDetector) update(x, y int) { } func (h *hoverDetector) reset() { - h.ticks = 0 - if h.resetFunc != nil { - h.resetFunc() - h.resetFunc = nil + if h.resetFunc != nil && !h.resetFunc() { + return } + h.ticks = 0 + h.resetFunc = nil } func (h *hoverDetector) startedHovering() bool { @@ -62,19 +72,28 @@ func (h *hoverDetector) startedHovering() bool { } type Game struct { - gameState *game.State - activePlayer *game.Player - playerCtrl *game.ChanPlayerControl - hasPriority bool - handLayer *ui.HandView - stateBar *ui.StateBar - mapView *ui.MapView - actionChoice *ui.Choice - passButton *ui.SimpleButton - stackBuffer *ui.Buffer - widgets []ui.Widget selectedObject interface{} hoverDetector hoverDetector + + handLayer *ui.HandView + stateBar *ui.StateBar + mapView *ui.MapView + actionChoice *ui.Choice + passButton *ui.SimpleButton + discardPileView *ui.PocList + discardPileButton *ui.SimpleButton + storeView *ui.PocList + storeButton *ui.SimpleButton + stackBuffer *ui.Buffer + widgets []ui.Widget + + activePlayer *game.Player + + playerCtrl *game.ChanPlayerControl + hasPriority bool + + gameState *game.State + storesOnMap bool } func NewGame() *Game { @@ -82,8 +101,12 @@ func NewGame() *Game { g.widgets = []ui.Widget{} g.gameState = game.NewState() - g.passButton = ui.NewSimpleButton(screenWidth-100, screenHeight-DEFAULT_BUTTON_HEIGHT, - 100, DEFAULT_BUTTON_HEIGHT, "Pass", func(*ui.SimpleButton) { + g.passButton = ui.NewSimpleButton(screenWidth-PASS_BUTTON_WIDTH, + screenHeight-DEFAULT_BUTTON_HEIGHT, + PASS_BUTTON_WIDTH, + DEFAULT_BUTTON_HEIGHT, + "Pass", + func(*ui.SimpleButton) { if g.hasPriority { g.passPriority() } @@ -107,6 +130,9 @@ func (g *Game) loadMap(mapName string) { } g.gameState.Map = m g.mapView = ui.NewMapView(g.gameState) + + storeTiles := m.FilterTiles(func(t *game.Tile) bool { return t.Type == game.TileTypes.Store }) + g.storesOnMap = len(storeTiles) > 0 } // func NewGameFromMapWithPlayers(mapName string, players []string, decks []*game.Deck) *Game { @@ -120,9 +146,9 @@ func (g *Game) loadMap(mapName string) { // return g // } -func (g *Game) addPlayer(name string, deck *game.Deck, store *game.Store) *game.Player { +func (g *Game) addPlayer(name string, deck *game.Deck) *game.Player { color := playerColors[len(g.gameState.Players)] - g.gameState.AddNewPlayer(name, deck, game.NewStore(), color) + g.gameState.AddNewPlayer(name, deck, color) return g.getPlayer(name) } @@ -139,33 +165,101 @@ func (g *Game) initPlayerUi(player *game.Player) { g.activePlayer = player g.playerCtrl = game.NewChanPlayerControl() player.Ctrl = g.playerCtrl + var x, y int + + g.handLayer = ui.NewHandView(0, + g.mapView.GetHeight(), + HAND_VIEW_WIDTH, + screenHeight-g.mapView.GetHeight()-DEFAULT_BUTTON_HEIGHT, + player.Hand, + true) + g.addWidget(g.handLayer) + + g.stateBar = ui.NewStateBar(0, + screenHeight-DEFAULT_BUTTON_HEIGHT, + STATE_BAR_WIDTH, + DEFAULT_BUTTON_HEIGHT, + g.gameState, + player) + g.addWidget(g.stateBar) + + g.discardPileButton = ui.NewSimpleButton(g.stateBar.Width, + screenHeight-DEFAULT_BUTTON_HEIGHT, + 150, + DEFAULT_BUTTON_HEIGHT, + "DiscardPile", + func(*ui.SimpleButton) { + if idx := g.findWidget(g.discardPileView); idx == -1 { + g.discardPileView.ForceRedraw() + g.addWidget(g.discardPileView) + } else { + g.removeWidget(g.discardPileView) + } + }) + g.addWidget(g.discardPileButton) + + x = g.discardPileButton.X + g.discardPileButton.Width/2 + y = screenHeight - DEFAULT_BUTTON_HEIGHT + g.discardPileView = ui.NewPocList(x, y, g.activePlayer.DiscardPile) + + if !g.storesOnMap { + x = g.stateBar.Width + g.discardPileButton.Width + y = screenHeight - DEFAULT_BUTTON_HEIGHT + g.storeButton = ui.NewSimpleButton(x, y, + 150, + DEFAULT_BUTTON_HEIGHT, + "Store", + func(*ui.SimpleButton) { + if idx := g.findWidget(g.storeView); idx == -1 { + g.showStore() + } else { + g.hideStore() + } + }) + g.addWidget(g.storeView) - g.handLayer = ui.NewHandView(0, g.mapView.GetHeight(), - HAND_VIEW_WIDTH, screenHeight-g.mapView.GetHeight()-DEFAULT_BUTTON_HEIGHT, player.Hand) - g.widgets = append(g.widgets, g.handLayer) + x = g.storeButton.X + g.storeButton.Width/2 + y = screenHeight - DEFAULT_BUTTON_HEIGHT + g.storeView = ui.NewPocList(x, y, g.activePlayer.Store) + } +} + +func (g *Game) showStore() { + g.storeView.ForceRedraw() + g.addWidget(g.storeView) +} - g.stateBar = ui.NewStateBar(0, screenHeight-DEFAULT_BUTTON_HEIGHT, - screenWidth-g.passButton.Width, DEFAULT_BUTTON_HEIGHT, g.gameState, player) - g.widgets = append(g.widgets, g.stateBar) +func (g *Game) hideStore() { + g.removeWidget(g.storeView) } func (g *Game) addWidget(w ui.Widget) { g.widgets = append(g.widgets, w) } -func (g *Game) removeWidget(widget ui.Widget) { - nwidgets := len(g.widgets) +func (g *Game) findWidget(widget ui.Widget) int { for i, w := range g.widgets { if w != widget { continue } + return i + } + return -1 +} - g.widgets[i] = g.widgets[nwidgets-1] - g.widgets = g.widgets[:nwidgets-1] - return +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] +} + func (g *Game) addActionChoice(perm game.Permanent, x, y int) { actions := perm.GetAvailActionPrototypes() if len(actions) > 0 { @@ -193,6 +287,7 @@ func (g *Game) clearMapHighlights() { func (g *Game) declareAction(a game.Action) { g.playerCtrl.SendAction(a) g.clearMapHighlights() + g.handLayer.ClearHighlights() g.hasPriority = false g.removeWidget(g.passButton) g.stateBar.ForceRedraw() @@ -209,20 +304,38 @@ func (g *Game) handlePlayerNotifications() { case game.PriorityNotification: g.addWidget(g.passButton) g.hasPriority = true + case game.DeclaredActionNotification: - g.stackBuffer.AddLine(n.Action.String()) + if n.Error != nil { + log.Fatal(n.Error) + } + a := n.Context.(game.Action) + g.stackBuffer.AddLine(a.String()) + case game.ResolvedActionNotification: g.clearMapHighlights() g.stackBuffer.RemoveLast() g.mapView.ForceRedraw() + + case game.BuyPrompt: + if !g.storesOnMap { + g.showStore() + } } + + g.stateBar.ForceRedraw() + g.handLayer.ForceRedraw() + n = g.playerCtrl.RecvNotification() } } func (g *Game) findObjectAt(x, y int) interface{} { - for _, b := range g.widgets { - if obj := b.FindObjectAt(x, y); obj != nil { + // 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] + if obj := w.FindObjectAt(x, y); obj != nil { return obj } } @@ -233,96 +346,137 @@ func (g *Game) findObjectAt(x, y int) interface{} { return nil } -func (g *Game) Update() error { - g.handlePlayerNotifications() - - x, y := ebiten.CursorPosition() - g.hoverDetector.update(x, y) - - obj := g.findObjectAt(x, y) - - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { - if obj == nil { - fmt.Printf("No object found at cursor position (%d, %d)\n", x, y) - return nil - } +func (g *Game) handleSelection(obj interface{}, x, y int) { + if obj == nil { + fmt.Printf("No object found at cursor position (%d, %d)\n", x, y) + return + } - log.Printf("Active object is of type %T\n", g.selectedObject) - - switch obj := obj.(type) { - case ui.Button: - obj.Click(x, y) - - case *game.Tile: - tile := obj - if g.selectedObject != nil { - var action game.Action - switch obj := g.selectedObject.(type) { - case *game.MoveActionPrototype: - action = obj.Specialize(tile) - case *game.Card: - if obj.IsPermanent() { - action = game.NewPlayAction(g.activePlayer, obj, tile.Position) - g.handLayer.ForceRedraw() - } - } - if action != nil { - g.declareAction(action) - g.selectedObject = nil - g.mapView.HighlightTile(tile) + switch obj := obj.(type) { + case ui.Button: + obj.Click(x, y) + + case *game.Tile: + tile := obj + if g.selectedObject != nil { + var action game.Action + switch obj := g.selectedObject.(type) { + case *game.MoveActionPrototype: + action = obj.Specialize(tile) + case *game.Card: + if obj.IsPermanent() { + action = game.NewPlayAction(g.activePlayer, obj, tile.Position) } - } else { + } + if action != nil { + g.declareAction(action) + g.selectedObject = nil g.mapView.HighlightTile(tile) } + } else { + g.mapView.HighlightTile(tile) + } - case game.Permanent: - perm := obj - if g.selectedObject == nil { - g.addActionChoice(perm, x, y) - } else { - switch obj := g.selectedObject.(type) { - case *game.AttackActionPrototype: - action := obj.Specialize(perm) - g.selectedObject = nil - g.declareAction(action) - g.mapView.HighlightPermanent(perm) - } + case game.Permanent: + perm := obj + if g.selectedObject == nil { + g.addActionChoice(perm, x, y) + } else { + switch obj := g.selectedObject.(type) { + case *game.AttackActionPrototype: + action := obj.Specialize(perm) + g.selectedObject = nil + g.declareAction(action) + g.mapView.HighlightPermanent(perm) } + } - case ui.HandCard: - g.selectedObject = obj.C + case ui.HandCard: + g.selectedObject = obj.C - default: - log.Fatalf("Object of type %T not handled", obj) + case *game.Card: + if g.gameState.IsActivePlayer(g.activePlayer) && + g.gameState.ActivePhase == game.Phases.BuyPhase { + g.declareAction(game.NewBuyAction(g.activePlayer, obj)) } - switch obj := g.selectedObject.(type) { - case *game.MoveActionPrototype: - g.mapView.HighlightTiles(obj.Unit.MoveRangeTiles(g.gameState.Map)) - case *game.AttackActionPrototype: - perms := obj.Unit.AttackablePermanents(g.gameState.Map) - g.mapView.HighlightPermanents(perms) - case *game.Card: - if obj.IsPermanent() { - spawnTiles := g.gameState.AvailableSpawnTiles(g.activePlayer, obj) - g.mapView.HighlightTiles(spawnTiles) - } + default: + log.Fatalf("Object of type %T not handled", obj) + } +} + +func (g *Game) hightlightSelection(obj interface{}) { + switch obj := g.selectedObject.(type) { + case *game.MoveActionPrototype: + g.mapView.HighlightTiles(obj.Unit.MoveRangeTiles(g.gameState.Map)) + case *game.AttackActionPrototype: + perms := obj.Unit.AttackablePermanents(g.gameState.Map) + g.mapView.HighlightPermanents(perms) + case *game.Card: + g.handLayer.HighlightCard(obj) + if obj.IsPermanent() { + spawnTiles := g.gameState.AvailableSpawnTiles(g.activePlayer, obj) + g.mapView.HighlightTiles(spawnTiles) } - } else if g.hoverDetector.startedHovering() && g.selectedObject == nil && obj != nil { - var hoverHint *ui.TextBox - switch obj := obj.(type) { - case *game.Unit: - hoverHint = ui.NewUnitInfo(x+ui.TILE_WIDTH, y, obj) - case game.Permanent: - hoverHint = ui.NewPermInfo(x+ui.TILE_WIDTH, y, obj) + } +} + +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+500 > screenWidth || y+700 > screenHeight { + wx = x - 500 + wy = y - 700 + } + hoverHint = ui.NewScaledCardView(wx, wy, 500, 700, 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.GetStoreOn(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) - g.hoverDetector.resetFunc = func() { + 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() + + x, y := ebiten.CursorPosition() + g.hoverDetector.update(x, y) + + obj := g.findObjectAt(x, y) + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + g.handleSelection(obj, x, y) + g.hightlightSelection(obj) + } 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) { @@ -330,18 +484,19 @@ func (g *Game) Update() error { g.selectedObject = nil g.hoverDetector.reset() g.clearMapHighlights() + g.handLayer.ClearHighlights() } return nil } func (g *Game) Draw(screen *ebiten.Image) { g.mapView.Draw(screen) - for _, b := range g.widgets { - b.Draw(screen) - } if !g.gameState.Stack.IsEmpty() { g.stackBuffer.Draw(screen) } + for _, b := range g.widgets { + b.Draw(screen) + } ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f FPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS())) } @@ -358,9 +513,11 @@ func main() { g := NewGameFromMap("the-kraken") - muhq := g.addPlayer("muhq", - game.NewDeckFromDeckList(g.gameState.Map.StartDeckList), - game.NewStore()) + nautics := `1 nautics/barge +1 nautics/captain +1 nautics/fisher +1 nautics/galley` + muhq := g.addPlayer("muhq", game.NewDeckFromDeckList(nautics)) g.initPlayerUi(muhq) krakenId := len(g.gameState.Players) |
