diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2023-02-14 16:05:06 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-08-20 15:50:09 +0200 |
| commit | 379c17fe2244dc8fe8365cdbf3d62e21951c8121 (patch) | |
| tree | 32a1741be38f3745c1edea86a4adf9116cc7c93f | |
| parent | 1ac413e1d0ddc39a41374ff9b3375e6de3e1af1c (diff) | |
| download | muhqs-game-379c17fe2244dc8fe8365cdbf3d62e21951c8121.tar.gz muhqs-game-379c17fe2244dc8fe8365cdbf3d62e21951c8121.zip | |
add sealed activity to select a starting deck
| -rw-r--r-- | go/client/game.go | 6 | ||||
| -rw-r--r-- | go/client/main.go | 16 | ||||
| -rw-r--r-- | go/client/sealed.go | 106 | ||||
| -rw-r--r-- | go/client/startMenu.go | 132 | ||||
| -rw-r--r-- | go/game/card.go | 2 | ||||
| -rw-r--r-- | go/game/pileOfCards.go | 17 | ||||
| -rw-r--r-- | go/game/set.go | 43 | ||||
| -rw-r--r-- | go/go.mod | 1 | ||||
| -rw-r--r-- | go/go.sum | 1 | ||||
| -rw-r--r-- | go/ui/buffer.go | 2 | ||||
| -rw-r--r-- | go/ui/button.go | 4 | ||||
| -rw-r--r-- | go/ui/cardGrid.go | 183 | ||||
| -rw-r--r-- | go/ui/clickable.go | 40 | ||||
| -rw-r--r-- | go/ui/collection.go | 70 | ||||
| -rw-r--r-- | go/ui/colors.go | 4 | ||||
| -rw-r--r-- | go/ui/hand.go | 100 | ||||
| -rw-r--r-- | go/ui/stateBar.go | 2 | ||||
| -rw-r--r-- | go/ui/textBox.go | 10 | ||||
| -rw-r--r-- | go/ui/textInput.go | 1 |
19 files changed, 570 insertions, 170 deletions
diff --git a/go/client/game.go b/go/client/game.go index 91e7e365..76b04fa7 100644 --- a/go/client/game.go +++ b/go/client/game.go @@ -503,8 +503,10 @@ func (g *Game) Update() error { scrollX, scrollY := ebiten.Wheel() if scrollX != 0 || scrollY != 0 { - if scrollable, ok := obj.(ui.Scrollable); ok { - scrollable.Scroll(int(scrollX), int(scrollY)) + for _, w := range g.widgets { + if scrollable, ok := w.(ui.Scrollable); ok && w.Contains(x, y) { + scrollable.Scroll(int(scrollX), int(scrollY)) + } } } diff --git a/go/client/main.go b/go/client/main.go index 632bcc13..2b7496ad 100644 --- a/go/client/main.go +++ b/go/client/main.go @@ -28,15 +28,23 @@ type app struct { windowWidth int windowHeight int - active ebiten.Game + activities []ebiten.Game +} + +func (a *app) pushActivity(activity ebiten.Game) { + a.activities = append(a.activities, activity) +} + +func (a *app) popActivity() { + a.activities = a.activities[:len(a.activities)-1] } func (a *app) Update() error { - return a.active.Update() + return a.activities[len(a.activities)-1].Update() } func (a *app) Draw(screen *ebiten.Image) { - a.active.Draw(screen) + a.activities[len(a.activities)-1].Draw(screen) } func (a *app) Layout(outsideWidth, outsideHeight int) (int, int) { @@ -84,7 +92,7 @@ func main() { 1 nautics/galley` } - app.active = startMenu + app.pushActivity(startMenu) if err := ebiten.RunGame(app); err != nil { log.Fatal(err) } diff --git a/go/client/sealed.go b/go/client/sealed.go new file mode 100644 index 00000000..6096a1d5 --- /dev/null +++ b/go/client/sealed.go @@ -0,0 +1,106 @@ +package main + +import ( + "strings" + + "muhq.space/muhqs-game/go/game" + "muhq.space/muhqs-game/go/ui" +) + +const ( + SIDEBARD_WIDTH = 200 +) + +type Sealed struct { + ui.Collection + app *app + width, height int + + pool game.PileOfCards + deck game.PileOfCards +} + +func NewSealed(width, height int, app *app) *Sealed { + s := &Sealed{app: app, 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, + 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, + START_BUTTON_WIDTH, + START_BUTTON_HEIGHT, + "set list", + ) + s.AddWidget(setInput) + + s.AddWidget(ui.NewSimpleButton( + (s.width-START_BUTTON_WIDTH)/2, + (s.height-START_BUTTON_HEIGHT)/2, + START_BUTTON_WIDTH, + START_BUTTON_HEIGHT, + "Start", + func(*ui.SimpleButton) { + s.startSealed(setInput.Text()) + })) + + return s +} + +func (s *Sealed) Layout(width, height int) (int, int) { + return s.width, s.height +} + +func (s *Sealed) startSealed(_sets string) { + s.pool = game.NewPileOfCards() + s.deck = game.NewPileOfCards() + + s.Clear() + + sets := strings.Split(_sets, ",") + for _, setName := range sets { + set := game.SetNames[setName] + setDeck := game.NewDeckFromCardPaths(set.CardPaths()) + setDeck.MoveInto(s.pool) + } + + 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) + selectCard := func(x, y int) { + _card := grid.FindObjectAt(x, y) + if _card == nil { + return + } + + card := _card.(*game.Card) + s.pool.MoveCard(card, s.deck) + grid.ForceRedraw() + deckList.ForceRedraw() + } + s.AddWidget(ui.NewClickWrapper(grid, selectCard)) + + s.AddWidget(ui.NewSimpleButton( + s.width-SIDEBARD_WIDTH/2-START_BUTTON_WIDTH/2, + s.height-START_BUTTON_HEIGHT, + START_BUTTON_WIDTH, + START_BUTTON_HEIGHT, + "Done", + func(*ui.SimpleButton) { + s.done() + })) +} + +func (s *Sealed) done() { + startMenu := s.app.activities[len(s.app.activities)-2].(*StartMenu) + startMenu.deckInput.SetInput(s.deck.ToList()) + s.app.popActivity() +} diff --git a/go/client/startMenu.go b/go/client/startMenu.go index 8f149f8b..29a4a18c 100644 --- a/go/client/startMenu.go +++ b/go/client/startMenu.go @@ -1,11 +1,8 @@ package main import ( - "image/color" "log" - "github.com/hajimehoshi/ebiten/v2" - "github.com/hajimehoshi/ebiten/v2/inpututil" // "muhq.space/muhqs-game/go/game" "muhq.space/muhqs-game/go/ui" ) @@ -13,17 +10,22 @@ import ( const ( START_BUTTON_WIDTH = 150 START_BUTTON_HEIGHT = 50 + + DECK_LIST_WIDTH = 300 + DECK_LIST_HEIGHT = 500 ) type StartMenu struct { + ui.Collection app *app width, height int + deckInput *ui.TextInput + playerName string startDeck string remote string mapPath string - widgets []ui.Widget } func NewStartMenu(app *app) *StartMenu { @@ -33,18 +35,55 @@ func NewStartMenu(app *app) *StartMenu { } func (m *StartMenu) build() { - playerLabel := ui.NewFixedTextBox( + deckInput := ui.NewTextInput( + (m.width-DECK_LIST_WIDTH)/2, + (m.height-DECK_LIST_HEIGHT)/2, + DECK_LIST_WIDTH, + DECK_LIST_HEIGHT, + "deck", + ) + deckInput.Bg(ui.Gray) + + if m.startDeck != "" { + deckInput.AddInput([]rune(m.startDeck)) + } + + m.deckInput = deckInput + m.AddWidget(deckInput) + + m.AddWidget(ui.NewFixedTextBox( + (m.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, + deckInput.Y-START_BUTTON_HEIGHT, + START_BUTTON_WIDTH, + START_BUTTON_HEIGHT, + "map:", + ).Centering(true)) + + mapInput := ui.NewTextInput( + (m.width-START_BUTTON_WIDTH)/2, + deckInput.Y-START_BUTTON_HEIGHT, + START_BUTTON_WIDTH, + START_BUTTON_HEIGHT, + "map", + ) + + if m.mapPath != "" { + mapInput.AddInput([]rune(m.mapPath)) + } + + m.AddWidget(mapInput) + + m.AddWidget(ui.NewFixedTextBox( (m.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH, - (m.height-START_BUTTON_HEIGHT)/2-START_BUTTON_HEIGHT, + mapInput.Y-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "name:", - ).Centering(true).Bg(color.Black) - m.widgets = append(m.widgets, playerLabel) + ).Centering(true)) playerInput := ui.NewTextInput( (m.width-START_BUTTON_WIDTH)/2, - (m.height-START_BUTTON_HEIGHT)/2-START_BUTTON_HEIGHT, + mapInput.Y-START_BUTTON_HEIGHT, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "player name", @@ -54,77 +93,46 @@ func (m *StartMenu) build() { playerInput.AddInput([]rune(m.playerName)) } - m.widgets = append(m.widgets, playerInput) + m.AddWidget(playerInput) - m.widgets = append(m.widgets, ui.NewSimpleButton( - (m.width-START_BUTTON_WIDTH)/2, - (m.height-START_BUTTON_HEIGHT)/2, + m.AddWidget(ui.NewSimpleButton( + (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) + m.app.pushActivity(sealed) + })) + + m.AddWidget(ui.NewSimpleButton( + (m.width-START_BUTTON_WIDTH)/2-START_BUTTON_WIDTH/2, + deckInput.Y+deckInput.Height, START_BUTTON_WIDTH, START_BUTTON_HEIGHT, "Start", func(*ui.SimpleButton) { m.playerName = playerInput.Text() - m.app.active = m.next() + m.mapPath = mapInput.Text() + m.startDeck = deckInput.Text() + m.startGame() })) } func (m *StartMenu) Update() error { - if m.widgets == nil { + if m.Widgets() == nil { m.build() } - x, y := ebiten.CursorPosition() - - var activeWidget ui.Widget - for _, w := range m.widgets { - if !w.Contains(x, y) { - continue - } - activeWidget = w - break - } - - if activeWidget == nil { - return nil - } - - scrollX, scrollY := ebiten.Wheel() - if scrollX != 0 || scrollY != 0 { - if scrollable, ok := activeWidget.(ui.Scrollable); ok { - scrollable.Scroll(int(scrollX), int(scrollY)) - } - } - - if textInput, ok := activeWidget.(*ui.TextInput); ok { - // var input []rune - // ebiten.AppendInputChars(input) - input := ebiten.InputChars() - if len(input) > 0 { - textInput.AddInput(input) - } - textInput.HandleKey() - } - - if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { - if btn, ok := activeWidget.(ui.Button); ok { - btn.Click(x, y) - } - } - - return nil -} - -func (m *StartMenu) Draw(screen *ebiten.Image) { - for _, w := range m.widgets { - w.Draw(screen) - } + return m.Collection.Update() } func (m *StartMenu) Layout(width, height int) (int, int) { return m.width, m.height } -func (m *StartMenu) next() ebiten.Game { +func (m *StartMenu) startGame() { if m.remote != "" { log.Fatal("Remote games are currently not implemented") } @@ -138,5 +146,5 @@ func (m *StartMenu) next() ebiten.Game { g.gameState.Players[idx].Color = color } - return g.Start() + m.app.pushActivity(g.Start()) } diff --git a/go/game/card.go b/go/game/card.go index a6abffd1..031b95bf 100644 --- a/go/game/card.go +++ b/go/game/card.go @@ -291,7 +291,7 @@ func NewCard(cardPath string) *Card { c := Card{ Name: "", - Set: SetIdentifierNames[set], + Set: SetNames[set], Type: undefinedCardType, BuyCost: NOT_BUYABLE, Values: map[string]interface{}{}, diff --git a/go/game/pileOfCards.go b/go/game/pileOfCards.go index 0a5eaa97..f88dfe93 100644 --- a/go/game/pileOfCards.go +++ b/go/game/pileOfCards.go @@ -1,6 +1,8 @@ package game import ( + "fmt" + "golang.org/x/exp/slices" ) @@ -14,6 +16,7 @@ type PileOfCards interface { RemoveCard(card *Card) MoveCard(card *Card, into PileOfCards) MoveInto(into PileOfCards) + ToList() string } type PileOfCardsBase struct { @@ -75,3 +78,17 @@ func (poc *PileOfCardsBase) MoveInto(into PileOfCards) { into.AddCards(poc.cards) poc.cards = []*Card{} } + +func (poc *PileOfCardsBase) ToList() (list string) { + m := make(map[string]int) + for _, card := range poc.cards { + path := card.Path() + m[path]++ + } + + for path, amount := range m { + list += fmt.Sprintf("%d %s\n", amount, path) + } + + return list[:len(list)-1] +} diff --git a/go/game/set.go b/go/game/set.go index b0565eca..aca5b7f5 100644 --- a/go/game/set.go +++ b/go/game/set.go @@ -2,6 +2,11 @@ package game import ( "log" + "net/http" + "path" + "strings" + + "golang.org/x/net/html" ) type SetIdentifier int @@ -43,7 +48,7 @@ func (si SetIdentifier) String() string { return "" } -var SetIdentifiers = struct { +var Sets = struct { Base SetIdentifier Magic SetIdentifier Equipments SetIdentifier @@ -65,7 +70,7 @@ var SetIdentifiers = struct { Exp1: Exp1, } -var SetIdentifierNames = map[string]SetIdentifier{ +var SetNames = map[string]SetIdentifier{ "base": Base, "magic": Magic, "equipments": Equipments, @@ -76,3 +81,37 @@ var SetIdentifierNames = map[string]SetIdentifier{ "tyrant": Tyrant, "exp1": Exp1, } + +func (set *SetIdentifier) CardPaths() []string { + url := PROTOCOL + path.Join(BASE_URL, CARD_DATA_URL, set.String()) + log.Printf("loading %s\n", url) + resp, err := http.Get(url) + if err != nil { + log.Fatal(err) + } + + defer resp.Body.Close() + + doc, err := html.Parse(resp.Body) + if err != nil { + log.Fatal(err) + } + + paths := []string{} + var walk func(*html.Node) + walk = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + for _, a := range n.Attr { + if a.Key == "href" { + paths = append(paths, path.Join(set.String(), strings.TrimSuffix(a.Val, ".yml"))) + break + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + walk(c) + } + } + walk(doc) + return paths +} @@ -7,6 +7,7 @@ require ( github.com/hajimehoshi/ebiten/v2 v2.4.15 golang.org/x/exp v0.0.0-20230116083435-1de6713980de golang.org/x/image v0.3.0 + golang.org/x/net v0.0.0-20220722155237-a158d28d115b gopkg.in/yaml.v3 v3.0.1 ) @@ -60,6 +60,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/go/ui/buffer.go b/go/ui/buffer.go index e95102fe..201273b8 100644 --- a/go/ui/buffer.go +++ b/go/ui/buffer.go @@ -10,7 +10,7 @@ import ( ) var ( - BUFFER_BACKGROUND = gray + BUFFER_BACKGROUND = Gray BUFFER_FOREGROUND = color.White ) diff --git a/go/ui/button.go b/go/ui/button.go index 0fd26936..78ab459f 100644 --- a/go/ui/button.go +++ b/go/ui/button.go @@ -12,7 +12,7 @@ import ( type Button interface { Widget - Click(x, y int) + Clickable } type SimpleButton struct { @@ -24,7 +24,7 @@ type SimpleButton struct { } func NewSimpleButton(x, y, w, h int, label string, onClick func(b *SimpleButton)) *SimpleButton { - sb := &SimpleButton{NewWidgetBase(x, y, w, h), label, onClick, gray, color.White} + sb := &SimpleButton{NewWidgetBase(x, y, w, h), label, onClick, Gray, color.White} sb.renderImpl = func() *ebiten.Image { return sb.render() } return sb } diff --git a/go/ui/cardGrid.go b/go/ui/cardGrid.go new file mode 100644 index 00000000..9ed92aa4 --- /dev/null +++ b/go/ui/cardGrid.go @@ -0,0 +1,183 @@ +package ui + +import ( + "log" + "math" + + "github.com/hajimehoshi/ebiten/v2" + "muhq.space/muhqs-game/go/assets" + "muhq.space/muhqs-game/go/game" + + "golang.org/x/exp/slices" +) + +type CardGrid struct { + WidgetBase + cards game.PileOfCards + columns, rows int + scale float64 + cardWidth, cardHeight float64 + xOffset, yOffset int + highlights []*game.Card + grid *ebiten.Image + gridCards int +} + +func NewCardGrid(x, y, width, height int, scale float64, cards game.PileOfCards) *CardGrid { + w := &CardGrid{ + WidgetBase: NewWidgetBase(x, y, width, height), + cards: cards, + scale: scale, + } + w.renderImpl = func() *ebiten.Image { return w.render() } + return w +} + +func (w *CardGrid) Columns(columns int) *CardGrid { + if w.rows != 0 { + log.Fatalf("CardGrid can not have fixed columns and rows") + } + w.columns = columns + return w +} + +func (w *CardGrid) Rows(rows int) *CardGrid { + if w.columns != 0 { + log.Fatalf("CardGrid can not have fixed columns and rows") + } + w.rows = rows + return w +} + +func (w *CardGrid) render() *ebiten.Image { + img := ebiten.NewImage(w.Width, w.Height) + + cards := w.cards.Cards() + nCards := len(cards) + + if nCards == 0 { + return img + } + + if nCards != w.gridCards { + if w.cardWidth == 0 && w.cardHeight == 0 { + cardImg := assets.GetCard(w.cards.Cards()[0].Path(), "en") + b := cardImg.Bounds() + + // Fit the row height to the widgets height + if w.rows == 1 && w.scale == -1 { + w.scale = float64(w.Height) / float64(b.Dy()) + } + + w.cardWidth = float64(b.Dx()) * w.scale + w.cardHeight = float64(b.Dy()) * w.scale + } + + var width, height int + if w.rows != 0 { + width = int(math.Ceil(float64(nCards)/float64(w.rows)) * w.cardWidth) + height = int(float64(w.rows) * w.cardHeight) + } else if w.columns != 0 { + width = int(float64(w.columns) * w.cardWidth) + height = int(math.Ceil(float64(nCards)/float64(w.columns)) * w.cardHeight) + } else { + log.Fatalf("CardGrid must have have fixed columns or rows") + } + w.gridCards = nCards + w.grid = ebiten.NewImage(width, height) + + xOffset, yOffset := 0.0, 0.0 + for i, card := range cards { + cardImg := assets.GetCard(card.Path(), "en") + if cardImg == nil { + log.Fatalf("No image found for %s", card.Path()) + } + + op := &ebiten.DrawImageOptions{} + op.GeoM.Scale(w.scale, w.scale) + op.GeoM.Translate(xOffset, yOffset) + + if slices.Contains(w.highlights, card) { + op.ColorM.Scale(1, 0, 0, 0.5) + } + + w.grid.DrawImage(cardImg, op) + + columns := w.columns + if columns == 0 { + columns = nCards / w.rows + } + + if i != 0 && i%columns == columns-1 { + xOffset = 0.0 + yOffset += w.cardHeight + } else { + xOffset += w.cardWidth + } + } + } + + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(float64(w.xOffset), float64(w.yOffset)) + img.DrawImage(w.grid, op) + return img +} + +func (w *CardGrid) FindObjectAt(_x, _y int) interface{} { + if !w.Contains(_x, _y) || w.grid == nil { + return nil + } + + x, y := _x-w.X-w.xOffset, _y-w.Y-w.yOffset + + gridBounds := w.grid.Bounds() + if x > gridBounds.Dx() || y > gridBounds.Dy() { + return nil + } + + column := int(float64(x) / w.cardWidth) + row := int(float64(y) / w.cardHeight) + var i int + if w.rows != 0 { + i = row*w.cards.Size()/w.rows + column + } else { + i = row*w.columns + column + } + + if i >= w.cards.Size() { + return nil + } + + return w.cards.Cards()[i] +} + +func (w *CardGrid) setHighlights(cards []*game.Card) { + w.highlights = cards + w.ForceRedraw() +} + +func (w *CardGrid) HighlightCard(card *game.Card) { + w.setHighlights([]*game.Card{card}) +} + +func (w *CardGrid) AddHighlightCard(card *game.Card) { + w.setHighlights(append(w.highlights, card)) +} + +func (w *CardGrid) ClearHighlights() { + w.setHighlights([]*game.Card{}) +} + +func (w *CardGrid) Scroll(x, y int) { + w.xOffset += x * 10 + if w.xOffset > 0 { + w.xOffset = 0 + } + + w.yOffset += y * 10 + if w.yOffset > 0 { + w.yOffset = 0 + } + + w.ForceRedraw() +} diff --git a/go/ui/clickable.go b/go/ui/clickable.go new file mode 100644 index 00000000..761b7534 --- /dev/null +++ b/go/ui/clickable.go @@ -0,0 +1,40 @@ +package ui + +import ( + "github.com/hajimehoshi/ebiten/v2" +) + +type Clickable interface { + Click(x, y int) +} + +type ClickWrapper struct { + w Widget + onClick func(x, y int) +} + +func NewClickWrapper(w Widget, onClick func(x, y int)) *ClickWrapper { + return &ClickWrapper{w, onClick} +} + +func (w *ClickWrapper) Draw(screen *ebiten.Image) { + w.w.Draw(screen) +} + +func (w *ClickWrapper) FindObjectAt(x, y int) interface{} { + return w.w.FindObjectAt(x, y) +} + +func (w *ClickWrapper) Contains(x, y int) bool { + return w.w.Contains(x, y) +} + +func (w *ClickWrapper) Click(x, y int) { + w.onClick(x, y) +} + +func (w *ClickWrapper) Scroll(x, y int) { + if w, ok := w.w.(Scrollable); ok { + w.Scroll(x, y) + } +} diff --git a/go/ui/collection.go b/go/ui/collection.go new file mode 100644 index 00000000..ac53ba8c --- /dev/null +++ b/go/ui/collection.go @@ -0,0 +1,70 @@ +package ui + +import ( + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/inpututil" +) + +type Collection struct { + widgets []Widget +} + +func (c *Collection) Widgets() []Widget { + return c.widgets +} + +func (c *Collection) Clear() { + c.widgets = []Widget{} +} + +func (c *Collection) AddWidget(w Widget) { + c.widgets = append(c.widgets, w) +} + +func (c *Collection) Draw(screen *ebiten.Image) { + for _, w := range c.widgets { + w.Draw(screen) + } +} + +func (c *Collection) Update() error { + x, y := ebiten.CursorPosition() + + var activeWidget Widget + for _, w := range c.widgets { + if !w.Contains(x, y) { + continue + } + activeWidget = w + break + } + + if activeWidget == nil { + return nil + } + + scrollX, scrollY := ebiten.Wheel() + if scrollX != 0 || scrollY != 0 { + if scrollable, ok := activeWidget.(Scrollable); ok { + scrollable.Scroll(int(scrollX), int(scrollY)) + } + } + + if textInput, ok := activeWidget.(*TextInput); ok { + // var input []rune + // ebiten.AppendInputChars(input) + input := ebiten.InputChars() + if len(input) > 0 { + textInput.AddInput(input) + } + textInput.HandleKey() + } + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { + if btn, ok := activeWidget.(Button); ok { + btn.Click(x, y) + } + } + + return nil +} diff --git a/go/ui/colors.go b/go/ui/colors.go index 3998e640..d8422479 100644 --- a/go/ui/colors.go +++ b/go/ui/colors.go @@ -5,6 +5,6 @@ import ( ) var ( - gray = color.RGBA{0x80, 0x80, 0x80, 0xff} - resourceBg = color.RGBA{230, 180, 128, 0xff} + Gray = color.RGBA{0x80, 0x80, 0x80, 0xff} + ResourceBg = color.RGBA{230, 180, 128, 0xff} ) diff --git a/go/ui/hand.go b/go/ui/hand.go index f7157232..7c8feab8 100644 --- a/go/ui/hand.go +++ b/go/ui/hand.go @@ -1,13 +1,8 @@ package ui import ( - "log" - "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 { @@ -15,99 +10,24 @@ type HandCard struct { } type HandView struct { - WidgetBase - hand *game.Hand - cardWidth float64 - cardHeight float64 - scale float64 - bottomAligned bool - highlights []*game.Card + CardGrid } 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 := &HandView{CardGrid{ + WidgetBase: NewWidgetBase(x, y, width, height), + cards: h, + rows: 1, + scale: -1, + }} hv.renderImpl = func() *ebiten.Image { return hv.render() } return hv } -func (hv *HandView) render() *ebiten.Image { - img := ebiten.NewImage(hv.Width, hv.Height) - handCards := hv.hand.Cards() - nHandCards := len(handCards) - for i, card := range handCards { - cardImg := assets.GetCard(card.Path(), "en") - if cardImg == nil { - log.Fatalf("No image found for %s", card.Path()) - } - - if hv.scale == 0 { - b := cardImg.Bounds() - x_scale := (float64(hv.Width) / float64(nHandCards)) / float64(b.Dx()) - y_scale := float64(hv.Height) / float64(b.Dy()) - hv.scale = x_scale - if y_scale < x_scale { - 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, yOffset) - - if slices.Contains(hv.highlights, card) { - op.ColorM.Scale(1, 0, 0, 0.5) - } - - img.DrawImage(cardImg, op) - } - return img -} - func (hv *HandView) FindObjectAt(x, y int) interface{} { - if !hv.Contains(x, y) { - return nil + if obj := hv.CardGrid.FindObjectAt(x, y); obj != nil { + return HandCard{obj.(*game.Card)} } - 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 - } - - return HandCard{hv.hand.Cards()[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) AddHighlightCard(card *game.Card) { - hv.setHighlights(append(hv.highlights, card)) -} - -func (hv *HandView) ClearHighlights() { - hv.setHighlights([]*game.Card{}) + return nil } diff --git a/go/ui/stateBar.go b/go/ui/stateBar.go index b698100a..93de9af9 100644 --- a/go/ui/stateBar.go +++ b/go/ui/stateBar.go @@ -33,7 +33,7 @@ func (sb *StateBar) phaseLabel() string { func (sb *StateBar) Draw(screen *ebiten.Image) { if sb.resourceView == nil { sb.resourceView = NewFixedTextBox(sb.X, sb.Y, - sb.Height*2, sb.Height, sb.resourceLabel()).Centering(true).Bg(resourceBg) + sb.Height*2, sb.Height, sb.resourceLabel()).Centering(true).Bg(ResourceBg) } sb.resourceView.Draw(screen) diff --git a/go/ui/textBox.go b/go/ui/textBox.go index 1c82a307..cc813a4e 100644 --- a/go/ui/textBox.go +++ b/go/ui/textBox.go @@ -127,6 +127,7 @@ func NewPocList(centerX, bottomY int, poc game.PileOfCards) *PocList { bottomY: bottomY, poc: poc, } + w.renderImpl = func() *ebiten.Image { w.setText() return w.render() @@ -135,6 +136,7 @@ func NewPocList(centerX, bottomY int, poc game.PileOfCards) *PocList { } func (w *PocList) setText() { + w.Width, w.Height = -1, -1 t := "" for _, c := range w.poc.Cards() { t = fmt.Sprintf("%s%s\n", t, c.Name) @@ -188,11 +190,15 @@ func NewTextInput(x, y int, width, height int, label string) *TextInput { return w } -func (ti *TextInput) AddInput(input []rune) { - ti.input += string(input) +func (ti *TextInput) SetInput(input string) { + ti.input = input ti.ForceRedraw() } +func (ti *TextInput) AddInput(input []rune) { + ti.SetInput(ti.input + string(input)) +} + func (ti *TextInput) HandleKey() { if inpututil.IsKeyJustPressed(ebiten.KeyBackspace) { if len(ti.input) > 0 { diff --git a/go/ui/textInput.go b/go/ui/textInput.go deleted file mode 100644 index 5b1faa29..00000000 --- a/go/ui/textInput.go +++ /dev/null @@ -1 +0,0 @@ -package ui |
