commit ca681f08cd6fb1cfe044e391fd5651701bc74f36 Author: iegod Date: Tue Dec 10 18:55:23 2024 -0500 Client initial commit. 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 0000000..f2222e1 Binary files /dev/null and b/client/fonts/bitbybit.ttf differ 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 0000000..dcca687 Binary files /dev/null and b/client/fonts/vcrmono.ttf differ 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() { + +}