From 379719a2e2b2dc5c1bfd212cbb5d81b746d5a482 Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 26 Jul 2021 17:34:04 -0400 Subject: [PATCH] begin tui client --- cmd/client2/backend.go | 225 +++++++++++++++++++++++++++++++++++++++++ cmd/client2/main.go | 78 ++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 cmd/client2/backend.go create mode 100644 cmd/client2/main.go diff --git a/cmd/client2/backend.go b/cmd/client2/backend.go new file mode 100644 index 0000000..e7faa76 --- /dev/null +++ b/cmd/client2/backend.go @@ -0,0 +1,225 @@ +package main + +import ( + "flag" + "log" + "os" + "os/signal" + "strconv" + "strings" + "time" + "unicode/utf8" + + "code.rocketnine.space/tslocum/cview" + "git.saintnet.tech/stryan/snengame/internal/coordinator" + "git.saintnet.tech/stryan/snengame/internal/game" + "github.com/google/uuid" + "github.com/gorilla/websocket" +) + +var done chan interface{} +var interrupt chan os.Signal +var pid int +var matchID uuid.UUID + +func receiveHandler(connection *websocket.Conn, output chan string) { + defer close(done) + for { + var resp coordinator.SessionCommandResult + err := connection.ReadJSON(&resp) + if err != nil { + log.Println("Error in receive:", err) + } + switch resp.Result { + case coordinator.SessionRespJoined1: + pid = game.SentinalID + output <- "joined as sentinal" + case coordinator.SessionRespJoined2: + pid = game.ScourgeID + output <- "joined as scourge" + case coordinator.SessionRespFound: + matchID = resp.MatchID + output <- "game found" + case coordinator.SessionRespPlayed: + if resp.GameResult != nil { + output <- "played succesfully" + switch resp.GameResult.ResultType { + case game.ActCmd: + output <- resp.GameResult.ActionResult.String() + case game.StateCmd: + output <- resp.GameResult.StateResult.String() + case game.DebugCmd: + output <- resp.GameResult.DebugResult.String() + } + } else { + output <- "error playing" + } + case coordinator.SessionRespLeft: + output <- "game left" + break + case coordinator.SessionRespBroadcastSenTurn: + output <- "Sentinal may take their turn" + case coordinator.SessionRespBroadcastScoTrun: + output <- "Scourge may take their turn" + case coordinator.SessionRespBroadcastSenWin: + output <- "Sentinal wins!" + case coordinator.SessionRespBroadcastScoWin: + output <- "Scourge wins!" + case coordinator.SessionRespBroadcastNone: + + case coordinator.SessionRespError: + output <- "generic error" + + default: + output <- "Received a server response we don't know how to handle" + } + } +} + +func backend(input chan string, outputview *cview.TextView) { + done = make(chan interface{}) // Channel to indicate that the receiverHandler is done + interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully + cmd := make(chan coordinator.SessionCommand) + output := make(chan string) + + signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT + hostname := flag.String("host", "localhost", "server hostname to connect to") + port := flag.Int("port", 7636, "port to connect to") + + flag.Parse() + port_s := strconv.Itoa(*port) + socketUrl := "ws://" + *hostname + ":" + port_s + "/ws" + conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil) + id := uuid.New() + + if err != nil { + log.Fatal("Error connecting to Websocket Server:", err) + } + defer conn.Close() + go receiveHandler(conn, output) + go GetCommand(id, cmd, input) + ticker := time.NewTicker(1 * time.Second) + // Our main loop for the client + // We send our relevant packets here + for { + var c coordinator.SessionCommand + var o string + select { + case c = <-cmd: + err := conn.WriteJSON(c) + if err != nil { + log.Println("Error during writing to websocket:", err) + return + } + if c.Command == coordinator.SessionCmdLeave { + break + } + case o = <-output: + outputview.SetText(o) + case <-interrupt: + // We received a SIGINT (Ctrl + C). Terminate gracefully... + log.Println("Received SIGINT interrupt signal. Closing all pending connections") + + // Close our websocket connection + err := conn.WriteJSON(coordinator.SessionCommand{ + ID: id, + Command: coordinator.SessionCmdLeave, + }) + if err != nil { + log.Println("Error during closing websocket:", err) + return + } + + select { + case <-done: + log.Println("Receiver Channel Closed! Exiting....") + case <-time.After(time.Duration(1) * time.Second): + log.Println("Timeout in closing receiving channel. Exiting....") + } + return + case <-ticker.C: + if matchID != uuid.Nil { + err := conn.WriteJSON(coordinator.SessionCommand{ + ID: id, + MatchID: matchID, + Command: coordinator.SessionCmdPoll, + }) + if err != nil { + log.Println("Error writing to websocket:", err) + return + } + } + } + } +} + +func GetCommand(uid uuid.UUID, resp chan coordinator.SessionCommand, inputchan chan string) { + for { + var cmd string + var t int + input := <-inputchan + input_s := strings.Split(input, " ") + t, err := strconv.Atoi(input_s[0]) + if err != nil { + continue + } + cmd = trimFirstRune(input) + + cmd = strings.TrimSpace(cmd) + switch t { + case 0: + //session + switch coordinator.SessionCmd(cmd) { + case coordinator.SessionCmdQuery: + resp <- coordinator.SessionCommand{ + ID: uid, + Command: coordinator.SessionCmdQuery, + } + case coordinator.SessionCmdJoin: + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdJoin, + } + case coordinator.SessionCmdLeave: + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdLeave, + } + default: + break + } + case 1: + //state + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.StateCmd, + Cmd: cmd, + }, + } + case 2: + //action + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.ActCmd, + Cmd: cmd, + }, + } + } + + } +} + +func trimFirstRune(s string) string { + _, i := utf8.DecodeRuneInString(s) + return s[i:] +} diff --git a/cmd/client2/main.go b/cmd/client2/main.go new file mode 100644 index 0000000..74b9138 --- /dev/null +++ b/cmd/client2/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "code.rocketnine.space/tslocum/cview" + "github.com/gdamore/tcell/v2" +) + +func main() { + app := cview.NewApplication() + app.EnableMouse(true) + newPrimitive := func(text string) cview.Primitive { + tv := cview.NewTextView() + tv.SetTextAlign(cview.AlignCenter) + tv.SetText(text) + return tv + } + inputchan := make(chan string) + grid := cview.NewGrid() + grid.SetRows(3, 0, 0, 3, 3) + grid.SetColumns(30, 0, 30) + grid.SetBorders(true) + + board := cview.NewTable() + board.SetBorders(true) + cols, rows := 4, 2 + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + color := tcell.ColorWhite.TrueColor() + if c < 1 || r < 1 { + color = tcell.ColorYellow.TrueColor() + } + cell := cview.NewTableCell("test") + cell.SetTextColor(color) + cell.SetAlign(cview.AlignCenter) + board.SetCell(r, c, cell) + } + } + rows = 1 + hand := cview.NewTable() + hand.SetBorders(true) + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + color := tcell.ColorWhite.TrueColor() + if c < 1 || r < 1 { + color = tcell.ColorYellow.TrueColor() + } + cell := cview.NewTableCell("test") + cell.SetTextColor(color) + cell.SetAlign(cview.AlignCenter) + hand.SetCell(r, c, cell) + } + } + + playerside := newPrimitive("Player") + oppside := newPrimitive("Opponent") + commandinput := cview.NewInputField() + commandoutput := cview.NewTextView() + commandinput.SetDoneFunc(func(key tcell.Key) { + inputchan <- commandinput.GetText() + commandinput.SetText("") + }) + commandoutput.SetChangedFunc(func() { + app.Draw() + }) + + grid.AddItem(playerside, 1, 0, 2, 1, 0, 100, false) + grid.AddItem(board, 1, 1, 1, 1, 0, 100, false) + grid.AddItem(hand, 2, 1, 1, 1, 0, 100, false) + grid.AddItem(oppside, 1, 2, 2, 1, 0, 100, false) + grid.AddItem(commandinput, 3, 0, 1, 3, 0, 0, true) + grid.AddItem(commandoutput, 4, 0, 1, 3, 0, 0, false) + go backend(inputchan, commandoutput) + + app.SetRoot(grid, true) + if err := app.Run(); err != nil { + panic(err) + } +}