From b8bdd9d0419bab8ee0a7dd4b6e9fb1df7fecf221 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Sun, 19 Feb 2017 22:22:46 +0100 Subject: move network code from uis to net logic: add NewPlayer function --- goffel.go | 5 +- logic/player.go | 4 + net/client.go | 12 ++ net/server.go | 489 +++++++++++++++++++++++++++++++++++++++++++++++++++++ uis/client.go | 12 -- uis/interactive.go | 2 +- uis/server.go | 489 ----------------------------------------------------- 7 files changed, 509 insertions(+), 504 deletions(-) create mode 100644 net/client.go create mode 100644 net/server.go delete mode 100644 uis/client.go delete mode 100644 uis/server.go diff --git a/goffel.go b/goffel.go index 3ee90e0..a6efeb7 100644 --- a/goffel.go +++ b/goffel.go @@ -7,6 +7,7 @@ import ( "muhq.space/go/muhq/goffel/logic" "muhq.space/go/muhq/goffel/uis" + "muhq.space/go/muhq/goffel/net" ) var ( @@ -30,10 +31,10 @@ func main() { logic.SetFancyPrint(fancy) if server { - s := uis.NewServer(port, greeting) + s := net.NewServer(port, greeting) s.Serve() } else if client { - c := uis.Client{Port: port} + c := net.Client{Port: port} c.Run() } else { i := uis.Interactive{} diff --git a/logic/player.go b/logic/player.go index 31aa5d0..0642495 100644 --- a/logic/player.go +++ b/logic/player.go @@ -7,3 +7,7 @@ type Player struct { Name string Score Score } + +func NewPlayer(name string) Player { + return Player{name, NewScore()} +} diff --git a/net/client.go b/net/client.go new file mode 100644 index 0000000..dae0fa1 --- /dev/null +++ b/net/client.go @@ -0,0 +1,12 @@ +// Copyright (c) 2016 Florian Fischer. All rights reserved. +// Use of this source code is governed by a MIT license found in the LICENSE file. + +package net + +type Client struct { + Port string +} + +func (ui *Client) Run() { + println("Not Implemented") +} diff --git a/net/server.go b/net/server.go new file mode 100644 index 0000000..3c65b20 --- /dev/null +++ b/net/server.go @@ -0,0 +1,489 @@ +// Copyright (c) 2016 Florian Fischer. All rights reserved. +// Use of this source code is governed by a MIT license found in the LICENSE file. + +package net + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "net" + "strconv" + "strings" + "sync" + + "muhq.space/go/muhq/goffel/logic" +) + +var ( + // 0-9 Misc + // 10-19 Sucess + // 20-29 IO Errors + // 30 Command Errors + // 90-99 Session Responses + responsePlain = map[int]string{ + 0: "Success: ", + 1: "Welcome ", + 2: "Sessions:", + 11: "Help:\r\n", + 12: "Score:\r\n", + 13: "Dice: ", + 14: "Insert: ", + 15: "Cancel: ", + 16: "Reroll: ", + 20: "I/O Error: ", + 21: "Json Decoding Error: ", + 30: "Invalid Command: ", + 31: "Insert Error: ", + 32: "Cancel Error: ", + 33: "Reroll Error: ", + 90: "Connected To: ", + 91: "To Many Players: ", + 92: "New Players: ", + 93: "Starting Game: ", + 94: "Round: ", + 95: "", // Used to forward results of other players + 96: "Remove Client: ", + 97: "Winners: ", + } +) + +// Internal structure representing a server response +type sMsg struct { + Response int + Payload interface{} +} + +func (msg sMsg) String() string { + response := responsePlain[msg.Response] + payload := msg.Payload + res := "" + if msg.Response == 2 { + sessionsptr, _ := msg.Payload.(*map[string]*session) + sessions := *sessionsptr + s := "" + for name, session := range sessions { + s += fmt.Sprintf("\r\n%s: %v", name, session) + } + payload = s + } + if msg.Response == 11 { + a, _ := msg.Payload.([]string) + payload = strings.Join(a, "\r\n") + } + if msg.Response == 95 { + a, _ := msg.Payload.([]interface{}) + res, _ = a[0].(string) + msg, _ = a[1].(sMsg) + res += " " + responsePlain[msg.Response] + } + if msg.Response == 14 { + a, _ := msg.Payload.([]interface{}) + c, _ := a[0].(logic.Cmd) + payload = fmt.Sprintf("%d -> %v", a[1], logic.ScoreNames[c.Argv[0]]) + } else if msg.Response == 15 { + a, _ := msg.Payload.([]interface{}) + c, _ := a[0].(logic.Cmd) + payload = fmt.Sprintf("%v", logic.ScoreNames[c.Argv[0]]) + } + return fmt.Sprintf("%s%s%v", response, res, payload) +} + +type player struct { + logic.Player + client *client +} + +func (p player) String() string { + return p.Name +} + +type client struct { + sock net.Conn + reader *bufio.Reader + writer *bufio.Writer + encoder *json.Encoder + decoder *json.Decoder + encoding string + players []player + results chan sMsg +} + +func (c *client) send(msg sMsg) error { + var err error + if c.encoding == "plain" { + _, err = c.writer.WriteString(fmt.Sprintf("%v\r\n", msg)) + if err == nil { + err = c.writer.Flush() + } + } else { + err = c.encoder.Encode(msg) + } + return err +} + +func (c *client) fail(res int, pl interface{}) { + c.send(sMsg{res, pl}) + c.sock.Close() +} + +func (c *client) recvCmd() (logic.Cmd, error) { + var err error + var cmd logic.Cmd + if c.encoding == "plain" { + input := "" + input, err = c.reader.ReadString('\n') + if err != nil { + return cmd, err + } + cmd, err = logic.ParseCmd(input) + } else { + err = c.decoder.Decode(&cmd) + } + return cmd, err +} + +func (c *client) getBest() ([]player, int) { + best := []player{} + max := 0 + for _, p := range c.players { + t := p.Score.Score() + if t > max { + best = []player{p} + max = t + } else if t == max { + best = append(best, p) + } + } + return best, max +} + +func (c *client) round(round int, wg *sync.WaitGroup) { + dice := logic.Dice{} + var msg sMsg + + for _, p := range c.players { + fin := false + dice.Roll(nil) + cmd := logic.Cmd{} + var err error + rerolls := 0 + err = c.send(sMsg{94, []interface{}{round, p.Name, dice}}) + if err != nil { + fin = true + } + for !fin { + // clear message + msg = sMsg{} + cmd, err = c.recvCmd() + if err != nil { + msg.Response = 30 + msg.Payload = err + } + + switch cmd.Cmd { + case "h": + msg.Response = 11 + msg.Payload = logic.CmdHelp(cmd.Argv[0]) + case "p": + msg.Response = 12 + msg.Payload = p.Score + case "d": + msg.Response = 13 + msg.Payload = dice + case "q": + //client quits + err = errors.New("Quit") + c.sock.Close() + case "i": + i, err := p.Score.Insert(dice, cmd.Argv[0]) + if err != nil { + msg.Response = 31 + msg.Payload = err + } else { + msg.Response = 14 + msg.Payload = []interface{}{cmd, i, p.Score} + fin = true + } + case "c": + err := p.Score.Cancel(cmd.Argv[0]) + if err != nil { + msg.Response = 32 + msg.Payload = err + } else { + msg.Response = 15 + msg.Payload = []interface{}{cmd, p.Score} + fin = true + } + case "r": + var err error + if rerolls < 2 { + err = dice.Roll(cmd.Argv) + } else { + err = errors.New("Only two rerolls are allowed.") + } + if err != nil { + msg.Response = 33 + msg.Payload = err + } else { + msg.Response = 16 + msg.Payload = dice + rerolls++ + } + default: + } + if err = c.send(msg); err != nil { + fin = true + } + } + if err != nil { + msg = sMsg{96, err} + } else { + msg = sMsg{95, []interface{}{p.Name, msg}} + } + c.results <- msg + } + wg.Done() +} + +type session struct { + clients []*client + maxPlayers int + curPlayers int + status string +} + +func (s session) String() (ret string) { + ret += s.status + ret += " max:" + ret += strconv.Itoa(s.maxPlayers) + ret += " p:" + names := make([]string, 0, s.maxPlayers) + for _, c := range s.clients { + for _, p := range c.players { + names = append(names, p.Name) + } + } + return fmt.Sprintf("%s%v", ret, names) +} + +func (s *session) terminate(reason sMsg) { + for _, c := range s.clients { + noreason := sMsg{} + if reason != noreason { + c.send(reason) + } + c.sock.Close() + } + s.status = "TERMINATED" +} + +func (s *session) addClient(c *client) { + newNames := make([]string, 0, len(c.players)) + for _, n := range c.players { + newNames = append(newNames, n.Name) + } + okClients := make([]*client, 0, len(c.players)) + for _, other := range s.clients { + err := other.send(sMsg{92, newNames}) + if err == nil { + okClients = append(okClients, other) + } else { + s.curPlayers-- + } + } + s.clients = okClients + s.clients = append(s.clients, c) + s.curPlayers += len(c.players) + if s.curPlayers == s.maxPlayers { + go s.play() + } +} + +func (s *session) play() { + s.status = "PLAYING" + msg := sMsg{93, ""} + wg := sync.WaitGroup{} + for _, c := range s.clients { + c.send(msg) + } + var reason sMsg + for round := 1; round < 14; round++ { + wg.Add(len(s.clients)) + for _, c := range s.clients { + go c.round(round, &wg) + } + // broadcast results + wg.Wait() + badClients := make(map[*client]bool) + for i, c := range s.clients { + for _, p := range c.players { + res := <-c.results + // collect broken clients + if res.Response == 96 { + badClients[c] = true + res = sMsg{96, []interface{}{p.Name, res.Payload}} + } + for j, c2 := range s.clients { + if i != j { + // ignore error + c2.send(res) + } + } + } + } + // remove broken clients + okClients := make([]*client, 0, len(s.clients)-len(badClients)) + for _, c := range s.clients { + if _, bad := badClients[c]; bad == false { + okClients = append(okClients, c) + } + } + s.clients = okClients + } + winners := []player{} + max := 0 + for _, c := range s.clients { + best, m := c.getBest() + if m > max { + winners = best + max = m + } else if m == max { + winners = append(winners, best...) + } + } + msg = sMsg{97, []interface{}{max, winners}} + for _, c := range s.clients { + c.send(msg) + } + s.terminate(reason) +} + +type Server struct { + port string + greeting string + sessions map[string]*session +} + +func NewServer(port string, greeting string) Server { + return Server{port, greeting, map[string]*session{}} +} + +func (s *Server) Serve() error { + ln, err := net.Listen("tcp", ":"+s.port) + if err != nil { + return err + } + sessionLock := sync.Mutex{} + for { + sock, err := ln.Accept() + if err != nil { + return err + } + go func() { + reader := bufio.NewReader(sock) + + input, err := reader.ReadString('\n') + if err != nil { + sock.Close() + return + } + c := client{} + c.sock = sock + + // is HELO json encoded ? + var helo struct { + Cmd string + Players []string + } + err = json.Unmarshal([]byte(input), &helo) + if err == nil { + c.encoder = json.NewEncoder(sock) + c.decoder = json.NewDecoder(sock) + c.encoding = "json" + if helo.Cmd != "HELO" { + c.fail(30, "HELO expected") + return + } + } else { + c.reader = reader + c.writer = bufio.NewWriter(sock) + c.encoding = "plain" + + input = strings.TrimRight(input, " \r\n") + line := strings.Split(input, " ") + if line[0] != "HELO" { + c.fail(30, "HELO expected") + return + } else if len(line) < 2 { + c.fail(30, "player name expected") + return + } + helo.Players = line[1:] + } + for _, name := range helo.Players { + c.players = append(c.players, player{logic.NewPlayer(name), &c}) + } + c.results = make(chan sMsg, len(c.players)) + + c.send(sMsg{1, s.greeting}) + //names := []string{} + //for name := range s.session { + //names = append(names, name) + //} + //c.send(2, names) + sessionLock.Lock() + c.send(sMsg{2, &s.sessions}) + sessionLock.Unlock() + var reg struct { + Name string + MaxPlayers int + } + if c.encoding == "json" { + err = c.decoder.Decode(®) + if err != nil { + c.fail(21, err) + return + } + } else { + input, err = c.reader.ReadString('\n') + if err != nil { + c.fail(20, err) + return + } + input = strings.TrimRight(input, " \r\n") + line := strings.Split(input, " ") + reg.Name = line[0] + if len(line) > 1 { + reg.MaxPlayers, err = strconv.Atoi(line[1]) + if err != nil { + c.fail(30, err) + return + } + } + } + if reg.Name == "" { + c.fail(30, "Session name expected") + return + } + if reg.MaxPlayers == 0 { + reg.MaxPlayers = 2 + } + + sessionLock.Lock() + defer sessionLock.Unlock() + ses, exists := s.sessions[reg.Name] + if !exists || ses.status == "TERMINATED" { + ses = &session{[]*client{}, reg.MaxPlayers, 0, "OPEN"} + s.sessions[reg.Name] = ses + } + if ses.curPlayers+len(c.players) > ses.maxPlayers { + c.fail(91, ses.maxPlayers-ses.curPlayers) + return + } + ses.addClient(&c) + }() + } + return nil +} diff --git a/uis/client.go b/uis/client.go deleted file mode 100644 index beca3a6..0000000 --- a/uis/client.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2016 Florian Fischer. All rights reserved. -// Use of this source code is governed by a MIT license found in the LICENSE file. - -package uis - -type Client struct { - Port string -} - -func (ui *Client) Run() { - println("Not Implemented") -} diff --git a/uis/interactive.go b/uis/interactive.go index 8210122..5e7994e 100644 --- a/uis/interactive.go +++ b/uis/interactive.go @@ -36,7 +36,7 @@ func (i *Interactive) init() { if err != nil { break } - i.players = append(i.players, Player{name, NewScore()}) + i.players = append(i.players, NewPlayer(name)) count++ } diff --git a/uis/server.go b/uis/server.go deleted file mode 100644 index 220699c..0000000 --- a/uis/server.go +++ /dev/null @@ -1,489 +0,0 @@ -// Copyright (c) 2016 Florian Fischer. All rights reserved. -// Use of this source code is governed by a MIT license found in the LICENSE file. - -package uis - -import ( - "bufio" - "encoding/json" - "errors" - "fmt" - "net" - "strconv" - "strings" - "sync" - - "muhq.space/go/muhq/goffel/logic" -) - -var ( - // 0-9 Misc - // 10-19 Sucess - // 20-29 IO Errors - // 30 Command Errors - // 90-99 Session Responses - responsePlain = map[int]string{ - 0: "Success: ", - 1: "Welcome ", - 2: "Sessions:", - 11: "Help:\r\n", - 12: "Score:\r\n", - 13: "Dice: ", - 14: "Insert: ", - 15: "Cancel: ", - 16: "Reroll: ", - 20: "I/O Error: ", - 21: "Json Decoding Error: ", - 30: "Invalid Command: ", - 31: "Insert Error: ", - 32: "Cancel Error: ", - 33: "Reroll Error: ", - 90: "Connected To: ", - 91: "To Many Players: ", - 92: "New Players: ", - 93: "Starting Game: ", - 94: "Round: ", - 95: "", // Used to forward results of other players - 96: "Remove Client: ", - 97: "Winners: ", - } -) - -// Internal structure representing a server response -type sMsg struct { - Response int - Payload interface{} -} - -func (msg sMsg) String() string { - response := responsePlain[msg.Response] - payload := msg.Payload - res := "" - if msg.Response == 2 { - sessionsptr, _ := msg.Payload.(*map[string]*session) - sessions := *sessionsptr - s := "" - for name, session := range sessions { - s += fmt.Sprintf("\r\n%s: %v", name, session) - } - payload = s - } - if msg.Response == 11 { - a, _ := msg.Payload.([]string) - payload = strings.Join(a, "\r\n") - } - if msg.Response == 95 { - a, _ := msg.Payload.([]interface{}) - res, _ = a[0].(string) - msg, _ = a[1].(sMsg) - res += " " + responsePlain[msg.Response] - } - if msg.Response == 14 { - a, _ := msg.Payload.([]interface{}) - c, _ := a[0].(logic.Cmd) - payload = fmt.Sprintf("%d -> %v", a[1], logic.ScoreNames[c.Argv[0]]) - } else if msg.Response == 15 { - a, _ := msg.Payload.([]interface{}) - c, _ := a[0].(logic.Cmd) - payload = fmt.Sprintf("%v", logic.ScoreNames[c.Argv[0]]) - } - return fmt.Sprintf("%s%s%v", response, res, payload) -} - -type player struct { - logic.Player - client *client -} - -func (p player) String() string { - return p.Name -} - -type client struct { - sock net.Conn - reader *bufio.Reader - writer *bufio.Writer - encoder *json.Encoder - decoder *json.Decoder - encoding string - players []player - results chan sMsg -} - -func (c *client) send(msg sMsg) error { - var err error - if c.encoding == "plain" { - _, err = c.writer.WriteString(fmt.Sprintf("%v\r\n", msg)) - if err == nil { - err = c.writer.Flush() - } - } else { - err = c.encoder.Encode(msg) - } - return err -} - -func (c *client) fail(res int, pl interface{}) { - c.send(sMsg{res, pl}) - c.sock.Close() -} - -func (c *client) recvCmd() (logic.Cmd, error) { - var err error - var cmd logic.Cmd - if c.encoding == "plain" { - input := "" - input, err = c.reader.ReadString('\n') - if err != nil { - return cmd, err - } - cmd, err = logic.ParseCmd(input) - } else { - err = c.decoder.Decode(&cmd) - } - return cmd, err -} - -func (c *client) getBest() ([]player, int) { - best := []player{} - max := 0 - for _, p := range c.players { - t := p.Score.Score() - if t > max { - best = []player{p} - max = t - } else if t == max { - best = append(best, p) - } - } - return best, max -} - -func (c *client) round(round int, wg *sync.WaitGroup) { - dice := logic.Dice{} - var msg sMsg - - for _, p := range c.players { - fin := false - dice.Roll(nil) - cmd := logic.Cmd{} - var err error - rerolls := 0 - err = c.send(sMsg{94, []interface{}{round, p.Name, dice}}) - if err != nil { - fin = true - } - for !fin { - // clear message - msg = sMsg{} - cmd, err = c.recvCmd() - if err != nil { - msg.Response = 30 - msg.Payload = err - } - - switch cmd.Cmd { - case "h": - msg.Response = 11 - msg.Payload = logic.CmdHelp(cmd.Argv[0]) - case "p": - msg.Response = 12 - msg.Payload = p.Score - case "d": - msg.Response = 13 - msg.Payload = dice - case "q": - //client quits - err = errors.New("Quit") - c.sock.Close() - case "i": - i, err := p.Score.Insert(dice, cmd.Argv[0]) - if err != nil { - msg.Response = 31 - msg.Payload = err - } else { - msg.Response = 14 - msg.Payload = []interface{}{cmd, i, p.Score} - fin = true - } - case "c": - err := p.Score.Cancel(cmd.Argv[0]) - if err != nil { - msg.Response = 32 - msg.Payload = err - } else { - msg.Response = 15 - msg.Payload = []interface{}{cmd, p.Score} - fin = true - } - case "r": - var err error - if rerolls < 2 { - err = dice.Roll(cmd.Argv) - } else { - err = errors.New("Only two rerolls are allowed.") - } - if err != nil { - msg.Response = 33 - msg.Payload = err - } else { - msg.Response = 16 - msg.Payload = dice - rerolls++ - } - default: - } - if err = c.send(msg); err != nil { - fin = true - } - } - if err != nil { - msg = sMsg{96, err} - } else { - msg = sMsg{95, []interface{}{p.Name, msg}} - } - c.results <- msg - } - wg.Done() -} - -type session struct { - clients []*client - maxPlayers int - curPlayers int - status string -} - -func (s session) String() (ret string) { - ret += s.status - ret += " max:" - ret += strconv.Itoa(s.maxPlayers) - ret += " p:" - names := make([]string, 0, s.maxPlayers) - for _, c := range s.clients { - for _, p := range c.players { - names = append(names, p.Name) - } - } - return fmt.Sprintf("%s%v", ret, names) -} - -func (s *session) terminate(reason sMsg) { - for _, c := range s.clients { - noreason := sMsg{} - if reason != noreason { - c.send(reason) - } - c.sock.Close() - } - s.status = "TERMINATED" -} - -func (s *session) addClient(c *client) { - newNames := make([]string, 0, len(c.players)) - for _, n := range c.players { - newNames = append(newNames, n.Name) - } - okClients := make([]*client, 0, len(c.players)) - for _, other := range s.clients { - err := other.send(sMsg{92, newNames}) - if err == nil { - okClients = append(okClients, other) - } else { - s.curPlayers-- - } - } - s.clients = okClients - s.clients = append(s.clients, c) - s.curPlayers += len(c.players) - if s.curPlayers == s.maxPlayers { - go s.play() - } -} - -func (s *session) play() { - s.status = "PLAYING" - msg := sMsg{93, ""} - wg := sync.WaitGroup{} - for _, c := range s.clients { - c.send(msg) - } - var reason sMsg - for round := 1; round < 14; round++ { - wg.Add(len(s.clients)) - for _, c := range s.clients { - go c.round(round, &wg) - } - // broadcast results - wg.Wait() - badClients := make(map[*client]bool) - for i, c := range s.clients { - for _, p := range c.players { - res := <-c.results - // collect broken clients - if res.Response == 96 { - badClients[c] = true - res = sMsg{96, []interface{}{p.Name, res.Payload}} - } - for j, c2 := range s.clients { - if i != j { - // ignore error - c2.send(res) - } - } - } - } - // remove broken clients - okClients := make([]*client, 0, len(s.clients)-len(badClients)) - for _, c := range s.clients { - if _, bad := badClients[c]; bad == false { - okClients = append(okClients, c) - } - } - s.clients = okClients - } - winners := []player{} - max := 0 - for _, c := range s.clients { - best, m := c.getBest() - if m > max { - winners = best - max = m - } else if m == max { - winners = append(winners, best...) - } - } - msg = sMsg{97, []interface{}{max, winners}} - for _, c := range s.clients { - c.send(msg) - } - s.terminate(reason) -} - -type Server struct { - port string - greeting string - sessions map[string]*session -} - -func NewServer(port string, greeting string) Server { - return Server{port, greeting, map[string]*session{}} -} - -func (s *Server) Serve() error { - ln, err := net.Listen("tcp", ":"+s.port) - if err != nil { - return err - } - sessionLock := sync.Mutex{} - for { - sock, err := ln.Accept() - if err != nil { - return err - } - go func() { - reader := bufio.NewReader(sock) - - input, err := reader.ReadString('\n') - if err != nil { - sock.Close() - return - } - c := client{} - c.sock = sock - - // is HELO json encoded ? - var helo struct { - Cmd string - Players []string - } - err = json.Unmarshal([]byte(input), &helo) - if err == nil { - c.encoder = json.NewEncoder(sock) - c.decoder = json.NewDecoder(sock) - c.encoding = "json" - if helo.Cmd != "HELO" { - c.fail(30, "HELO expected") - return - } - } else { - c.reader = reader - c.writer = bufio.NewWriter(sock) - c.encoding = "plain" - - input = strings.TrimRight(input, " \r\n") - line := strings.Split(input, " ") - if line[0] != "HELO" { - c.fail(30, "HELO expected") - return - } else if len(line) < 2 { - c.fail(30, "player name expected") - return - } - helo.Players = line[1:] - } - for _, name := range helo.Players { - c.players = append(c.players, player{logic.Player{name, logic.NewScore()}, &c}) - } - c.results = make(chan sMsg, len(c.players)) - - c.send(sMsg{1, s.greeting}) - //names := []string{} - //for name := range s.session { - //names = append(names, name) - //} - //c.send(2, names) - sessionLock.Lock() - c.send(sMsg{2, &s.sessions}) - sessionLock.Unlock() - var reg struct { - Name string - MaxPlayers int - } - if c.encoding == "json" { - err = c.decoder.Decode(®) - if err != nil { - c.fail(21, err) - return - } - } else { - input, err = c.reader.ReadString('\n') - if err != nil { - c.fail(20, err) - return - } - input = strings.TrimRight(input, " \r\n") - line := strings.Split(input, " ") - reg.Name = line[0] - if len(line) > 1 { - reg.MaxPlayers, err = strconv.Atoi(line[1]) - if err != nil { - c.fail(30, err) - return - } - } - } - if reg.Name == "" { - c.fail(30, "Session name expected") - return - } - if reg.MaxPlayers == 0 { - reg.MaxPlayers = 2 - } - - sessionLock.Lock() - defer sessionLock.Unlock() - ses, exists := s.sessions[reg.Name] - if !exists || ses.status == "TERMINATED" { - ses = &session{[]*client{}, reg.MaxPlayers, 0, "OPEN"} - s.sessions[reg.Name] = ses - } - if ses.curPlayers+len(c.players) > ses.maxPlayers { - c.fail(91, ses.maxPlayers-ses.curPlayers) - return - } - ses.addClient(&c) - }() - } - return nil -} -- cgit v1.2.3