package client import ( "encoding/json" "fmt" "log/slog" "net" "os" "sshpong/internal/ansii" "sshpong/internal/pong" "sshpong/internal/renderer" "strings" ) var state pong.GameState var quit chan int var egress chan pong.StateUpdate var isPlayer1 bool = false func Game(conn net.Conn) { fmt.Println("Connected to game!") egress = make(chan pong.StateUpdate) quit = make(chan int) // Network reader go func() { bytes := make([]byte, 512) for { n, err := conn.Read(bytes) if err != nil { slog.Debug("failed to read from game connection...") quit <- 1 } stateUpdateHandler(bytes[:n]) } }() // Network writer go func() { for { update := <-egress bytes, err := json.Marshal(update) if err != nil { slog.Debug("failed to unmarhal game update message from server") } _, err = conn.Write(bytes) if err != nil { slog.Debug("failed to write to game connection...") } } }() prev, err := ansii.MakeTermRaw() if err != nil { fmt.Println("Failed to make terminal raw") return } defer ansii.RestoreTerm(prev) os.Stdout.WriteString(string(ansii.Screen.HideCursor)) defer os.Stdout.WriteString(string(ansii.Screen.ShowCursor)) // Input handler go func() { buf := make([]byte, 3) for { n, err := os.Stdin.Read(buf) if err != nil { fmt.Println("Error reading from stdin", err) return } handleGameInput(buf[:n]) } }() <-quit return } func stateUpdateHandler(bytes []byte) { update := pong.StateUpdate{} err := json.Unmarshal(bytes, &update) if err != nil { slog.Debug("error unmarshalling server json", slog.Any("Unmarshal error", err)) update.FieldPath = "Message" update.Value = []byte("An error has occured ") } fields := strings.Split(update.FieldPath, ".") // type GameState struct { // Message string // Winner string // Score map[string]int // Player1 Player // Player2 Player // Ball Ball // } // For now let's just send the whole field from a top level // of the state. If things are slow we can optimize that later val := update.Value switch fields[0] { case "All": ns := pong.GameState{} err = json.Unmarshal(val, &ns) if err != nil { slog.Debug("error unmarshalling whole state update") return } state = ns case "Message": state.Message = string(update.Value) case "Winner": state.Winner = string(update.Value) case "Score": sc := map[string]int{} err = json.Unmarshal(val, &sc) if err != nil { slog.Debug("error unmarshalling score update") return } state.Score = sc case "Player1": p1 := pong.Player{} err = json.Unmarshal(val, &p1) if err != nil { slog.Debug("error unmarshalling player1 update") } state.Player1 = p1 case "Player2": p2 := pong.Player{} err = json.Unmarshal(val, &p2) if err != nil { slog.Debug("error unmarshalling player2 update") } state.Player2 = p2 case "Ball": b := pong.Ball{} err = json.Unmarshal(val, &b) if err != nil { slog.Debug("error unmarshalling ball update") } state.Ball = b // Special update message that determines if the client is player1 or player2 case "isPlayer1": if update.Value[0] != 0 { isPlayer1 = true } } renderer.Render(state) } func handleGameInput(bytes []byte) { switch bytes[0] { // Up case 'w': if isPlayer1 { state.Player1.Pos.Y = state.Player1.Pos.Y + 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player1.Pos.Y, }) if err != nil { slog.Debug("error marshalling player movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player1.Pos", Value: v, } egress <- update } else { state.Player2.Pos.Y = state.Player2.Pos.Y + 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player2.Pos.Y, }) if err != nil { slog.Debug("error marshalling Player2 movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player2.Pos", Value: v, } egress <- update } return // Down case 's': if isPlayer1 { state.Player1.Pos.Y = state.Player1.Pos.Y - 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player1.Pos.Y, }) if err != nil { slog.Debug("error marshalling player movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player1.Pos", Value: v, } egress <- update } else { state.Player2.Pos.Y = state.Player2.Pos.Y - 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player2.Pos.Y, }) if err != nil { slog.Debug("error marshalling player movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player2.Pos", Value: v, } egress <- update } return // Quit case 'q': // Acts as a forfeit. Other player wins if isPlayer1 { update := pong.StateUpdate{ FieldPath: "Winner", Value: []byte("Player2"), } egress <- update } else { update := pong.StateUpdate{ FieldPath: "Winner", Value: []byte("Player1"), } egress <- update } quit <- 1 return // Esc char case 27: // Arrow Keys switch bytes[1] { // Up case 65: if isPlayer1 { state.Player1.Pos.Y = state.Player1.Pos.Y + 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player1.Pos.Y, }) if err != nil { slog.Debug("error marshalling player movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player1.Pos", Value: v, } egress <- update } else { state.Player2.Pos.Y = state.Player2.Pos.Y + 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player2.Pos.Y, }) if err != nil { slog.Debug("error marshalling Player2 movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player2.Pos", Value: v, } egress <- update } return // Down case 66: if isPlayer1 { state.Player1.Pos.Y = state.Player1.Pos.Y - 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player1.Pos.Y, }) if err != nil { slog.Debug("error marshalling player movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player1.Pos", Value: v, } egress <- update } else { state.Player2.Pos.Y = state.Player2.Pos.Y - 1 v, err := json.Marshal(pong.Vector{ X: 0, Y: state.Player2.Pos.Y, }) if err != nil { slog.Debug("error marshalling player movement", slog.Any("error", err)) } update := pong.StateUpdate{ FieldPath: "Player2.Pos", Value: v, } egress <- update } return } } }