func (g *Game) listenClientMessage(kill chan bool) { serverlog.General("listenClientMessage has started for", g.Identification()) for { select { case message, more := <-g.Initiator.IncommingMessages: if !more { serverlog.General(g.Initiator.Identification(), "(player 1) has disconnected while in", g.Identification(), "so sending disconnect message to game instance") g.UDS.Write(newDisconnectedMessage(true)) } else { g.interpretClientMessage(true, message) } case message, more := <-g.Player2.IncommingMessages: if !more { serverlog.General(g.Initiator.Identification(), "(player 2) has disconnected while in", g.Identification(), "so sending disconnect message to game instance") g.UDS.Write(newDisconnectedMessage(false)) } else { g.interpretClientMessage(false, message) } case <-kill: serverlog.General(g.Identification(), "listenClientMessage gorutine received kill signal so terminating") close(kill) return } } }
// JoinGame handles the case where a player sends a JoinGame message func JoinGame(conn *connection.Conn, allGames map[string]*games.Game, message messages.JoinGameMessage) { serverlog.General("Received JoinGame message from conn:", conn.Alias) if !conn.Registered { serverlog.General("Unregistered", conn.Identification, "called JoinGame") denied := messages.NewJoinGameDeniedMessage("You have not registered an Alias") conn.Write(denied.Bytes()) return } if conn.InGame { serverlog.General(conn.Identification(), "attempted to join a game but is already in game:", allGames[conn.GameID].Name) denied := messages.NewJoinGameDeniedMessage("You re already in a game") conn.Write(denied.Bytes()) return } if _, ok := allGames[message.GameID]; !ok { serverlog.General(conn.Identification(), "attempted to join a non existing game:", message.GameID) denied := messages.NewJoinGameDeniedMessage("No games with that ID") conn.Write(denied.Bytes()) return } game := allGames[message.GameID] serverlog.General(conn.Identification(), "Successfully joined", game.Identification()) conn.InGame = true conn.GameID = game.ID game.Start(conn) go listenFinished(game, allGames) }
func (g *Game) deleteSocket() { serverlog.General("Deleting socket:", g.UDSPath, "for", g.Identification()) err := os.Remove(g.UDSPath) if err != nil { if os.IsNotExist(err) { serverlog.General("socket:", g.UDSPath, "does not exist so can't be deleted") } else { serverlog.Warning("Failed to delete socket:", g.UDSPath, "for", g.Identification(), "err:", err) } } }
// RequestGameList handles the case where a player sends a RequestGameList message func RequestGameList(conn *connection.Conn, allGames map[string]*games.Game, message messages.RequestGameListMessage) { serverlog.General("Received RequestGameList message from", conn.Identification()) if !conn.Registered { serverlog.General("Unregistered", conn.Identification(), "called RequestGameList") return } serverlog.General("Sending Game list to", conn.Identification()) gameList := games.NewGameListMessage(allGames) conn.Write(gameList.Bytes()) }
// startWriter starts the writer for the connection func (conn *Conn) startWriter() { serverlog.General("Writer started for", conn.Identification()) for { messageBytes, more := <-conn.outgoingMessages if !more { serverlog.General("outgoingMessages killed: Writer closed for", conn.Identification()) return } length := uint16(len(messageBytes)) lengthBytes := make([]byte, 2) binary.LittleEndian.PutUint16(lengthBytes, length) messageToWrite := append(lengthBytes, messageBytes...) conn.Socket.Write(messageToWrite) } }
func (g *Game) createSocket() { serverlog.General("Creating socket:", g.UDSPath, "for", g.Identification()) _, err := os.Create(g.UDSPath) if err != nil { serverlog.Fatal("Failed to create socket:", g.UDSPath, "for", g.Identification(), "err:", err) } }
func (g *Game) interpretGameMessage(message []byte) { switch message[0] { case 1: // ready i := messages.NewStartGameMessage(true, 0, 0, 0, 0, g.Player2.Alias, g.ID, g.Name) p := messages.NewStartGameMessage(false, 0, 0, 0, 0, g.Initiator.Alias, g.ID, g.Name) g.Initiator.Write(i.Bytes()) g.Player2.Write(p.Bytes()) case 13: // status g.Initiator.Write(message) g.Player2.Write(message) case 3: // finished fin := newFinishedMessage(message) serverlog.General(g.Identification(), "Has finished with status", fin) inMs := messages.NewGameOverMessage(fin.p1score, fin.p2score, 0) p2Ms := messages.NewGameOverMessage(fin.p2score, fin.p1score, 0) if fin.p1won { inMs.Status = 0 p2Ms.Status = 1 } else { inMs.Status = 1 p2Ms.Status = 0 } g.Initiator.Write(inMs.Bytes()) g.Player2.Write(p2Ms.Bytes()) g.Kill() } }
// startReader starts the connection reader func (conn *Conn) startReader() { serverlog.General("Reader started for", conn.Identification()) var messageBuffer bytes.Buffer var bytesToRead int for { buf := make([]byte, 1400) dataSize, err := conn.Socket.Read(buf) if err != nil { serverlog.General("TCP connection closed for", conn.Identification()) serverlog.General("Reader closing net.conn socket for", conn.Identification()) conn.Socket.Close() serverlog.General("Reader closing OutgoingMessages channel for", conn.Identification()) close(conn.IncommingMessages) serverlog.General("Reader closing IncomingMessages channel for", conn.Identification()) close(conn.outgoingMessages) return } data := buf[0:dataSize] messageBuffer.Write(data) for messageBuffer.Len() > 1 { if bytesToRead == 0 { btrBuffer := make([]byte, 2) _, err := messageBuffer.Read(btrBuffer) if err != nil { serverlog.Fatal("Error happened in reader bts:2 for", conn.Identification(), "Error:", err) serverlog.Fatal(err) } bytesToRead = int(binary.LittleEndian.Uint16(btrBuffer)) } if messageBuffer.Len() >= bytesToRead { message := make([]byte, bytesToRead) _, err := messageBuffer.Read(message) if err != nil { serverlog.Fatal("Error happened in reader bts:var for", conn.Identification(), "Error:", err) } conn.IncommingMessages <- message bytesToRead = 0 } else { break } } } }
// Kill destroys all game related gorutines func (g *Game) Kill() { serverlog.General("Kill called on", g.Identification(), "closing UDS and sending message through FinChan") g.Initiator.InGame = false if g.Ready { g.UDS.Close() g.Player2.InGame = false } g.FinChan <- true }
func (g *Game) interpretClientMessage(player1 bool, message []byte) { switch message[0] { case messages.TypeLeaveGame: serverlog.General("Someone disconnected from ongoing", g.Identification(), "Telling game instance") g.UDS.Write(newDisconnectedMessage(player1)) case messages.TypeMove: mv := messages.NewMoveMessageFromBytes(message) g.UDS.Write(newMovementMessage(player1, mv.Position)) } }
func (g *Game) listenGameMessage() { serverlog.General("listenGameMessage gorutine started for", g.Identification()) clientListenerStarted := false clientKill := make(chan bool, 1) for { message, more := <-g.gameMessage if !more { serverlog.General("gameMessage channel closed for", g.Identification(), "so listenGameMessage is sending a kill signal to the client listeners and terminating") clientKill <- true return } if !clientListenerStarted && message[0] == 1 { // ready message sent serverlog.General("Received ready message for", g.Identification(), "so starting clientListeners") go g.listenClientMessage(clientKill) clientListenerStarted = true } g.interpretGameMessage(message) } }
// LeaveGame handles the case where a player sends a LeaveGame message func LeaveGame(conn *connection.Conn, allGames map[string]*games.Game, message messages.LeaveGameMessage) { serverlog.General("Received LeaveGame message from", conn.Identification()) if !conn.Registered { serverlog.General("Unregistered", conn.Identification(), "called LeaveGame") return } if !conn.InGame { serverlog.General(conn.Identification(), "attempted to leave a game but isn't in a game:", allGames[conn.GameID].Name) return } if allGames[conn.GameID].Ready { // will be dealt with by game object return } conn.InGame = false delete(allGames, conn.GameID) }
// RequestAlias handles the situation when a client sends a RequestAlias message func RequestAlias(message messages.RequestAliasMessage, conn *connection.Conn, al map[string]bool) { serverlog.General("Received RequestAlias message from", conn.Identification()) if conn.Registered { serverlog.General(conn.Identification(), "attempted to request new alias:", message.Alias) denied := messages.NewAliasDeniedMessage("Already registered with alias: " + conn.Alias) conn.Write(denied.Bytes()) return } if _, ok := al[message.Alias]; ok { serverlog.General(conn.Identification(), "requested existing alias:", message.Alias) denied := messages.NewAliasDeniedMessage("That alias is taken") conn.Write(denied.Bytes()) return } if len(message.Alias) < 3 || len(message.Alias) > 10 { serverlog.General(conn.Identification(), "requested too long or too short alias:", message.Alias) denied := messages.NewAliasDeniedMessage("An alias needs to be between 3 and 10 characters long (inclusive)") conn.Write(denied.Bytes()) return } serverlog.General("Successful registration under alias:", message.Alias) conn.Alias = message.Alias conn.Registered = true serverlog.General("Adding:", message.Alias, "to takenAliases map") al[message.Alias] = true approved := messages.NewAliasApprovedMessage() conn.Write(approved.Bytes()) }
// CreateGame handles the case where a player sends a CreateGame message func CreateGame(conn *connection.Conn, allGames map[string]*games.Game, message messages.CreateGameMessage) { serverlog.General("Received CreateGame message from", conn.Identification()) if !conn.Registered { serverlog.General("Unregistered", conn.Identification(), "called createGame") denied := messages.NewCreateGameDeniedMessage(message.GameName, "You are not registered") conn.Write(denied.Bytes()) return } if conn.InGame { serverlog.General(conn.Identification(), "tried to create a new game but is already in game:", allGames[conn.GameID].Name) denied := messages.NewCreateGameDeniedMessage(message.GameName, "You are already in a game") conn.Write(denied.Bytes()) return } serverlog.General("Creating game:", message.GameName, "by", conn.Identification()) game := games.NewGame(conn, message.GameName) serverlog.General(conn.Identification(), "setting InGame to true and Game to the game:", game.Name) conn.InGame = true conn.GameID = game.ID serverlog.General("Attatching", game.Identification(), "to games list") allGames[game.ID] = game approved := messages.NewCreateGameApprovedMessage(game.ID, game.Name) conn.Write(approved.Bytes()) }
// NewGame returns a pointer to a game instance given two connections func NewGame(initiator *connection.Conn, name string) *Game { id := uuid.NewV4().String() serverlog.General("Initiating Game:", id, "with", initiator.Identification()) return &Game{ ID: id, Name: name, Initiator: initiator, InitTime: time.Now(), Ready: false, UDSPath: path.Join("~", ".pppsrv", "sockets", id+".sock"), FinChan: make(chan bool, 1), } }
// Bytes returns an API friendly binary representation of the game object // which can be sent to clients. func (g *Game) Bytes() []byte { serverlog.General("Getting byte version of", g.Identification()) var buf bytes.Buffer unixBytes := make([]byte, 4) binary.LittleEndian.PutUint32(unixBytes, uint32(g.InitTime.Unix())) buf.Write(unixBytes) buf.WriteString(g.ID) buf.WriteByte(0) buf.WriteString(g.Name) buf.WriteByte(0) buf.WriteString(g.Initiator.Alias) buf.WriteByte(0) return buf.Bytes() }
func (g *Game) startUDS() { serverlog.General("Initiationg gameMessage channel for", g.Identification()) g.gameMessage = make(chan []byte, 100) go g.listenGameMessage() g.deleteSocket() g.createSocket() defer g.deleteSocket() defer g.Kill() listener, err := net.Listen("unix", g.UDSPath) if err != nil { serverlog.Fatal("Failed to establish listener for Unix domain socket:", g.UDSPath) } cmd := exec.Command(path.Join("~", ".pppsrv", "game"), g.UDSPath, "60") err = cmd.Start() if err != nil { serverlog.Fatal("Failed to start game instance at:", path.Join("~", ".pppsrv", "game"), "err:", err) } g.UDS, err = listener.Accept() if err != nil { serverlog.Fatal("Failed to accept connection for unix domain socket:", g.UDSPath) } serverlog.General("Accepted connection on:", g.UDSPath) for { buffer := make([]byte, 1400) mSize, err := g.UDS.Read(buffer) if err != nil { serverlog.General("Unix domain socket closed for:", g.Identification(), "so closing gameMessage channel") close(g.gameMessage) return } g.gameMessage <- buffer[:mSize] } }
// Close kills the reader and writer and closes the TCP connection func (conn *Conn) Close() { serverlog.General("Close called on", conn.Identification()) conn.Socket.Close() }
// Open starts the reader and writer for a connection func (conn *Conn) Open() { serverlog.General("Open called on", conn.Identification()) go conn.startWriter() go conn.startReader() }
func listenFinished(g *games.Game, allGames map[string]*games.Game) { <-g.FinChan serverlog.General("Received message through FinChan of", g.Identification(), "Removing it from allGames") delete(allGames, g.ID) }
// Start will start the game func (g *Game) Start(player2 *connection.Conn) { serverlog.General("Starting", g.Identification(), "with player 2", player2.Identification()) g.Player2 = player2 g.Ready = true g.startUDS() }