From ca681f08cd6fb1cfe044e391fd5651701bc74f36 Mon Sep 17 00:00:00 2001 From: iegod Date: Tue, 10 Dec 2024 18:55:23 -0500 Subject: [PATCH] Client initial commit. --- client/.vscode/launch.json | 15 +++ client/client/client.go | 68 ++++++++++++ client/client/identity.go | 7 ++ client/elements/block.go | 38 +++++++ client/fonts/bitbybit.ttf | Bin 0 -> 121220 bytes client/fonts/fonts.go | 81 ++++++++++++++ client/fonts/vcrmono.ttf | Bin 0 -> 75864 bytes client/game/game.go | 175 +++++++++++++++++++++++++++++++ client/gamedata/gamedata.go | 6 ++ client/go.mod | 22 ++++ client/main.go | 20 ++++ server/.vscode/launch.json | 15 +++ server/client/client.go | 8 ++ server/clientdata.pb.go | 204 ++++++++++++++++++++++++++++++++++++ server/clientdata.proto | 15 +++ server/commands/command.go | 4 + server/gamedata/gamedata.go | 6 ++ server/go.mod | 8 ++ server/main.go | 13 +++ server/server/server.go | 165 +++++++++++++++++++++++++++++ 20 files changed, 870 insertions(+) create mode 100644 client/.vscode/launch.json create mode 100644 client/client/client.go create mode 100644 client/client/identity.go create mode 100644 client/elements/block.go create mode 100644 client/fonts/bitbybit.ttf create mode 100644 client/fonts/fonts.go create mode 100644 client/fonts/vcrmono.ttf create mode 100644 client/game/game.go create mode 100644 client/gamedata/gamedata.go create mode 100644 client/go.mod create mode 100644 client/main.go create mode 100644 server/.vscode/launch.json create mode 100644 server/client/client.go create mode 100644 server/clientdata.pb.go create mode 100644 server/clientdata.proto create mode 100644 server/commands/command.go create mode 100644 server/gamedata/gamedata.go create mode 100644 server/go.mod create mode 100644 server/main.go create mode 100644 server/server/server.go diff --git a/client/.vscode/launch.json b/client/.vscode/launch.json new file mode 100644 index 0000000..6deb9a6 --- /dev/null +++ b/client/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "main.go" + } + ] +} \ No newline at end of file diff --git a/client/client/client.go b/client/client/client.go new file mode 100644 index 0000000..c1f6e7b --- /dev/null +++ b/client/client/client.go @@ -0,0 +1,68 @@ +package client + +import ( + "bufio" + "fmt" + "net" +) + +type Client struct { + conn net.Conn + connected bool +} + +func NewClient() *Client { + c := &Client{ + connected: false, + } + + //conn, err := net.Dial("tcp", "localhost:501") + conn, err := net.Dial("tcp", "192.168.5.100:501") + if err != nil { + fmt.Println("Error connecting to server:", err) + } else { + c.connected = true + } + + c.conn = conn + return c +} + +func (c *Client) SendData(msg string) { + // Send input to the server + //fmt.Fprintf(c.conn, msg) + _, err := c.conn.Write([]byte(msg)) + if err != nil { + fmt.Println("Error writing to connection:", err) + return + } +} + +func (c *Client) ReadData(callback func(string)) { + + for { + message, err := bufio.NewReader(c.conn).ReadString('\n') + if err != nil { + fmt.Println("Error reading from server:", err) + return + } + + if callback != nil { + callback(message) + } + } + +} + +func (c *Client) GetLocalAddr() string { + return c.conn.LocalAddr().String() +} + +func (c *Client) IsConnected() bool { + return c.connected +} + +func (c *Client) Disconnect() { + c.conn.Close() + c.connected = false +} diff --git a/client/client/identity.go b/client/client/identity.go new file mode 100644 index 0000000..a5dfd3e --- /dev/null +++ b/client/client/identity.go @@ -0,0 +1,7 @@ +package client + +import "net" + +type Identity struct { + conn net.Conn +} diff --git a/client/elements/block.go b/client/elements/block.go new file mode 100644 index 0000000..6eb81a7 --- /dev/null +++ b/client/elements/block.go @@ -0,0 +1,38 @@ +package elements + +import ( + "client/gamedata" + "image/color" + + "github.com/hajimehoshi/ebiten/v2" +) + +type Block struct { + Sprite *ebiten.Image + cycle int + position gamedata.Coordinates +} + +func NewBlock() *Block { + return &Block{ + Sprite: ebiten.NewImage(20, 20), + cycle: 0, + } +} + +func (b *Block) Update() { + b.cycle++ +} + +func (b *Block) Draw() { + b.Sprite.Clear() + b.Sprite.Fill(color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0x00}) +} + +func (b *Block) SetPosition(pos gamedata.Coordinates) { + b.position = pos +} + +func (b *Block) GetPosition() gamedata.Coordinates { + return b.position +} diff --git a/client/fonts/bitbybit.ttf b/client/fonts/bitbybit.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f2222e1c5fb6d55db8fe524e0b7898a78d610562 GIT binary patch literal 121220 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�KEhpc8O>tpZXY(C- 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 jzmix#%d&qw|nnqAe| literal 0 HcmV?d00001 diff --git a/client/fonts/fonts.go b/client/fonts/fonts.go new file mode 100644 index 0000000..49d4e1b --- /dev/null +++ b/client/fonts/fonts.go @@ -0,0 +1,81 @@ +package fonts + +import ( + "bytes" + "log" + + _ "embed" + + "github.com/hajimehoshi/ebiten/v2/text/v2" + "golang.org/x/image/font" + "golang.org/x/image/font/opentype" + "golang.org/x/image/font/sfnt" +) + +const ( + FontDPI = 72 + FontSizeStandard = 16 + FontSizeLarge = 24 +) + +type FontStruct struct { + Standard font.Face + Large font.Face + New *text.GoTextFaceSource + Bitfont *text.GoTextFaceSource +} + +var ( + + //go:embed vcrmono.ttf + vcrmono_ttf []byte + //go:embed bitbybit.ttf + bitbybit_ttf []byte + + LaunchyFont FontStruct +) + +func LoadFontFatal(src []byte) *sfnt.Font { + tt, err := opentype.Parse(src) + if err != nil { + log.Fatal(err) + } + return tt +} + +func GetFaceFatal(fnt *sfnt.Font, dpi, size float64) font.Face { + var face font.Face + var err error + + if dpi > 0 && size > 0 && fnt != nil { + face, err = opentype.NewFace(fnt, &opentype.FaceOptions{ + Size: size, + DPI: dpi, + Hinting: font.HintingVertical, + }) + if err != nil { + log.Fatal(err) + } + } + return face +} + +func init() { + LaunchyFont = FontStruct{} + + fnt := LoadFontFatal(vcrmono_ttf) + LaunchyFont.Standard = GetFaceFatal(fnt, FontDPI, FontSizeStandard) + LaunchyFont.Large = GetFaceFatal(fnt, FontDPI, FontSizeLarge) + + s, err := text.NewGoTextFaceSource(bytes.NewReader(vcrmono_ttf)) + if err != nil { + log.Fatal(err) + } + LaunchyFont.New = s + + s, err = text.NewGoTextFaceSource(bytes.NewReader(bitbybit_ttf)) + if err != nil { + log.Fatal(err) + } + LaunchyFont.Bitfont = s +} diff --git a/client/fonts/vcrmono.ttf b/client/fonts/vcrmono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..dcca687a434d5c7b6a3027e65e0b7d8728b25c71 GIT binary patch literal 75864 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 literal 0 HcmV?d00001 diff --git a/client/game/game.go b/client/game/game.go new file mode 100644 index 0000000..09e80ad --- /dev/null +++ b/client/game/game.go @@ -0,0 +1,175 @@ +package game + +import ( + "client/client" + "client/elements" + "client/fonts" + "client/gamedata" + "fmt" + "maps" + "strconv" + "strings" + "sync" + "time" + + "github.com/hajimehoshi/ebiten/v2" + "github.com/hajimehoshi/ebiten/v2/text/v2" + "golang.org/x/exp/rand" +) + +var ( + screenWidth = 640 + screenHeight = 480 + + namelist = []string{"slappy", "mick", "rodney", "george", "ringo", "robin", "temitry "} +) + +func init() { + rand.Seed(uint64(time.Now().UnixNano())) +} + +type ClientData struct { + Address string + Name string + Position gamedata.Coordinates +} + +type Game struct { + name string + blocky *elements.Block + + //players map[client.Identity] + + clients map[string]ClientData + + gameclient *client.Client + cycle int + + position gamedata.Coordinates + + mu sync.Mutex +} + +func NewGame() *Game { + g := &Game{ + gameclient: client.NewClient(), + blocky: elements.NewBlock(), + cycle: 0, + name: namelist[rand.Intn(len(namelist))], + } + g.clients = make(map[string]ClientData) + go g.gameclient.ReadData(g.HandleServerData) + return g +} + +func (g *Game) Update() error { + + x, y := ebiten.CursorPosition() + g.position.X = float64(x) + g.position.Y = float64(y) + + g.blocky.SetPosition(g.position) + + //broadcast our position + if g.cycle%2 == 0 { + if g.gameclient.IsConnected() { + g.gameclient.SendData(fmt.Sprintf("%s,%.0f,%.0f\n", g.name, g.position.X, g.position.Y)) + } + } + + //cleanup client list every 2 seconds + if g.cycle%120 == 0 { + go g.CleanupClients() + } + + g.cycle++ + return nil +} + +func (g *Game) Draw(screen *ebiten.Image) { + screen.Clear() + + g.blocky.Draw() + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(-float64(g.blocky.Sprite.Bounds().Dx())/2, -float64(g.blocky.Sprite.Bounds().Dy())/2) + op.GeoM.Translate(g.blocky.GetPosition().X, g.blocky.GetPosition().Y) + screen.DrawImage(g.blocky.Sprite, op) + f2 := &text.GoTextFace{ + Source: fonts.LaunchyFont.New, + Size: 12, + } + top := &text.DrawOptions{} + top.GeoM.Translate(g.blocky.GetPosition().X-50, g.blocky.GetPosition().Y+15) + text.Draw(screen, "you ("+g.name+")", f2, top) + + g.mu.Lock() + clientcopy := maps.Clone(g.clients) + g.mu.Unlock() + for _, client := range clientcopy { + op := &ebiten.DrawImageOptions{} + op.GeoM.Translate(-float64(g.blocky.Sprite.Bounds().Dx())/2, -float64(g.blocky.Sprite.Bounds().Dy())/2) + op.GeoM.Translate(client.Position.X, client.Position.Y) + screen.DrawImage(g.blocky.Sprite, op) + + f2 := &text.GoTextFace{ + Source: fonts.LaunchyFont.New, + Size: 12, + } + top := &text.DrawOptions{} + top.GeoM.Translate(client.Position.X, client.Position.Y) + text.Draw(screen, client.Name, f2, top) + } + +} + +func (g *Game) Layout(outsideWidth, outsideHeight int) (screenwidth, screenheight int) { + return screenWidth, screenHeight +} + +func (g *Game) HandleServerData(data string) { + //log.Println(data) + + raw := data[1 : len(data)-1] + clientinfo := strings.Split(raw, ";") + for _, info := range clientinfo { + subdata := strings.Split(info, ",") + + if len(subdata) == 4 { + + if g.gameclient.GetLocalAddr() != subdata[0] { + + x, err := strconv.Atoi(subdata[2]) + if err != nil { + x = 0 + } + + y, err := strconv.Atoi(subdata[3]) + if err != nil { + y = 0 + } + + update := ClientData{ + Address: subdata[0], + Name: subdata[1], + Position: gamedata.Coordinates{ + X: float64(x), + Y: float64(y), + }, + } + + g.mu.Lock() + g.clients[update.Address] = update + g.mu.Unlock() + } + } + } + +} + +func (g *Game) CleanupClients() { + g.mu.Lock() + for k := range g.clients { + delete(g.clients, k) + } + g.mu.Unlock() +} diff --git a/client/gamedata/gamedata.go b/client/gamedata/gamedata.go new file mode 100644 index 0000000..5db1cc6 --- /dev/null +++ b/client/gamedata/gamedata.go @@ -0,0 +1,6 @@ +package gamedata + +type Coordinates struct { + X float64 `json:"X"` + Y float64 `json:"Y"` +} diff --git a/client/go.mod b/client/go.mod new file mode 100644 index 0000000..86783eb --- /dev/null +++ b/client/go.mod @@ -0,0 +1,22 @@ +module client + +go 1.22.0 + +toolchain go1.22.10 + +require ( + github.com/hajimehoshi/ebiten/v2 v2.8.5 + golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d + golang.org/x/image v0.20.0 +) + +require ( + github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 // indirect + github.com/ebitengine/hideconsole v1.0.0 // indirect + github.com/ebitengine/purego v0.8.0 // indirect + github.com/go-text/typesetting v0.2.0 // indirect + github.com/jezek/xgb v1.1.1 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect +) diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..b6ab5ce --- /dev/null +++ b/client/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "client/game" + "log" + + "github.com/hajimehoshi/ebiten/v2" +) + +func main() { + + game := game.NewGame() + ebiten.SetWindowSize(640*1.5, 480*1.5) + ebiten.SetWindowTitle("game") + + err := ebiten.RunGame(game) + if err != nil { + log.Fatal(err) + } +} diff --git a/server/.vscode/launch.json b/server/.vscode/launch.json new file mode 100644 index 0000000..6deb9a6 --- /dev/null +++ b/server/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "main.go" + } + ] +} \ No newline at end of file diff --git a/server/client/client.go b/server/client/client.go new file mode 100644 index 0000000..b4459f2 --- /dev/null +++ b/server/client/client.go @@ -0,0 +1,8 @@ +package client + +import "net" + +type Client struct { + Name string + Connection net.Conn +} diff --git a/server/clientdata.pb.go b/server/clientdata.pb.go new file mode 100644 index 0000000..a785bd8 --- /dev/null +++ b/server/clientdata.pb.go @@ -0,0 +1,204 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc v5.29.1 +// source: clientdata.proto + +package __ + +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 ClientData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"` + Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` + Coordinates *ClientData_Coordinates `protobuf:"bytes,4,opt,name=coordinates,proto3" json:"coordinates,omitempty"` +} + +func (x *ClientData) Reset() { + *x = ClientData{} + mi := &file_clientdata_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientData) ProtoMessage() {} + +func (x *ClientData) ProtoReflect() protoreflect.Message { + mi := &file_clientdata_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientData.ProtoReflect.Descriptor instead. +func (*ClientData) Descriptor() ([]byte, []int) { + return file_clientdata_proto_rawDescGZIP(), []int{0} +} + +func (x *ClientData) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *ClientData) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ClientData) GetCoordinates() *ClientData_Coordinates { + if x != nil { + return x.Coordinates + } + return nil +} + +type ClientData_Coordinates struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + X float64 `protobuf:"fixed64,1,opt,name=X,proto3" json:"X,omitempty"` + Y float64 `protobuf:"fixed64,2,opt,name=Y,proto3" json:"Y,omitempty"` +} + +func (x *ClientData_Coordinates) Reset() { + *x = ClientData_Coordinates{} + mi := &file_clientdata_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ClientData_Coordinates) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientData_Coordinates) ProtoMessage() {} + +func (x *ClientData_Coordinates) ProtoReflect() protoreflect.Message { + mi := &file_clientdata_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClientData_Coordinates.ProtoReflect.Descriptor instead. +func (*ClientData_Coordinates) Descriptor() ([]byte, []int) { + return file_clientdata_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *ClientData_Coordinates) GetX() float64 { + if x != nil { + return x.X + } + return 0 +} + +func (x *ClientData_Coordinates) GetY() float64 { + if x != nil { + return x.Y + } + return 0 +} + +var File_clientdata_proto protoreflect.FileDescriptor + +var file_clientdata_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x04, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0xa5, 0x01, 0x0a, 0x0a, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x0b, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x6d, 0x61, 0x69, + 0x6e, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x43, 0x6f, 0x6f, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x52, 0x0b, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x74, 0x65, 0x73, 0x1a, 0x29, 0x0a, 0x0b, 0x43, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x74, 0x65, 0x73, 0x12, 0x0c, 0x0a, 0x01, 0x58, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x01, 0x58, 0x12, 0x0c, 0x0a, 0x01, 0x59, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x01, 0x59, + 0x42, 0x04, 0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_clientdata_proto_rawDescOnce sync.Once + file_clientdata_proto_rawDescData = file_clientdata_proto_rawDesc +) + +func file_clientdata_proto_rawDescGZIP() []byte { + file_clientdata_proto_rawDescOnce.Do(func() { + file_clientdata_proto_rawDescData = protoimpl.X.CompressGZIP(file_clientdata_proto_rawDescData) + }) + return file_clientdata_proto_rawDescData +} + +var file_clientdata_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_clientdata_proto_goTypes = []any{ + (*ClientData)(nil), // 0: main.ClientData + (*ClientData_Coordinates)(nil), // 1: main.ClientData.Coordinates +} +var file_clientdata_proto_depIdxs = []int32{ + 1, // 0: main.ClientData.coordinates:type_name -> main.ClientData.Coordinates + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_clientdata_proto_init() } +func file_clientdata_proto_init() { + if File_clientdata_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_clientdata_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_clientdata_proto_goTypes, + DependencyIndexes: file_clientdata_proto_depIdxs, + MessageInfos: file_clientdata_proto_msgTypes, + }.Build() + File_clientdata_proto = out.File + file_clientdata_proto_rawDesc = nil + file_clientdata_proto_goTypes = nil + file_clientdata_proto_depIdxs = nil +} diff --git a/server/clientdata.proto b/server/clientdata.proto new file mode 100644 index 0000000..a0189b6 --- /dev/null +++ b/server/clientdata.proto @@ -0,0 +1,15 @@ +syntax="proto3"; + +package main; + +option go_package = "./"; + +message ClientData { + string Address = 1; + string Name = 2; + message Coordinates { + double X=1; + double Y=2; + } + Coordinates coordinates = 4; +} \ No newline at end of file diff --git a/server/commands/command.go b/server/commands/command.go new file mode 100644 index 0000000..f7a24cb --- /dev/null +++ b/server/commands/command.go @@ -0,0 +1,4 @@ +package commands + +type Command struct { +} diff --git a/server/gamedata/gamedata.go b/server/gamedata/gamedata.go new file mode 100644 index 0000000..208f25e --- /dev/null +++ b/server/gamedata/gamedata.go @@ -0,0 +1,6 @@ +package gamedata + +type Coordinates struct { + X float64 + Y float64 +} diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..1db746f --- /dev/null +++ b/server/go.mod @@ -0,0 +1,8 @@ +module server + +go 1.21.6 + +require ( + github.com/golang/protobuf v1.5.4 // indirect + google.golang.org/protobuf v1.33.0 // indirect +) diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..02c7d8f --- /dev/null +++ b/server/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + "server/server" +) + +func main() { + fmt.Println("Server v0.04") + server := server.NewServer() + server.Start(501) + +} diff --git a/server/server/server.go b/server/server/server.go new file mode 100644 index 0000000..c408e4f --- /dev/null +++ b/server/server/server.go @@ -0,0 +1,165 @@ +package server + +import ( + "bufio" + "fmt" + "log" + "net" + "server/gamedata" + "strconv" + "strings" + "sync" + "time" +) + +type ClientData struct { + Address string + Name string + Position gamedata.Coordinates +} + +type Server struct { + //clientlist map[net.Conn]string + clientlist map[net.Conn]ClientData + mu sync.Mutex +} + +func NewServer() *Server { + s := &Server{ + //clientlist: make(map[net.Conn]string), + clientlist: make(map[net.Conn]ClientData), + } + return s +} + +func (s *Server) Start(port int) { + log.Println("Listening for new connections") + listener, err := net.Listen("tcp", ":"+fmt.Sprintf("%d", port)) + if err != nil { + log.Fatal(err) + } + defer listener.Close() + + go s.ManageBroadcast() + + for { + conn, err := listener.Accept() + if err != nil { + log.Println("Error accepting connection: ", err) + continue + } + log.Println("New connection " + conn.RemoteAddr().String()) + go s.HandleClient(conn) + } +} + +func (s *Server) HandleClient(conn net.Conn) { + + //remove client from list upon disconnection + defer func() { + log.Println("Disconnecting client " + conn.RemoteAddr().String()) + s.mu.Lock() + delete(s.clientlist, conn) + s.mu.Unlock() + conn.Close() + }() + + //create the outline of client data, so far we know only + //their addr, then add it to our client list + clientdata := ClientData{ + Address: conn.RemoteAddr().String(), + } + s.mu.Lock() + //s.clientlist[conn] = conn.RemoteAddr().String() + s.clientlist[conn] = clientdata + s.mu.Unlock() + + for { + // Read data from the client + data, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + return // Exit the Goroutine when the client disconnects + } + + fmt.Println("Received:", string(data)) + data = data[:len(data)-1] + //s.BroadcastMessage(conn, string(data)) + + //now we update our information with their data: + serverinfo := strings.Split(string(data), ",") + if len(serverinfo) == 3 { + + x, err := strconv.Atoi(serverinfo[1]) + if err != nil { + x = 0 + } + + y, err := strconv.Atoi(serverinfo[2]) + if err != nil { + y = 0 + } + + cd := ClientData{ + Address: conn.RemoteAddr().String(), + Name: serverinfo[0], + Position: gamedata.Coordinates{X: float64(x), Y: float64(y)}, + } + + s.mu.Lock() + s.clientlist[conn] = cd + s.mu.Unlock() + } + + } + +} + +func (s *Server) BroadcastMessage(sender net.Conn, message string) { + s.mu.Lock() + defer s.mu.Unlock() + + for client, addr := range s.clientlist { + if client != sender { + //_, err := client.Write([]byte("From " + sender.RemoteAddr().String() + ": " + message + "\n")) + _, err := client.Write([]byte(message)) + if err != nil { + fmt.Println("Error sending message to", addr, ":", err) + } + } + } +} + +func (s *Server) ManageBroadcast() { + for { + + broadcastmsg := s.BuildBroadcastMessage() + + s.mu.Lock() + for client, data := range s.clientlist { + _, err := client.Write([]byte(broadcastmsg)) + if err != nil { + fmt.Println("Error sending message to ", data.Address, ": ", err) + } + } + s.mu.Unlock() + //fmt.Println("Broadcasting:: ", broadcastmsg) + time.Sleep(time.Millisecond * 30) + } +} + +func (s *Server) BuildBroadcastMessage() string { + + msg := "{" + s.mu.Lock() + for _, data := range s.clientlist { + msg = msg + fmt.Sprintf("%s,%s,%.0f,%.0f;", data.Address, data.Name, data.Position.X, data.Position.Y) + } + s.mu.Unlock() + msg = msg + "}\n" + + return msg +} + +func (s *Server) Disconnect() { + +}