sshpong/internal/client/game.go

317 lines
6.5 KiB
Go
Raw Normal View History

2024-09-26 19:49:59 -06:00
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
}
}
}