diff options
| author | Florian Fischer <florian.fl.fischer@fau.de> | 2017-02-16 17:50:24 +0100 |
|---|---|---|
| committer | Florian Fischer <florian.fl.fischer@fau.de> | 2017-02-16 17:50:24 +0100 |
| commit | ccdc8abe087b5b1a7dcd1c3fb1fec2203f36274c (patch) | |
| tree | 255f3015b0e1cef085f1649944e4671ae19aaf90 | |
| parent | e945d998f547900461e9fa313d18ec0d29c5d2c4 (diff) | |
| download | goffel-ccdc8abe087b5b1a7dcd1c3fb1fec2203f36274c.tar.gz goffel-ccdc8abe087b5b1a7dcd1c3fb1fec2203f36274c.zip | |
server: add sever implementation
| -rw-r--r-- | uis/server.go | 481 |
1 files changed, 477 insertions, 4 deletions
diff --git a/uis/server.go b/uis/server.go index daafef9..321b507 100644 --- a/uis/server.go +++ b/uis/server.go @@ -3,15 +3,488 @@ 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 { + name string + score logic.Score + 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.Dices{} + 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 + port string greeting string + sessions map[string]*session } func NewServer(port string, greeting string) Server { - return Server{port, greeting} + return Server{port, greeting, map[string]*session{}} } -func (s *Server) Serve() { - println("Not Implemented") +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{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 } |
