Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix UI deadlocks + improve visual word colors #26

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 18 additions & 29 deletions wordle/terminal_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func NewTerminalManager(ctx context.Context, game *WordGame) *TerminalManager {
stateBox := tview.NewTextView()
stateBox.SetDynamicColors(true)
stateBox.SetBorder(true)
stateBox.SetTitle(fmt.Sprintf("P2P Wordle"))
stateBox.SetTitle("P2P Wordle")

// text views are io.Writers, but they don't automatically refresh.
// this sets a change handler to force the app to redraw when we get
Expand Down Expand Up @@ -107,77 +107,66 @@ func NewTerminalManager(ctx context.Context, game *WordGame) *TerminalManager {
}
}

func (ui *TerminalManager) Run() error {
func (ui *TerminalManager) Run(initialized *bool) error {
go ui.handleEvents()
defer ui.end()

go func() {
// mark as initialized after ~200 milliseconds
time.Sleep(200 * time.Millisecond)
*initialized = true
}()
Comment on lines +114 to +118
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, basing initialization on a timer is not a good idea in my opinion. You should have someplace in your code where you are sure everything is initialized.

err := ui.app.Run()
if err != nil {
return err
}
ui.displayStateString(ui.Game.ComposeStateUI())
return nil

return nil
}

// end signals the event loop to exit gracefully
func (ui *TerminalManager) end() {
ui.doneCh <- struct{}{}
}

func (ui *TerminalManager) displayStateString(s string) {
fmt.Fprintf(ui.stateBox, "%s\n", s)
}

func (ui *TerminalManager) displayStateStatus() {
s := ui.Game.ComposeStateUI()
ui.displayStateString(s)
}

func (ui *TerminalManager) displayDebugString(s string) {
func (ui *TerminalManager) RefreshWordleState() {
stateB := ui.stateBox.(*tview.TextView)
stateB.Clear()
fmt.Fprintf(ui.debugBox, "%s\n", s)
s := ui.Game.ComposeStateUI()
fmt.Fprintf(ui.stateBox, "%s\n", s)
}

func (ui *TerminalManager) AddDebugItem(s string) {
ui.displayDebugString(s)
func (ui *TerminalManager) AddDebugMsg(s string) {
fmt.Fprintf(ui.debugBox, "%s\n", s)
}

func (ui *TerminalManager) handleEvents() {
peerRefreshTicker := time.NewTicker(time.Second)
defer peerRefreshTicker.Stop()

ui.RefreshWordleState()
for {
ui.displayStateStatus()
select {
case input := <-ui.inputCh:
switch ui.Game.StateIdx {
case 0:
ui.AddDebugItem(fmt.Sprintf("Your next proposed word: %s", input))
ui.AddDebugMsg(fmt.Sprintf("Your next proposed word: %s", input))
case 1:
ui.AddDebugItem(fmt.Sprintf("Last guess: %s (freezes are expected if no peers connected)", input))
ui.AddDebugMsg(fmt.Sprintf("Last guess: %s (freezes are expected if no peers connected)", input))
default:
continue
}
// when the user types in a line, publish it to the chat room and print to the message window
err := ui.Game.NewStdinInput(input)
if err != nil {
ui.AddDebugItem(fmt.Sprintf("publish error: %s", err))
ui.AddDebugMsg(fmt.Sprintf("publish error: %s", err))
}

case others := <-ui.OthersGuessC:
ui.AddDebugItem(fmt.Sprintln("new gueess from someone", others))
// when we receive a message from the chat room, print it to the message window

case <-ui.ctx.Done():
fmt.Println("context done")
return

case <-ui.doneCh:
fmt.Println("channel done")

return
}
ui.RefreshWordleState()
}
}
87 changes: 59 additions & 28 deletions wordle/user_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package wordle

import (
"context"
"fmt"
"time"

"github.com/p2p-games/wordle/model"
)
Expand All @@ -21,7 +21,8 @@ type WordleUI struct {

CannonicalHeader *model.Header

tm *TerminalManager
uiInitialized bool
tm *TerminalManager
}

func NewWordleUI(ctx context.Context, wordleServ *Service, peerId string) *WordleUI {
Expand All @@ -33,6 +34,10 @@ func NewWordleUI(ctx context.Context, wordleServ *Service, peerId string) *Wordl
}

wordleServ.SetLog(func(s string) {
// wait untill UI is initialilzed
for !ui.uiInitialized {
time.Sleep(200 * time.Millisecond)
}
Comment on lines +37 to +40
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing this, you can call SetLog when you know you can do it (UI is initialized) from the parent class.

ui.AddDebugItem(s)
})

Expand All @@ -48,43 +53,69 @@ func (w *WordleUI) Run() {
panic("non able to load any header from the datastore, not even genesis??!")
}

// generate a guess channel
userGuessC := make(chan guess)

// generate a new game
w.CurrentGame = NewWordGame(w.ctx, w.PeerId, w.CannonicalHeader.PeerID, w.CannonicalHeader.Proposal, w.WordleServ)
w.CurrentGame = NewWordGame(w.PeerId, w.CannonicalHeader.PeerID, w.CannonicalHeader.Proposal, userGuessC)

// generate a terminal manager
w.tm = NewTerminalManager(w.ctx, w.CurrentGame)
err = w.tm.Run()
if err != nil {
panic(err)
}
// get the channel for incoming headers
incomingHeaders, err := w.WordleServ.Guesses(w.ctx)
if err != nil {
panic("unable to retrieve the channel of headers from the user interface")
}

for {
select {
case recHeader := <-incomingHeaders: // incoming New Message from surrounding peers
w.AddDebugItem(fmt.Sprintf("guess received from %s", recHeader.PeerID))
// verify weather the header is correct or not
if model.Verify(recHeader.Guess, w.CannonicalHeader.Proposal) {
w.CannonicalHeader = recHeader
// generate a new one game
w.CurrentGame = NewWordGame(w.ctx, w.PeerId, w.CannonicalHeader.PeerID, recHeader.Proposal, w.WordleServ)

// refresh the terminal manager
w.tm.Game = w.CurrentGame
} else {
// Actually, there isn't anything else to do
continue
go func() {
for {
select {
case userGuess := <-userGuessC:
w.tm.AddDebugMsg("about to send new guess")
// notify the service of a new User Guess
err = w.WordleServ.Guess(w.ctx, userGuess.Guess, userGuess.Proposal)
if err != nil {
w.tm.AddDebugMsg("error sending guess" + userGuess.Guess + " - " + err.Error())
}

// check if the guess if the guess was right for debug-msg purpose
bools, err := model.VerifyString(userGuess.Guess, w.CannonicalHeader.Proposal)
if err != nil {
w.tm.AddDebugMsg("error verifying the guess" + err.Error())
}

if IsGuessSuccess(bools) {
w.tm.AddDebugMsg("succ guess sent")
} else {
w.tm.AddDebugMsg("wrong guess sent")
}

case recHeader := <-incomingHeaders: // incoming New Message from surrounding peers
//w.AddDebugItem(fmt.Sprintf("guess received from %s", recHeader.PeerID))
// verify weather the header is correct or not
if model.Verify(recHeader.Guess, w.CannonicalHeader.Proposal) {
w.CannonicalHeader = recHeader
// generate a new one game

w.CurrentGame = NewWordGame(w.PeerId, w.CannonicalHeader.PeerID, recHeader.Proposal, userGuessC)
// refresh the terminal manager
w.tm.Game = w.CurrentGame
}
case <-w.ctx.Done(): // context shutdow
return
}
case <-w.ctx.Done(): // context shutdow
return

// render the new state of the UI
w.tm.RefreshWordleState()
}
}()

// generate a terminal manager
w.tm = NewTerminalManager(w.ctx, w.CurrentGame)
err = w.tm.Run(&w.uiInitialized)
if err != nil {
panic(err)
}

}

func (w *WordleUI) AddDebugItem(s string) {
w.tm.AddDebugItem(s)
w.tm.AddDebugMsg(s)
}
4 changes: 2 additions & 2 deletions wordle/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ func ComposeWordleVisualWord(word string, target *model.Word) string {

switch charStatus {
case 0: // not in the word
compWord += composeCharWithColor(c[i], "")
compWord += composeCharWithColor(c[i], "white")
case 1: // in the word but on wrong possition
compWord += composeCharWithColor(c[i], Yellow)
case 2: // bingo
compWord += composeCharWithColor(c[i], Green)
}

}
return compWord
return compWord + "[white]"
}

// compose the character over the color and reset the terminal color
Expand Down
Loading