Skip to content

Commit

Permalink
separate UI thread from logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Steinke committed Feb 9, 2024
1 parent f7a66b7 commit 8a95a98
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 108 deletions.
105 changes: 105 additions & 0 deletions logic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package main

import (
"crypto/rand"
"fmt"
"math/big"
"time"

"github.com/maxence-charriere/go-app/v9/pkg/app"
)

func NewLogic() *logic {
return &logic{
sequence: []int64{},
}
}

type logic struct {
app.Compo

sequence []int64
clicks int
stage int
state gameState
}

func (g *logic) simonSays(ctx app.Context, a app.Action) {
sequence, ok := a.Value.([]int64)
if !ok {
fmt.Println("wrong type")
return
}
fmt.Println("sequence:", sequence)

go func() {
// TODO: This is so weird. I somehow need to wait before I can do the next
// action, otherwise it just won't update the DOM.
<-time.After(200 * time.Millisecond)
for _, btnIndex := range sequence {
fmt.Println("sending", btnIndex)
ctx.NewAction(fmt.Sprintf(playButton, btnIndex))
<-time.After(time.Second)
}
g.state = gameStatePlayerSays
ctx.NewActionWithValue(stateChange, g.state)
}()
}

func (g *logic) handleNewGame(ctx app.Context, a app.Action) {
fmt.Println("New Game")
g.clicks = 0
// TODO: allow setting difficulty
g.sequence = GenerateSequence(4)
g.stage = 1
g.state = gameStateSimonSays
ctx.NewActionWithValue(stateChange, g.state)
ctx.NewActionWithValue(simonSays, g.sequence[:1])
}

func (g *logic) handleClick(ctx app.Context, a app.Action) {
if g.state != gameStatePlayerSays {
fmt.Println("no game")
return
}
click, ok := a.Value.(int64)
if !ok {
fmt.Println("wrong type")
return
}

fmt.Println("received click:", click)
if g.sequence[g.clicks] != click {
g.state = gameStateLost
ctx.NewActionWithValue(stateChange, g.state)
return
}
g.clicks++
if len(g.sequence) == g.clicks {
g.state = gameStateWon
ctx.NewActionWithValue(stateChange, g.state)
return
}
if g.clicks == g.stage {
g.clicks = 0
g.stage++
g.state = gameStateSimonSays
ctx.NewActionWithValue(stateChange, g.state)
ctx.After(1*time.Second, func(ctx app.Context) {
ctx.NewActionWithValue(simonSays, g.sequence[:g.stage])
})
}
}

func GenerateSequence(l int) []int64 {
seq := []int64{}
for i := 0; i < l; i++ {
n, err := rand.Int(rand.Reader, big.NewInt(4))
if err != nil {
panic(err)
}
seq = append(seq, n.Int64())

}
return seq
}
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ func main() {
serve := flag.Bool("serve", false, "set to serve instead of generating resources")
flag.Parse()

g := NewGame()
l := NewLogic()
// TODO: move logic out of the UI thread
app.Handle(simonSays, l.simonSays)
app.Handle(click, l.handleClick)
app.Handle(newGame, l.handleNewGame)

g := NewUI()
app.Route("/", g)

// When executed on the client-side, the RunWhenOnBrowser() function
Expand Down
137 changes: 30 additions & 107 deletions ui.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package main

import (
"crypto/rand"
"fmt"
"math/big"
"time"

"github.com/maxence-charriere/go-app/v9/pkg/app"
)

type events = string

const (
click events = "click"
simonSays events = "playSequence"
playButton events = "play%d"
newGame events = "newGame"
click events = "click"
simonSays events = "playSequence"
playButton events = "play%d"
newGame events = "newGame"
stateChange events = "stateChange"
)

type gameState int
Expand All @@ -28,29 +26,21 @@ const (
gameStateWon
)

func NewGame() *game {
return &game{
sequence: []int64{},
}
func NewUI() *ui {
return &ui{}
}

type game struct {
type ui struct {
app.Compo

sequence []int64
clicks int
stage int
State gameState
Text string
}

func (g *game) OnMount(ctx app.Context) {
// TODO: move logic out of the UI thread
ctx.Handle(simonSays, g.simonSays)
ctx.Handle(click, g.handleClick)
ctx.Handle(newGame, g.handleNewGame)
func (g *ui) OnMount(ctx app.Context) {
ctx.Handle(stateChange, g.handleStateChange)
}

func (g *game) Render() app.UI {
func (g *ui) Render() app.UI {
gameField := app.Div().Class("game-field")

firstButton := NewButton(0)
Expand All @@ -65,22 +55,8 @@ func (g *game) Render() app.UI {
fourthButton,
)

txt := ""
switch g.State {
case gameStateNoGame:
txt = "Start a New Game"
case gameStatePlayerSays:
txt = "Your turn"
case gameStateSimonSays:
txt = "Simon says..."
case gameStateLost:
txt = "You Lost. Start a New Game"
case gameStateWon:
txt = "You Won. Start a New Game"
}

// TODO: styling
gameStateText := app.Div().Class("game-state").Text(txt)
gameStateText := app.Div().Class("game-state").Text(g.Text)
newGameButton := app.Button().Class("new-game").Text("New Game").OnClick(func(ctx app.Context, _ app.Event) {
ctx.NewAction(newGame)
})
Expand All @@ -91,79 +67,26 @@ func (g *game) Render() app.UI {
)
}

func (g *game) simonSays(ctx app.Context, a app.Action) {
g.Update()
sequence, ok := a.Value.([]int64)
func (b *ui) handleStateChange(ctx app.Context, a app.Action) {
state, ok := a.Value.(gameState)
if !ok {
fmt.Println("wrong type")
return
}
fmt.Println("sequence:", sequence)

ctx.Async(func() {
// TODO: This is so weird. I somehow need to wait before I can do the next
// action, otherwise it just won't update the DOM.
<-time.After(200 * time.Millisecond)
for _, btnIndex := range sequence {
fmt.Println("sending", btnIndex)
ctx.NewAction(fmt.Sprintf(playButton, btnIndex))
<-time.After(time.Second)
}
})
g.State = gameStatePlayerSays
g.Update()
}

func (g *game) handleNewGame(ctx app.Context, a app.Action) {
fmt.Println("New Game")
g.clicks = 0
// TODO: allow setting difficulty
g.sequence = GenerateSequence(4)
g.stage = 1
g.State = gameStateSimonSays
ctx.NewActionWithValue(simonSays, g.sequence[:1])
}

func (g *game) handleClick(ctx app.Context, a app.Action) {
if g.State != gameStatePlayerSays {
fmt.Println("no game")
return
}
click, ok := a.Value.(int64)
if !ok {
fmt.Println("wrong type")
return
}

fmt.Println("received click:", click)
if g.sequence[g.clicks] != click {
g.State = gameStateLost
return
}
g.clicks++
if len(g.sequence) == g.clicks {
g.State = gameStateWon
return
}
if g.clicks == g.stage {
g.clicks = 0
g.stage++
g.State = gameStateSimonSays
ctx.After(1*time.Second, func(ctx app.Context) {
ctx.NewActionWithValue(simonSays, g.sequence[:g.stage])
})
}
}

func GenerateSequence(l int) []int64 {
seq := []int64{}
for i := 0; i < l; i++ {
n, err := rand.Int(rand.Reader, big.NewInt(4))
if err != nil {
panic(err)
}
seq = append(seq, n.Int64())

txt := ""
switch state {
case gameStateNoGame:
txt = "Start a New Game"
case gameStatePlayerSays:
txt = "Your turn"
case gameStateSimonSays:
txt = "Simon says..."
case gameStateLost:
txt = "You Lost. Start a New Game"
case gameStateWon:
txt = "You Won. Start a New Game"
}
return seq
ctx.Dispatch(func(_ app.Context) {
b.Text = txt
})
}

0 comments on commit 8a95a98

Please sign in to comment.