middle of the re-work

This commit is contained in:
Beric Bearnson
2024-09-26 19:49:59 -06:00
parent e31dbef6ef
commit f2d488ef51
15 changed files with 1347 additions and 754 deletions
+139 -53
View File
@@ -1,9 +1,11 @@
package client
import (
"errors"
"fmt"
"io"
"sshpong/internal/netwrk"
"log/slog"
"sshpong/internal/lobby"
"strings"
)
@@ -14,113 +16,197 @@ type InterrupterMessage struct {
var help = fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
func HandleUserInput(args []string) (*netwrk.LobbyMessage, error) {
var red = "\x1b[31m"
var normal = "\033[0m"
func HandleUserInput(args []string, username string) (lobby.LobbyMessage, error) {
if len(args) == 0 {
return nil, help
return lobby.LobbyMessage{}, help
}
switch args[0] {
case "invite":
if args[1] != "" {
return &netwrk.LobbyMessage{
Type: "invite",
Content: args[1],
}, nil
return lobby.LobbyMessage{
MessageType: "invite",
Message: lobby.Invite{From: username, To: args[1]}}, nil
} else {
fmt.Println("Please provide a player to invite ")
}
case "chat":
if args[1] != "" {
return &netwrk.LobbyMessage{
Type: "chat",
Content: strings.Join(args[1:], " "),
return lobby.LobbyMessage{
MessageType: "chat",
Message: lobby.Chat{
From: username,
Message: args[1],
},
}, nil
}
case "/":
if args[1] != "" {
return &netwrk.LobbyMessage{
Type: "chat",
Content: strings.Join(args[1:], " "),
return lobby.LobbyMessage{
MessageType: "chat",
Message: lobby.Chat{
From: username,
Message: args[1],
},
}, nil
}
case "quit":
return nil, io.EOF
return lobby.LobbyMessage{}, io.EOF
case "q":
return nil, io.EOF
return lobby.LobbyMessage{}, io.EOF
case "help":
return nil, help
return lobby.LobbyMessage{}, help
case "h":
return nil, help
return lobby.LobbyMessage{}, help
default:
if strings.Index(args[0], "/") == 0 {
return &netwrk.LobbyMessage{
Type: "chat",
Content: strings.Join(args, " ")[1:],
return lobby.LobbyMessage{
MessageType: "chat",
Message: lobby.Chat{
From: username,
Message: args[1],
},
}, nil
}
return nil, help
return lobby.LobbyMessage{}, help
}
return nil, nil
return lobby.LobbyMessage{}, nil
}
func HandleInterruptInput(incoming InterrupterMessage, args []string) (*netwrk.LobbyMessage, error) {
func HandleInterruptInput(incoming InterrupterMessage, args []string, username string) (lobby.LobbyMessage, error) {
switch incoming.InterruptType {
case "invite":
if len(args) < 1 {
return &netwrk.LobbyMessage{
Type: "decline",
Content: incoming.Content,
return lobby.LobbyMessage{
MessageType: "decline",
Message: lobby.Decline{
From: username,
To: incoming.Content,
},
}, nil
} else {
if strings.ToLower(args[0]) == "y" || strings.ToLower(args[0]) == "yes" {
return &netwrk.LobbyMessage{Type: "accept", Content: incoming.Content}, nil
return lobby.LobbyMessage{MessageType: "accept", Message: lobby.Accept{
From: username,
To: incoming.Content,
},
}, nil
}
}
// Cancel waiting for invite?
case "decline":
// // Cancel waiting for invite? we aren't doing this I guess.
// case "decline":
// return nil,
// Disconnect and connect to game
case "accepted":
return &netwrk.LobbyMessage{
Type: "disconnect",
Content: "",
return lobby.LobbyMessage{
MessageType: "disconnect",
Message: lobby.Disconnect{
From: incoming.Content,
},
}, nil
case "start_game":
return lobby.LobbyMessage{
MessageType: "start_game",
Message: lobby.StartGame{GameID: incoming.Content},
}, nil
default:
return nil, fmt.Errorf("received a interrupt message that could not be handled %v", incoming)
}
return nil, nil
return lobby.LobbyMessage{}, fmt.Errorf("received a interrupt message that could not be handled %v", incoming)
}
func HandleServerMessage(message *netwrk.LobbyMessage) (InterrupterMessage, error) {
switch message.Type {
func HandleServerMessage(message lobby.LobbyMessage) (InterrupterMessage, error) {
msg := message.Message
switch message.MessageType {
case "name":
fmt.Printf("Current Players\n%s\n", message.Content)
nmsg, ok := msg.(lobby.Name)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted name message")
}
fmt.Printf("Current Players\n%s\n", nmsg)
case "invite":
fmt.Println(message.PlayerId, "is inviting you to a game\nType y to accept...")
imsg, ok := msg.(lobby.Invite)
if !ok {
return InterrupterMessage{}, errors.New("Not a propertly formatted invite message")
}
fmt.Println(imsg.From, "is inviting you to a game\nType y to accept...")
return InterrupterMessage{
InterruptType: "invite",
Content: message.PlayerId,
Content: imsg.From,
}, nil
case "pending_invite":
fmt.Println("Invite sent to", message.Content, "\nWaiting for response...")
pimsg, ok := msg.(lobby.PendingInvite)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted pending invite message")
}
fmt.Println("Invite sent to", pimsg.Recipient, "\nWaiting for response...")
case "accepted":
fmt.Println(message.PlayerId, "accepted your invite.\n", "Starting game...")
case "game_start":
fmt.Println("Invited accepted\n", "Starting game...")
case "text":
fmt.Println(message.PlayerId, ":", message.Content)
case "decline_game":
fmt.Println(message.Content, "declined your game invite")
amsg, ok := msg.(lobby.Accepted)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted accepted message")
}
fmt.Println(amsg.Accepter, "accepted your invite.", "Press Enter to connect to game...")
return InterrupterMessage{
InterruptType: "start_game",
Content: amsg.GameID,
}, nil
case "start_game":
sgmsg, ok := msg.(lobby.StartGame)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted start game message")
}
return InterrupterMessage{
InterruptType: "start_game",
Content: sgmsg.GameID,
}, nil
case "chat":
cmsg, ok := msg.(lobby.Chat)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted chat message")
}
fmt.Println(cmsg.From, ":", cmsg.Message)
case "decline":
dmsg, ok := msg.(lobby.Decline)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted decline message")
}
fmt.Println(dmsg.From, "declined your game invite")
case "disconnect":
fmt.Println(message.Content, "has disconnected")
dmsg, ok := msg.(lobby.Disconnect)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formatted disconnect message")
}
fmt.Println(dmsg.From, "has disconnected")
case "connect":
fmt.Println(message.Content, "has connected")
cmsg, ok := msg.(lobby.Connect)
if !ok {
return InterrupterMessage{}, errors.New("Not a properly formated connect message")
}
fmt.Println(cmsg.From, "has connected")
case "pong":
fmt.Println("Received", message.Content)
fmt.Println("Received pong")
case "error":
em, ok := msg.(lobby.Error)
if !ok {
slog.Debug("Received an indecipherable error message...", slog.Any("msg", msg))
}
fmt.Println(red, em.Message, normal)
default:
fmt.Println("Received", message.Content)
fmt.Println("Received", message.MessageType, message.Message)
}
return InterrupterMessage{}, nil
}
+316
View File
@@ -0,0 +1,316 @@
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
}
}
}