From e31dbef6eff61f0156601b3ad6a9ac346493a528 Mon Sep 17 00:00:00 2001 From: Beric Bearnson <37596980+bericyb@users.noreply.github.com> Date: Mon, 26 Aug 2024 10:02:05 -0600 Subject: [PATCH] start game logic and pong physics --- cmd/server/main.go | 1 + go.mod | 3 +- go.sum | 6 +- internal/netwrk/game.go | 35 ---- internal/netwrk/game_messages.pb.go | 152 --------------- internal/netwrk/lobby_messages.pb.go | 2 +- internal/netwrk/netwrk.go | 53 +++++- internal/pong/game_messages.pb.go | 233 +++++++++++++++++++++++ internal/pong/pong.go | 272 +++++++++++++++++++++++++++ internal/pong/state.go | 25 +++ proto/game_messages.proto | 15 +- 11 files changed, 591 insertions(+), 206 deletions(-) delete mode 100644 internal/netwrk/game.go delete mode 100644 internal/netwrk/game_messages.pb.go create mode 100644 internal/pong/game_messages.pb.go create mode 100644 internal/pong/pong.go create mode 100644 internal/pong/state.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 771c7dc..c630667 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -11,6 +11,7 @@ func main() { fmt.Println("Starting sshpong server!") netwrk.LobbyListen() + netwrk.GamesListen() _ = <-exit } diff --git a/go.mod b/go.mod index eaa812f..24f0fbe 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,8 @@ go 1.22.2 require ( github.com/google/uuid v1.6.0 - google.golang.org/protobuf v1.34.2 + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 golang.org/x/sys v0.23.0 // indirect golang.org/x/term v0.22.0 // indirect + google.golang.org/protobuf v1.34.2 ) diff --git a/go.sum b/go.sum index 17d040b..b977b25 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= diff --git a/internal/netwrk/game.go b/internal/netwrk/game.go deleted file mode 100644 index b1e2e8b..0000000 --- a/internal/netwrk/game.go +++ /dev/null @@ -1,35 +0,0 @@ -package netwrk - -import ( - "log" - "net" -) - -func handleGameConnection(conn net.Conn) { - defer conn.Close() - - messageBytes := make([]byte, 126) - - n, err := conn.Read(messageBytes) - if err != nil { - log.Printf("Error reading game ID on connection", err) - } - - _ = string(messageBytes[:n]) - if err != nil { - log.Printf("Game id was not a string?", err) - } - - _ = make(chan GameMessage) - - n, err = conn.Read(messageBytes) - if err != nil { - log.Printf("Error reading message %v", err) - return - } - -} - -func handleGameMessage(conn net.Conn, message *GameMessage) error { - return nil -} diff --git a/internal/netwrk/game_messages.pb.go b/internal/netwrk/game_messages.pb.go deleted file mode 100644 index 633a1a8..0000000 --- a/internal/netwrk/game_messages.pb.go +++ /dev/null @@ -1,152 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.34.2 -// protoc v5.27.1 -// source: proto/game_messages.proto - -package netwrk - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type GameMessage struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` - Value int32 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` -} - -func (x *GameMessage) Reset() { - *x = GameMessage{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_game_messages_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *GameMessage) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GameMessage) ProtoMessage() {} - -func (x *GameMessage) ProtoReflect() protoreflect.Message { - mi := &file_proto_game_messages_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GameMessage.ProtoReflect.Descriptor instead. -func (*GameMessage) Descriptor() ([]byte, []int) { - return file_proto_game_messages_proto_rawDescGZIP(), []int{0} -} - -func (x *GameMessage) GetAction() string { - if x != nil { - return x.Action - } - return "" -} - -func (x *GameMessage) GetValue() int32 { - if x != nil { - return x.Value - } - return 0 -} - -var File_proto_game_messages_proto protoreflect.FileDescriptor - -var file_proto_game_messages_proto_rawDesc = []byte{ - 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6e, 0x65, 0x74, - 0x77, 0x72, 0x6b, 0x22, 0x3b, 0x0a, 0x0b, 0x47, 0x61, 0x6d, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x72, 0x6b, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_proto_game_messages_proto_rawDescOnce sync.Once - file_proto_game_messages_proto_rawDescData = file_proto_game_messages_proto_rawDesc -) - -func file_proto_game_messages_proto_rawDescGZIP() []byte { - file_proto_game_messages_proto_rawDescOnce.Do(func() { - file_proto_game_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_game_messages_proto_rawDescData) - }) - return file_proto_game_messages_proto_rawDescData -} - -var file_proto_game_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_proto_game_messages_proto_goTypes = []any{ - (*GameMessage)(nil), // 0: netwrk.GameMessage -} -var file_proto_game_messages_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_proto_game_messages_proto_init() } -func file_proto_game_messages_proto_init() { - if File_proto_game_messages_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_proto_game_messages_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*GameMessage); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_proto_game_messages_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_proto_game_messages_proto_goTypes, - DependencyIndexes: file_proto_game_messages_proto_depIdxs, - MessageInfos: file_proto_game_messages_proto_msgTypes, - }.Build() - File_proto_game_messages_proto = out.File - file_proto_game_messages_proto_rawDesc = nil - file_proto_game_messages_proto_goTypes = nil - file_proto_game_messages_proto_depIdxs = nil -} diff --git a/internal/netwrk/lobby_messages.pb.go b/internal/netwrk/lobby_messages.pb.go index 2010eed..0f27cdb 100644 --- a/internal/netwrk/lobby_messages.pb.go +++ b/internal/netwrk/lobby_messages.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.34.2 -// protoc v5.27.1 +// protoc v5.27.3 // source: proto/lobby_messages.proto package netwrk diff --git a/internal/netwrk/netwrk.go b/internal/netwrk/netwrk.go index fa62c20..a1534f8 100644 --- a/internal/netwrk/netwrk.go +++ b/internal/netwrk/netwrk.go @@ -3,6 +3,8 @@ package netwrk import ( "log" "net" + "sshpong/internal/pong" + "strings" sync "sync" "google.golang.org/protobuf/proto" @@ -13,27 +15,26 @@ type Client struct { Conn net.Conn } -type LobbyPlayersMessage struct { - Type string - Username string - IsAvailable chan bool -} - type ExternalMessage struct { Target string Message *LobbyMessage } -var lobbyListener chan LobbyPlayersMessage +type GameClients struct { + Client1 Client + Client2 Client +} + var externalMessageChan chan ExternalMessage var lobbyMembers sync.Map +var games sync.Map func init() { - lobbyListener = make(chan LobbyPlayersMessage) externalMessageChan = make(chan ExternalMessage) lobbyMembers = sync.Map{} + games = sync.Map{} go func() { for { @@ -76,7 +77,6 @@ func LobbyListen() { } func GamesListen() { - gameListener, err := net.Listen("tcp", "127.0.0.1:42069") if err != nil { log.Fatal(err) @@ -89,6 +89,39 @@ func GamesListen() { log.Println(err) continue } - handleGameConnection(conn) + + go func(conn net.Conn) { + messageBytes := make([]byte, 126) + + n, err := conn.Read(messageBytes) + if err != nil { + log.Printf("Error reading game ID on connection %s", err) + } + + gInfo := strings.SplitAfter(string(messageBytes[:n]), ":") + if err != nil { + log.Printf("Game id was not a string? %s", err) + } + + game, ok := games.Load(gInfo[0]) + if !ok { + games.Store(gInfo[0], GameClients{Client1: Client{ + Username: gInfo[1], + Conn: conn, + }, Client2: Client{}}) + } else { + gameclients, _ := game.(GameClients) + client2 := Client{ + Username: gInfo[1], + Conn: conn, + } + + games.Store(gInfo[0], GameClients{ + Client1: gameclients.Client1, + Client2: client2}) + + go pong.StartGame(gameclients.Client1.Conn, client2.Conn, gameclients.Client1.Username, client2.Username) + } + }(conn) } } diff --git a/internal/pong/game_messages.pb.go b/internal/pong/game_messages.pb.go new file mode 100644 index 0000000..20879be --- /dev/null +++ b/internal/pong/game_messages.pb.go @@ -0,0 +1,233 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v5.27.3 +// source: proto/game_messages.proto + +package pong + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ServerUpdateMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ServerUpdateMessage) Reset() { + *x = ServerUpdateMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_game_messages_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServerUpdateMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerUpdateMessage) ProtoMessage() {} + +func (x *ServerUpdateMessage) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_messages_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerUpdateMessage.ProtoReflect.Descriptor instead. +func (*ServerUpdateMessage) Descriptor() ([]byte, []int) { + return file_proto_game_messages_proto_rawDescGZIP(), []int{0} +} + +func (x *ServerUpdateMessage) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ServerUpdateMessage) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type ClientUpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + Player int32 `protobuf:"zigzag32,3,opt,name=player,proto3" json:"player,omitempty"` +} + +func (x *ClientUpdateRequest) Reset() { + *x = ClientUpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_game_messages_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientUpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientUpdateRequest) ProtoMessage() {} + +func (x *ClientUpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_game_messages_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientUpdateRequest.ProtoReflect.Descriptor instead. +func (*ClientUpdateRequest) Descriptor() ([]byte, []int) { + return file_proto_game_messages_proto_rawDescGZIP(), []int{1} +} + +func (x *ClientUpdateRequest) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ClientUpdateRequest) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +func (x *ClientUpdateRequest) GetPlayer() int32 { + if x != nil { + return x.Player + } + return 0 +} + +var File_proto_game_messages_proto protoreflect.FileDescriptor + +var file_proto_game_messages_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x70, 0x6f, 0x6e, + 0x67, 0x22, 0x3f, 0x0a, 0x13, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x22, 0x57, 0x0a, 0x13, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x11, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x42, 0x08, 0x5a, 0x06, 0x2e, + 0x2f, 0x70, 0x6f, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_game_messages_proto_rawDescOnce sync.Once + file_proto_game_messages_proto_rawDescData = file_proto_game_messages_proto_rawDesc +) + +func file_proto_game_messages_proto_rawDescGZIP() []byte { + file_proto_game_messages_proto_rawDescOnce.Do(func() { + file_proto_game_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_game_messages_proto_rawDescData) + }) + return file_proto_game_messages_proto_rawDescData +} + +var file_proto_game_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_game_messages_proto_goTypes = []any{ + (*ServerUpdateMessage)(nil), // 0: pong.ServerUpdateMessage + (*ClientUpdateRequest)(nil), // 1: pong.ClientUpdateRequest +} +var file_proto_game_messages_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_game_messages_proto_init() } +func file_proto_game_messages_proto_init() { + if File_proto_game_messages_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_game_messages_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*ServerUpdateMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_game_messages_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*ClientUpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_game_messages_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_proto_game_messages_proto_goTypes, + DependencyIndexes: file_proto_game_messages_proto_depIdxs, + MessageInfos: file_proto_game_messages_proto_msgTypes, + }.Build() + File_proto_game_messages_proto = out.File + file_proto_game_messages_proto_rawDesc = nil + file_proto_game_messages_proto_goTypes = nil + file_proto_game_messages_proto_depIdxs = nil +} diff --git a/internal/pong/pong.go b/internal/pong/pong.go new file mode 100644 index 0000000..1bb05f3 --- /dev/null +++ b/internal/pong/pong.go @@ -0,0 +1,272 @@ +package pong + +import ( + "fmt" + "log" + "net" + "strconv" + "strings" + "time" + + "golang.org/x/exp/rand" + "google.golang.org/protobuf/proto" +) + +type GameClient struct { + Username string + Conn net.Conn +} + +var player1 GameClient +var player2 GameClient + +var ingress chan *ClientUpdateRequest +var egress chan *ServerUpdateMessage + +const posXBound = 50 +const negXBound = posXBound * -1 +const posYBound = 50 +const negYBound = posYBound * -1 + +func StartGame(conn1, conn2 net.Conn, username1, username2 string) { + player1 = GameClient{ + Username: username1, + Conn: conn1, + } + player2 = GameClient{ + Username: username2, + Conn: conn2, + } + + time.Sleep(1 * time.Second) + broadcastUpdate(&ServerUpdateMessage{ + Type: "message", + Value: "Ready...", + }) + time.Sleep(1 * time.Second) + broadcastUpdate(&ServerUpdateMessage{ + Type: "message", + Value: "Set...", + }) + time.Sleep(1 * time.Second) + broadcastUpdate(&ServerUpdateMessage{ + Type: "message", + Value: "Go!", + }) + time.Sleep(1 * time.Second) + bv := float32(rand.Intn(2)*2 - 1) + + state := GameState{ + Score: map[string]int{player1.Username: 0, player2.Username: 0}, + Player1: Player{ + client: player1, + Pos: Vector{ + X: -50, + Y: 0, + }, + Size: Vector{ + X: 1, + Y: 10, + }, + Speed: 0, + }, + Player2: Player{ + client: player2, + Pos: Vector{ + X: 50, + Y: 0, + }, + Size: Vector{ + X: 1, + Y: 10, + }, + Speed: 0, + }, + Ball: Ball{ + Pos: Vector{ + X: 0, + Y: 0, + }, + Vel: Vector{ + X: bv, + Y: 0, + }, + }, + } + go gameLoop(state) +} + +func gameLoop(state GameState) { + // Player 1 read loop + go func() { + for { + + bytes := make([]byte, 512) + n, err := state.Player1.client.Conn.Read(bytes) + msg := &ClientUpdateRequest{} + err = proto.Unmarshal(bytes[:n], msg) + if err != nil { + log.Println("error reading player 1's update request:", err) + return + } + msg.Player = 1 + ingress <- msg + } + }() + + // Player 2 read loop + go func() { + for { + + bytes := make([]byte, 512) + n, err := state.Player2.client.Conn.Read(bytes) + msg := &ClientUpdateRequest{} + err = proto.Unmarshal(bytes[:n], msg) + if err != nil { + log.Println("error reading player 2's update request:", err) + return + } + msg.Player = 2 + ingress <- msg + } + }() + + go func() { + for { + msg := <-egress + broadcastUpdate(msg) + } + }() + + ticker := time.NewTicker(time.Second / 64) + + for { + select { + case msg := <-ingress: + err := handlePlayerRequest(&state, msg) + if err != nil { + fmt.Println("FUCK!~", err) + } + + case _ = <-ticker.C: + update := process(&state) + egress <- &update + } + + } +} + +func process(state *GameState) ServerUpdateMessage { + + // Move ball + // Check if ball is out of bounds + // if out of bounds y, + // bounce by inverting y velocity and finding difference from bounds to out and reflect distance + // if out of bounds x, + // check if paddle is nearby, bounce by inverting and finding the remaining distance to the new position. + // or adjust score and ball position + + state.Ball.Pos.X = state.Ball.Pos.X + state.Ball.Vel.X + state.Ball.Pos.Y = state.Ball.Pos.Y + state.Ball.Vel.Y + + if state.Ball.Pos.Y >= posYBound-1 && state.Ball.Vel.Y > 0 { + state.Ball.Pos.Y = (posYBound - 1) - (state.Ball.Pos.Y - (posYBound - 1)) + state.Ball.Vel.Y = state.Ball.Vel.Y * -1 + } + if state.Ball.Pos.Y <= negYBound+1 && state.Ball.Vel.Y < 0 { + state.Ball.Pos.Y = (negYBound + 1) - (state.Ball.Pos.Y - (negYBound + 1)) + state.Ball.Vel.Y = state.Ball.Vel.Y * -1 + } + + // If the ball is within 1 pixel of x bounds and heading to the left (Player 1) + if state.Ball.Pos.X <= negXBound+1 && state.Ball.Vel.X < 0 { + // Paddle hit! + if state.Ball.Pos.Y <= state.Player1.Pos.Y+state.Player1.Size.Y && state.Ball.Pos.Y >= state.Player1.Pos.Y-state.Player1.Size.Y { + state.Ball.Pos.X = (negXBound + 1) - (state.Ball.Pos.X - (negXBound + 1)) + state.Ball.Vel.X = state.Ball.Vel.X * -1.001 + angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2) + state.Ball.Vel.Y = state.Ball.Vel.Y * angleTweak + } else { + state.Ball.Pos.X = 0 + state.Ball.Pos.Y = 0 + state.Ball.Vel.X = 1 + state.Ball.Vel.Y = 0 + if state.Score[player2.Username] >= 9 { + return ServerUpdateMessage{ + Type: "gameover", + Value: player2.Username, + } + } + state.Score[player2.Username] = state.Score[player2.Username] + 1 + } + } + + // If the ball is within 1 pixel of x bounds and heading towards player2 (to the right) + if state.Ball.Pos.X > posXBound-1 && state.Ball.Vel.X > 0 { + // Paddle hit! + if state.Ball.Pos.Y <= state.Player2.Pos.Y+state.Player2.Size.Y && state.Ball.Pos.Y >= state.Player2.Pos.Y-state.Player2.Size.Y { + state.Ball.Pos.X = (posXBound - 1) - (state.Ball.Pos.X - (posXBound - 1)) + state.Ball.Vel.X = state.Ball.Vel.X * -1.001 + angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2) + state.Ball.Vel.Y = state.Ball.Vel.Y * angleTweak + } else { + state.Ball.Pos.X = 0 + state.Ball.Pos.Y = 0 + state.Ball.Vel.X = -1 + state.Ball.Vel.Y = 0 + if state.Score[player1.Username] >= 9 { + return ServerUpdateMessage{ + Type: "gameover", + Value: player1.Username, + } + } + state.Score[player1.Username] = state.Score[player1.Username] + 1 + } + } + + return ServerUpdateMessage{} +} + +func handlePlayerRequest(state *GameState, msg *ClientUpdateRequest) error { + + switch msg.Type { + case "player_pos": + if msg.Player == 1 { + pos := strings.Split(msg.Value, " ") + x, err := strconv.ParseFloat(pos[0], 32) + if err != nil { + fmt.Println("Got weird position update for x", err) + } + y, err := strconv.ParseFloat(pos[1], 32) + if err != nil { + fmt.Println("Got weird position update for y", err) + } + + state.Player1.Pos = Vector{ + X: float32(x), + Y: float32(y), + } + } + default: + fmt.Println("Got unhandled update", msg.Type) + } + + return nil +} + +func broadcastUpdate(update *ServerUpdateMessage) error { + msg, err := proto.Marshal(update) + if err != nil { + return fmt.Errorf("malformed server update message %v", err) + } + _, err = player1.Conn.Write(msg) + if err != nil { + return err + } + _, err = player2.Conn.Write(msg) + if err != nil { + return err + } + + return nil +} diff --git a/internal/pong/state.go b/internal/pong/state.go new file mode 100644 index 0000000..58f5a5e --- /dev/null +++ b/internal/pong/state.go @@ -0,0 +1,25 @@ +package pong + +type GameState struct { + Score map[string]int + Player1 Player + Player2 Player + Ball Ball +} + +type Vector struct { + X float32 + Y float32 +} + +type Player struct { + client GameClient + Pos Vector + Size Vector + Speed float32 +} + +type Ball struct { + Pos Vector + Vel Vector +} diff --git a/proto/game_messages.proto b/proto/game_messages.proto index a3f31ba..40f996e 100644 --- a/proto/game_messages.proto +++ b/proto/game_messages.proto @@ -1,10 +1,15 @@ syntax = "proto3"; -package netwrk; +package pong; -option go_package = "./netwrk"; +option go_package = "./pong"; -message GameMessage { - string action = 1; - int32 value = 2; +message ServerUpdateMessage { + string type = 1; + string value = 2; +} +message ClientUpdateRequest { + string type = 1; + string value = 2; + sint32 player = 3; }