aboutsummaryrefslogtreecommitdiff
path: root/go/client
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2023-01-26 22:16:10 +0100
committerFlorian Fischer <florian.fischer@muhq.space>2023-01-26 22:17:17 +0100
commit147bfc12107971e585b39bd3b75898e5f2eb4f55 (patch)
tree026c2b6bc6da16bccc0ec370c65e0be14fef1b88 /go/client
parentd5e078ec59002f519a040fb88c36ea76ce6ce207 (diff)
downloadmuhqs-game-147bfc12107971e585b39bd3b75898e5f2eb4f55.tar.gz
muhqs-game-147bfc12107971e585b39bd3b75898e5f2eb4f55.zip
intermeditae commit
Diffstat (limited to 'go/client')
-rw-r--r--go/client/main.go385
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)