package main import ( "fmt" "image/color" "gioui.org/layout" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" "git.sr.ht/~whereswaldon/pointstar/gamestate" "git.sr.ht/~whereswaldon/pointstar/server/protocol" ) // Skin provides methods for rendering the game UI type Skin struct { *material.Theme } func (s *Skin) LayoutAll(g *gamestate.GameState, u *UIState, gtx *layout.Context) { switch g.Phase { case gamestate.Lobby: s.LayoutLobby(g, u, gtx) default: s.LayoutTable(g, u, gtx) } } func (s *Skin) LayoutLobby(g *gamestate.GameState, u *UIState, gtx *layout.Context) { layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Flexed(.5, func() {}), layout.Rigid(func() { layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Flexed(.5, func() {}), layout.Rigid(func() { if u.PlayerNum == -1 { clicked := false for u.NameSubmit.Clicked(gtx) { clicked = true } if clicked { u.Worker.ToServer <- protocol.SetPlayerName{Name: u.NameEditor.Text()} } layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Flexed(.5, func() {}), layout.Rigid(func() { s.Theme.Editor("Username").Layout(gtx, &u.NameEditor) }), layout.Rigid(func() { s.Theme.Button("Submit").Layout(gtx, &u.NameSubmit) }), layout.Flexed(.5, func() {})) } else { clicked := false for u.StartButton.Clicked(gtx) { clicked = true } if clicked { u.Worker.ToServer <- protocol.StartGame{} } layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func() { msg := "waiting for other players..." s.Theme.H1(msg).Layout(gtx) }), layout.Rigid(func() { players := "" for _, p := range g.Players { players += p.Name + " " } s.Theme.H2(players + " have joined").Layout(gtx) }), layout.Rigid(func() { if u.PlayerNum == 0 { s.Theme.Button("Start game").Layout(gtx, &u.StartButton) } else { s.Theme.H6("The first player to join can start the game at any time.").Layout(gtx) } })) } }), layout.Flexed(.5, func() {})) }), layout.Flexed(.5, func() {})) } // LayoutTable renders the entire game interface into the provided context func (s *Skin) LayoutTable(g *gamestate.GameState, u *UIState, gtx *layout.Context) { if g.IsPlayersTurn(u.PlayerNum) { for i := range u.PlayButtons[:] { clicked := false for u.PlayButtons[i].Clicked(gtx) { clicked = true } if clicked { u.Worker.ToServer <- protocol.PlayCard{Index: i} } } passed := false for u.PassButton.Clicked(gtx) { passed = true } if passed { u.Worker.ToServer <- protocol.PassTurn{} } } var children []layout.FlexChild // populate the slice with functions to lay out each player's hand for i := range g.Players { player := g.Players[i] children = append(children, layout.Rigid(func() { s.LayoutPlayed(player, gtx) })) children = append(children, layout.Flexed(.25, func() {})) } children = append(children, layout.Flexed(1, func() {})) children = append(children, layout.Rigid(func() { s.LayoutHand(g.Players[u.PlayerNum], g.IsPlayersTurn(u.PlayerNum), u.PlayButtons[:], &u.PassButton, gtx) })) // lay out all of the rows layout.Flex{Axis: layout.Vertical}.Layout(gtx, children...) } // LayoutPlayed arranges a row of information about a player and the cards // that they have played during the current battle func (s *Skin) LayoutPlayed(player gamestate.Player, gtx *layout.Context) { var children []layout.FlexChild // prepend player info hand := player.Played children = append(children, layout.Rigid(func() { layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func() { s.Theme.H3(player.Name).Layout(gtx) }), layout.Rigid(func() { format := "Strength: %d" if player.IsPassing { format += " Passed" } s.Theme.H5(fmt.Sprintf(format, hand.Score())).Layout(gtx) }), layout.Rigid(func() { s.Theme.H6(fmt.Sprintf("Victories: %d", player.Victories)).Layout(gtx) }), layout.Rigid(func() { s.Theme.H6(fmt.Sprintf("Hand: %d", len(player.Hand))).Layout(gtx) }), ) })) children = append(children, layout.Flexed(.5, func() {})) children = append(children, s.CardListAsFlexChildren(hand, gtx)...) children = append(children, layout.Flexed(.5, func() {})) children = append(children, layout.Rigid(func() { })) layout.Flex{Axis: layout.Horizontal}.Layout(gtx, children...) } // LayoutHand lays out the cards in a player's hand with buttons to play each card // and to pass the turn. func (s *Skin) LayoutHand(player gamestate.Player, isPlayersTurn bool, playButtons []widget.Button, passButton *widget.Button, gtx *layout.Context) { var children []layout.FlexChild hand := player.Hand children = append(children, layout.Flexed(.5, func() {})) for i := range hand { card := hand[i] button := &playButtons[i] children = append(children, layout.Rigid(func() { layout.UniformInset(unit.Dp(8)).Layout(gtx, func() { layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func() { layout.Inset{Bottom: unit.Dp(8)}.Layout(gtx, func() { s.LayoutCard(card, gtx) }) }), layout.Rigid(func() { if isPlayersTurn { s.Theme.Button("Play").Layout(gtx, button) } }), ) }) })) } if isPlayersTurn { children = append(children, layout.Rigid(func() { layout.UniformInset(unit.Dp(8)).Layout(gtx, func() { s.Theme.Button("Pass").Layout(gtx, passButton) }) })) } children = append(children, layout.Flexed(.5, func() {})) layout.Flex{Axis: layout.Horizontal}.Layout(gtx, children...) } func (s *Skin) LayoutCard(c gamestate.Card, gtx *layout.Context) { textColor := color.RGBA{A: 255} if gamestate.CardTypes[c] == gamestate.Special { textColor = color.RGBA{R: 155, A: 255} } layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func() { h := s.Theme.H2(gamestate.CardText[c]) h.Color = textColor h.Layout(gtx) }), layout.Rigid(func() { b := s.Theme.Body2(gamestate.CardNames[c]) b.Color = textColor b.Layout(gtx) }), ) } func (s *Skin) CardListAsFlexChildren(h gamestate.CardList, gtx *layout.Context) []layout.FlexChild { var children []layout.FlexChild for i := range h { card := h[i] children = append(children, layout.Rigid(func() { layout.UniformInset(unit.Dp(8)).Layout(gtx, func() { s.LayoutCard(card, gtx) }) })) } return children }