def testEnoughTurnsPlayed(self): txq = asyncio.Queue() wordset = SupportedWordSets["test"] allConns = Connections(txq) team1 = mockPlyrTeam(txq, allConns, 1, {"sb": [101]}, {"sb": 2}) team2 = mockPlyrTeam(txq, allConns, 2, {"jg": [102]}, {"jg": 2}) teams = {1: team1, 2: team2} hostParameters = HostParameters(numTeams=2, turnDurationSec=30, wordSets=["test"], numTurns=2) for team in teams.values(): for ws in team.conns._wss: allConns.addConn(ws) turnMgr = TurnManager("taboo:1", txq, wordset, teams, hostParameters, allConns, None) self.assertFalse(turnMgr.startNewTurn()) self.assertGiTxQueueMsgs(txq, [ ClientTxMsg(['PLAYER-STATUS', 'jg', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {101}), ClientTxMsg(['PLAYER-STATUS', 'sb', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {101}), ClientTxMsg(['PLAYER-STATUS', 'jg', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {102}), ClientTxMsg(['PLAYER-STATUS', 'sb', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {102}), ClientTxMsg(["SCORE", {1: 0, 2: 0}], {101, 102}), ], anyOrder=True)
def __processReady(self, qmsg): """ ["READY"] """ ws = qmsg.initiatorWs if len(qmsg.jmsg) != 1: self.txQueue.put_nowait(ClientTxMsg(["READY-BAD", "Invalid message length"], {ws}, initiatorWs=ws)) return True #if self.state != GameState.WAITING_TO_START: # self.txQueue.put_nowait(ClientTxMsg(["READY-BAD", "Game already started/ended"], # {ws}, initiatorWs=ws)) # return True player = self.playerByWs.get(ws, None) if not player: self.txQueue.put_nowait(ClientTxMsg(["READY-BAD", "Join first"], {ws}, initiatorWs=ws)) return True if player.ready: self.txQueue.put_nowait(ClientTxMsg(["READY-BAD", "Already ready"], {ws}, initiatorWs=ws)) return True player.ready = True if all(t.ready() for t in self.teams.values()): self.turnMgr.startNewTurn() self.state = GameState.RUNNING trace(Level.info, "Game started") return True
def __processJoin(self, qmsg): """ ["JOIN", playerName, team:int={0..T}] """ ws = qmsg.initiatorWs assert ws in self.playerByWs, "Join request from an unrecognized connection" if self.playerByWs[ws]: self.txQueue.put_nowait(ClientTxMsg(["JOIN-BAD", "Unexpected JOIN message from client that " "has already joined"], {ws}, initiatorWs=ws)) return True if len(qmsg.jmsg) != 3: self.txQueue.put_nowait(ClientTxMsg(["JOIN-BAD", "Invalid message length"], {ws}, initiatorWs=ws)) return True _, playerName, teamNumber = qmsg.jmsg if not isinstance(playerName, str) or not validPlayerNameRe.match(playerName): self.txQueue.put_nowait(ClientTxMsg(["JOIN-BAD", "Invalid player name", playerName], {ws}, initiatorWs=ws)) return True if (not isinstance(teamNumber, int) or teamNumber < 0 or teamNumber > self.hostParameters.numTeams): self.txQueue.put_nowait(ClientTxMsg(["JOIN-BAD", "Invalid team number", teamNumber], {ws}, initiatorWs=ws)) return True return self.joinPlayer(ws, playerName, teamNumber)
def __processKickoff(self, qmsg): """ ["KICKOFF"] """ ws = qmsg.initiatorWs if len(qmsg.jmsg) != 1: self.txQueue.put_nowait( ClientTxMsg(["KICKOFF-BAD", "Invalid message length"], {ws}, initiatorWs=ws)) return True if self.state != GameState.RUNNING: trace(Level.play, "_processDiscard current state", self.state.name) self.txQueue.put_nowait( ClientTxMsg(["KICKOFF-BAD", "Game not running"], {ws}, initiatorWs=ws)) return True player = self.playerByWs[ws] if player != self.turnMgr.activePlayer: trace( Level.play, "_processDiscard msg rcvd from", player.name if player else None, "activePlayer", self.turnMgr.activePlayer.name if self.turnMgr.activePlayer else None) self.txQueue.put_nowait( ClientTxMsg(["KICKOFF-BAD", "It is not your turn"], {ws}, initiatorWs=ws)) return True return self.turnMgr.processKickoff(qmsg)
def testHandleGiStatusWithConnections(self): """Handling of InternalGiStatus""" self.plugin.conns.addConn(self.connWs1) self.plugin.conns.addConn(self.connWs2) # Process InternalGiStatus with two clients connected msg = InternalGiStatus([], "foo:1") self.plugin.processMsg(msg) self.assertGiTxQueueMsgs(self.txq, [ClientTxMsg(["GAME-STATUS", "foo:1"], {self.connWs1, self.connWs2}, initiatorWs=None)]) # Process InternalGiStatus with two clients connected: updating an existing game msg = InternalGiStatus([{"count": 10}], "foo:1") self.plugin.processMsg(msg) self.assertGiTxQueueMsgs(self.txq, [ClientTxMsg(["GAME-STATUS", "foo:1", {"count": 10}], {self.connWs1, self.connWs2}, initiatorWs=None)]) # Processing same InternalGiStatus with two clients connected # should not create messages msg = InternalGiStatus([{"count": 10}], "foo:1") self.plugin.processMsg(msg) self.assertGiTxQueueMsgs(self.txq, [])
def testAddConnectionsAfterMsgSrc(self): # message source with no messages msgSrcEmpty = MsgSrc(self.conns) # pylint: disable=unused-variable msgSrc = MsgSrc(self.conns) msgSrc.setMsgs([ Jmai([1], initiatorWs=clientWs3), Jmai([2], initiatorWs=clientWs3) ]) self.assertGiTxQueueMsgs(self.txq, []) # Add websockets afterwards conns1 = Connections(self.txq) conns2 = Connections(self.txq) conns2.addConn(clientWs2) self.conns.addConnections(conns1) self.assertGiTxQueueMsgs(self.txq, []) self.conns.addConnections(conns2) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs2}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs2}, initiatorWs=clientWs3) ]) conns1.addConn(clientWs1) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs1}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs1}, initiatorWs=clientWs3) ])
def processDeclare(self, qmsg): """ ["DECLARE"] Should only be processed if: 1. game state is player turn 2. ws has joined 3. player[ws].name == turn.current() """ ws = qmsg.initiatorWs if not isinstance(self.gameState, StatePlayerTurn): self.txQueue.put_nowait( ClientTxMsg(["DECLARE-BAD", "Can't make moves now"], {ws}, initiatorWs=ws)) return True if self.playerByWs[ws] is None: self.txQueue.put_nowait( ClientTxMsg(["DECLARE-BAD", "You must join the game first"], {ws}, initiatorWs=ws)) return True if self.playerByWs[ws].name != self.currRound.turn.current(): self.txQueue.put_nowait( ClientTxMsg(["DECLARE-BAD", "It is not your turn"], {ws}, initiatorWs=ws)) return True if len(qmsg.jmsg) != 1: self.txQueue.put_nowait( ClientTxMsg(["DECLARE-BAD", "Invalid message length"], {ws}, initiatorWs=ws)) return True event = self.currRound.rule.processDeclare(self.currRound, self.playerByWs[ws]) if not event: self.txQueue.put_nowait( ClientTxMsg(["DECLARE-BAD", "Invalid declare"], {ws}, initiatorWs=ws)) else: jmsg = [ "UPDATE", self.currRound.roundParams.roundNum, { "DECLARE": event.toJmsg() } ] self.broadcast(jmsg) self.processEvent(event) return True
def __validateCompletedOrDiscard(self, qmsg): """ Validates [COMPLETED|DISCARD, turn<int>, wordIdx<int>] Replies a DISCARD-BAD or COMPLETED-BAD if the message is received at wrong turn state Returns True iff message is valid """ msgType = qmsg.jmsg[0] assert msgType in ("DISCARD", "COMPLETED") badReplyType = "{}-BAD".format(msgType) ws = qmsg.initiatorWs if self._state != TurnMgrState.RUNNING: trace(Level.play, "process{} turn state".format(msgType), self._state.name) self._txQueue.put_nowait( ClientTxMsg( [badReplyType, "Can't {} right now".format(msgType)], {ws}, initiatorWs=ws)) return False if qmsg.jmsg[1] != self._curTurnId: self._txQueue.put_nowait( ClientTxMsg([badReplyType, "Invalid turn"], {ws}, initiatorWs=ws)) return False assert self._curTurnId in self._wordsByTurnId, ( "Since the turn is in running state, {} must exist in {}".format( self._curTurnId, self._wordsByTurnId.keys())) assert self._wordsByTurnId[self._curTurnId], ( "Since the turn is in running state, there must be at least 1 word in {}" .format(self._wordsByTurnId[self._curTurnId])) lastWord = self._wordsByTurnId[self._curTurnId][-1] if qmsg.jmsg[2] != lastWord.wordId: self._txQueue.put_nowait( ClientTxMsg([badReplyType, "Invalid word"], {ws}, initiatorWs=ws)) return False if lastWord.state != WordState.IN_PLAY: self._txQueue.put_nowait( ClientTxMsg([badReplyType, "The word is no longer in play"], {ws}, initiatorWs=ws)) return False return True
def __finalizeJoin(self, ws, player): player.addConn(ws) self.playerByWs[ws] = player self.txQueue.put_nowait( ClientTxMsg(["JOIN-OKAY", player.name, player.team.teamNumber], {ws}, initiatorWs=ws))
def processHost(self, qmsg): """ original_message = [ "HOST", "taboo", {"numTeams": <int>, # 1..4 "turnDurationSec": <int>, # 30..180 "wordSets": ["name1", "name2", ...]}] qmsg.jmsg = original_message[2:] """ try: hostParameters = HostParameters.fromJmsg(qmsg.jmsg) except InvalidDataException as exc: self.txQueue.put_nowait( ClientTxMsg([MTYPE_HOST_BAD] + exc.toJmsg(), {qmsg.initiatorWs}, initiatorWs=qmsg.initiatorWs)) return True self.gameIdx += 1 newRoom = TabooRoom("taboo:{}".format(self.gameIdx), "Taboo Room #{}".format(self.gameIdx), hostParameters) self.rooms[self.gameIdx] = newRoom self.txQueue.put_nowait( InternalRegisterGi(newRoom, initiatorWs=qmsg.initiatorWs)) return True
def __validateCompletedOrDiscard(self, qmsg): """ Validates [COMPLETED|DISCARD, turn<int>, wordIdx<int>] Replies a DISCARD-BAD or COMPLETED-BAD if the message is incorrect, or if the message is received at wrong game state Returns True iff message format is valid """ msgType = qmsg.jmsg[0] assert msgType in ("DISCARD", "COMPLETED") badReplyType = "{}-BAD".format(msgType) ws = qmsg.initiatorWs if len(qmsg.jmsg) != 3: self.txQueue.put_nowait( ClientTxMsg([badReplyType, "Invalid message length"], {ws}, initiatorWs=ws)) return False if (not isinstance(qmsg.jmsg[1], int)) or (not isinstance( qmsg.jmsg[2], int)): self.txQueue.put_nowait( ClientTxMsg([badReplyType, "Invalid message type"], {ws}, initiatorWs=ws)) return False if self.state != GameState.RUNNING: trace(Level.play, "_process{} current state".format(msgType), self.state.name) self.txQueue.put_nowait( ClientTxMsg([badReplyType, "Game not running"], {ws}, initiatorWs=ws)) return False player = self.playerByWs[ws] if player != self.turnMgr.activePlayer: trace( Level.play, "_process{} msg rcvd from".format(msgType), player.name if player else None, "activePlayer", self.turnMgr.activePlayer.name if self.turnMgr.activePlayer else None) self.txQueue.put_nowait( ClientTxMsg([badReplyType, "It is not your turn"], {ws}, initiatorWs=ws)) return False return True
def testBasic(self): txq = asyncio.Queue() wordset = SupportedWordSets["test"] with stub(wordset, "nextWord", self.mockNextWord): allConns = Connections(txq) team1 = mockPlyrTeam(txq, allConns, 1, {"sb1": [101], "sb2": [102]}, {}) team2 = mockPlyrTeam(txq, allConns, 2, {"jg1": [201], "jg2": [202]}, {}) teams = {1: team1, 2: team2} hostParameters = HostParameters(numTeams=2, turnDurationSec=30, wordSets=["test"], numTurns=2) for team in teams.values(): for ws in team.conns._wss: allConns.addConn(ws) self.drainGiTxQueue(txq) turnMgr = TurnManager("taboo:1", txq, wordset, teams, hostParameters, allConns, None) def mockFindNextPlayer(): return team2.members["jg1"] stub1 = stub(turnMgr, "_findNextPlayer", mockFindNextPlayer) stub1.__enter__() # pylint: disable=no-member self.assertGiTxQueueMsgs(txq, [ ClientTxMsg(["SCORE", {1: 0, 2: 0}], {101, 102, 201, 202}), ], anyOrder=True) self.assertTrue(turnMgr.startNewTurn()) self.assertGiTxQueueMsgs(txq, [ ClientTxMsg(["WAIT-FOR-KICKOFF", 1, "jg1"], {101, 102, 201, 202}, None), ]) self.assertTrue(turnMgr.startNextWord()) secretMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'IN_PLAY', 'utcTimeout': None, # We didn't send KICKOFF explicitly 'secret': 'c', 'disallowed': ['c1', 'c2']}] publicMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'IN_PLAY', 'utcTimeout': None}] self.assertGiTxQueueMsgs(txq, [ ClientTxMsg(publicMsg, {101, 102, 201, 202}), ClientTxMsg(secretMsg, {101, 102}), ClientTxMsg(secretMsg, {201}), ], anyOrder=True)
def processRefresh(self, qmsg): if qmsg.jmsg[1] in SupportedWordSets: try: SupportedWordSets[qmsg.jmsg[1]].loadData() except Exception as exc: # pylint: disable=broad-except trace(Level.error, "Failed to refresh", qmsg.jmsg[1], str(exc)) self.txQueue.put_nowait( ClientTxMsg(["REFRESH-BAD"], {qmsg.initiatorWs}, initiatorWs=qmsg.initiatorWs)) return False # Pretend message wasn't understood
def testHandleNewConnection(self): """Handling of InternalGiStatus when a new client (connWs2) connects when (connWs1) is already connected""" self.plugin.giStatusByPath["foo:1"] = [True] self.plugin.conns.addConn(self.connWs1) # Process InternalGiStatus with two clients connected msg = InternalConnectWsToGi(self.connWs2) self.plugin.processMsg(msg) self.assertGiTxQueueMsgs(self.txq, [ClientTxMsg(["GAME-STATUS", "foo:1", True], {self.connWs2}, initiatorWs=None)])
def testNewGame(self): env = self.setUpTabooRoom() self.assertGiTxQueueMsgs(env.txq, [ InternalGiStatus([ {"hostParameters": {"numTeams": 2, "turnDurationSec": 30, "wordSets": ["test"], "numTurns": 1}, "gameState": "WAITING_TO_START", "clientCount": {1: {}, 2: {}}, "winners": []} ], "taboo:1"), ], anyOrder=True) ws1 = 1 env.room.processConnect(ws1) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["TEAM-STATUS", 1, []], {ws1}), ClientTxMsg(["TEAM-STATUS", 2, []], {ws1}), ClientTxMsg(['HOST-PARAMETERS', {'numTeams': 2, 'turnDurationSec': 30, 'wordSets': ['test'], 'numTurns': 1}], {ws1}), ClientTxMsg(["SCORE", {1: 0, 2: 0}], {ws1}), InternalGiStatus([ {"hostParameters": {"numTeams": 2, "turnDurationSec": 30, "wordSets": ["test"], "numTurns": 1}, "gameState": "WAITING_TO_START", "clientCount": {1: {}, 2: {}}, "winners":[] } ], "taboo:1"), ], anyOrder=True)
def processHost(self, qmsg): if qmsg.jmsg: self.txQueue.put_nowait( ClientTxMsg([MTYPE_HOST_BAD, "Unexpected parameters"], {qmsg.initiatorWs}, initiatorWs=qmsg.initiatorWs)) return True self.gameIdx += 1 newRoom = ChatRoom("chat:{}".format(self.gameIdx), "Chat Room #{}".format(self.gameIdx)) self.rooms[self.gameIdx] = newRoom self.txQueue.put_nowait( InternalRegisterGi(newRoom, initiatorWs=qmsg.initiatorWs)) return True
def testHandleMsg(self): """Received messages are broadcast to all clients""" self.testInstantiation() self.plugin.conns.addConn(self.connWs1) self.plugin.conns.addConn(self.connWs2) fakeWs = 55 msg = ClientRxMsg(["foo", 2, True, {"count": 3}], initiatorWs=fakeWs) self.plugin.processMsg(msg) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg(["foo", 2, True, { "count": 3 }], {self.connWs1, self.connWs2}, initiatorWs=fakeWs) ])
def send(self, jmaiList, wss=None): """Send the messages to a subset (or all) connections Arguments --------- jmsgs : list of messages wss : (optional) set of websockets Websockets to send the messages. If not specified, send the message to all websockets. """ wss = wss or self._wss if not wss: return for jmai in jmaiList: self._txQueue.put_nowait( ClientTxMsg(jmai.jmsg, wss, initiatorWs=jmai.initiatorWs))
async def worker(self): """The worker task for a plugin. All messages should be processed. Any unhandled message returns a bad message to the sender""" trace(Level.game, "{}.worker() ready".format(self.path)) while True: qmsg = await self.rxQueue.get() self.rxQueue.task_done() trace(Level.msg, self.path, "received", str(qmsg)) processed = self.processMsg(qmsg) if not processed: if not isinstance(qmsg, ClientRxMsg): trace(Level.error, "Unexpected message not handled:", str(qmsg)) continue self.txQueue.put_nowait( ClientTxMsg("Bad message", {qmsg.initiatorWs}, initiatorWs=qmsg.initiatorWs))
def processHost(self, qmsg): try: hostParameters = RoundParameters.fromJmsg(qmsg.jmsg) except InvalidDataException as exc: self.txQueue.put_nowait( ClientTxMsg([MTYPE_HOST_BAD] + exc.toJmsg(), {qmsg.initiatorWs}, initiatorWs=qmsg.initiatorWs)) return True self.gameIdx += 1 newRoom = Dirty7Room("dirty7:{}".format(self.gameIdx), "Dirty7 Room #{}".format(self.gameIdx), self.storage, hostParameters) self.rooms[self.gameIdx] = newRoom self.txQueue.put_nowait( InternalRegisterGi(newRoom, initiatorWs=qmsg.initiatorWs)) return True
def processKickoff(self, qmsg): """Always returns True (implies message ingested)""" ws = qmsg.initiatorWs if self._state != TurnMgrState.KICKOFF_WAIT: self._txQueue.put_nowait( ClientTxMsg(["KICKOFF-BAD", "Can't kickoff a turn"], {ws}, initiatorWs=ws)) return True # Start with a new word for the activePlayer ctx = {"turnId": self._curTurnId} self._txQueue.put_nowait( TimerRequest(self.turnDurationSec, self.timerExpiredCb, ctx)) self._utcTimeout = expiryEpoch(self.turnDurationSec) assert self.startNextWord( ) is True, "Must always be able to start a new word" return True
def testAddDelConnections(self): conns1 = Connections(self.txq) conns1.addConn(clientWs1) conns2 = Connections(self.txq) conns2.addConn(clientWs2) self.conns.addConnections(conns1) self.conns.addConnections(conns2) msgSrc = MsgSrc(self.conns) msgSrc.setMsgs([ Jmai([1], initiatorWs=clientWs3), Jmai([2], initiatorWs=clientWs3) ]) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs1}, initiatorWs=clientWs3), ClientTxMsg([1], {clientWs2}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs1}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs2}, initiatorWs=clientWs3) ], anyOrder=True) self.conns.delConnections(conns1) clientWs4 = 104 conns3 = Connections(self.txq) conns3.addConn(clientWs3) conns3.addConn(clientWs4) self.conns.addConnections(conns3) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs3, clientWs4}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs3, clientWs4}, initiatorWs=clientWs3) ], anyOrder=True)
def processPlay(self, qmsg): """ ["PLAY", {"dropCards": list of cards, "numDrawCards": int, "pickCards": list of cards}] Should only be processed if: 1. game state is player turn 2. ws has joined 3. player[ws].name == turn.current() """ ws = qmsg.initiatorWs if not isinstance(self.gameState, StatePlayerTurn): self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD", "Can't make moves now"], {ws}, initiatorWs=ws)) return True if self.playerByWs[ws] is None: self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD", "You must join the game first"], {ws}, initiatorWs=ws)) return True if self.playerByWs[ws].name != self.currRound.turn.current(): self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD", "It is not your turn"], {ws}, initiatorWs=ws)) return True if len(qmsg.jmsg) != 2: self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD", "Invalid message length"], {ws}, initiatorWs=ws)) return True playDesc = qmsg.jmsg[1] if not isinstance(playDesc, dict): self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD", "Invalid move description"], {ws}, initiatorWs=ws)) return True playDesc = dict(playDesc) # dropCards dropCards = playDesc.pop("dropCards", []) if not isinstance(dropCards, list): self.txQueue.put_nowait( ClientTxMsg( ["PLAY-BAD", "Invalid cards being dropped", dropCards], {ws}, initiatorWs=ws)) return True try: dropCards = [Card.fromJmsg(cd) for cd in dropCards] except InvalidDataException as exc: self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD"] + exc.toJmsg(), {ws}, initiatorWs=ws)) return True # numDrawCards numDrawCards = playDesc.pop("numDrawCards", 0) if not isinstance(numDrawCards, int): self.txQueue.put_nowait( ClientTxMsg([ "PLAY-BAD", "Drawing invalid number of cards", numDrawCards ], {ws}, initiatorWs=ws)) return True # pickCards pickCards = playDesc.pop("pickCards", []) if not isinstance(pickCards, list): self.txQueue.put_nowait( ClientTxMsg( ["PLAY-BAD", "Invalid cards being drawn", pickCards], {ws}, initiatorWs=ws)) return True try: pickCards = [Card.fromJmsg(cd) for cd in pickCards] except InvalidDataException as exc: self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD"] + exc.toJmsg(), {ws}, initiatorWs=ws)) return True # If item remain in play-description, complain if playDesc: self.txQueue.put_nowait( ClientTxMsg( ["PLAY-BAD", "Unrecognized play description", playDesc], {ws}, initiatorWs=ws)) return True # If the deck doesn't have numDrawCards if self.currRound.tableCards.deckCardCount() < numDrawCards: self.txQueue.put_nowait( ClientTxMsg([ "PLAY-BAD", "Drawing invalid number of cards", numDrawCards ], {ws}, initiatorWs=ws)) return None # pickCards should be in revealedCards if not self.currRound.tableCards.revealedCardsContains(pickCards): self.txQueue.put_nowait( ClientTxMsg([ "PLAY-BAD", "Picking cards not available", [cd.toJmsg() for cd in pickCards] ], {ws}, initiatorWs=ws)) return True event = self.currRound.rule.processPlay(self.currRound, self.playerByWs[ws], dropCards, numDrawCards, pickCards) if not event: self.txQueue.put_nowait( ClientTxMsg(["PLAY-BAD", "Invalid play"], {ws}, initiatorWs=ws)) else: jmsg = [ "UPDATE", self.currRound.roundParams.roundNum, { "PLAY": [ self.playerByWs[ws].name, [cd.toJmsg() for cd in dropCards], numDrawCards, [cd.toJmsg() for cd in pickCards] ] + event.toJmsg() } ] self.broadcast(jmsg) self.processEvent(event) return True
def test1MsgSrc1Websocket(self): """ Try various triggers of adding/removing MsgSrcs and Websockets """ self.assertGiTxQueueMsgs(self.txq, []) # Adding connections doesn't create a message self.conns.addConn(clientWs1) self.assertGiTxQueueMsgs(self.txq, []) # Adding msgSrc with no messages msgSrc1 = MsgSrc(self.conns) self.assertGiTxQueueMsgs(self.txq, []) # Set messages in msgSrc msgSrc1.setMsgs([ Jmai([1], initiatorWs=clientWs3), Jmai([2], initiatorWs=clientWs3) ]) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs1}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs1}, initiatorWs=clientWs3) ]) # Adding msgSrc with previous messages msgSrc2 = MsgSrc(self.conns) msgSrc2.setMsgs( [Jmai([True], initiatorWs=None), Jmai([False], initiatorWs=None)]) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([True], {clientWs1}), ClientTxMsg([False], {clientWs1}) ]) # Adding a second connection self.conns.addConn(clientWs2) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs2}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs2}, initiatorWs=clientWs3), ClientTxMsg([True], {clientWs2}, initiatorWs=None), ClientTxMsg([False], {clientWs2}, initiatorWs=None) ], anyOrder=True) # Adding msgSrc with state preset msgSrc2.setMsgs([Jmai(["yes"], initiatorWs=None)]) self.assertGiTxQueueMsgs( self.txq, [ClientTxMsg(["yes"], {clientWs1, clientWs2})]) # Delete msgSrc self.conns.delMsgSrc(msgSrc2) # Add another client self.conns.addConn(clientWs3) self.assertSetEqual(self.conns.msgSrcs, {msgSrc1}) self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg([1], {clientWs3}, initiatorWs=clientWs3), ClientTxMsg([2], {clientWs3}, initiatorWs=clientWs3) ], anyOrder=True) # Remove a client self.conns.delConn(clientWs2) self.assertGiTxQueueMsgs(self.txq, []) # Add msgSrc2 again self.conns.addMsgSrc(msgSrc2) self.assertGiTxQueueMsgs( self.txq, [ClientTxMsg(["yes"], {clientWs1, clientWs3}, initiatorWs=None)], anyOrder=True)
def testReady(self): env = self.setUpTabooRoom() self.drainGiTxQueue(env.txq) env.room.state = TabooRoom.GameState.WAITING_TO_START env.room.processMsg(ClientRxMsg(["READY", "stuff"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["READY-BAD", "Invalid message length"], {101}, 101), ]) env.room.processMsg(ClientRxMsg(["READY"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["READY-BAD", "Join first"], {101}, 101), ]) self.setUpTeamPlayer(env, 1, "sb1", [101]) self.setUpTeamPlayer(env, 1, "sb2", [102]) self.setUpTeamPlayer(env, 2, "jg1", [201]) self.setUpTeamPlayer(env, 2, "jg2", [202]) self.drainGiTxQueue(env.txq) def mockFindNextPlayer(remainingPlayers=[ env.room.playerByWs[201], env.room.playerByWs[101], env.room.playerByWs[202], ]): if remainingPlayers: return remainingPlayers.pop(0) return None with stub(env.room.turnMgr, "_findNextPlayer", mockFindNextPlayer): env.room.processMsg(ClientRxMsg(["READY"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'sb1', {'numConns': 1, 'ready': True, 'turnsPlayed': 0}], {101, 102, 201, 202}), ]) self.assertEqual(env.room.state, TabooRoom.GameState.WAITING_TO_START) env.room.processMsg(ClientRxMsg(["READY"], 102)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'sb2', {'numConns': 1, 'ready': True, 'turnsPlayed': 0}], {101, 102, 201, 202}), ]) self.assertEqual(env.room.state, TabooRoom.GameState.WAITING_TO_START) env.room.processMsg(ClientRxMsg(["READY"], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'jg1', {'numConns': 1, 'ready': True, 'turnsPlayed': 0}], {101, 102, 201, 202}), ]) self.assertEqual(env.room.state, TabooRoom.GameState.WAITING_TO_START) #If a player sends READY multiple times, it is replied with a READY-BAD env.room.processMsg(ClientRxMsg(["READY"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['READY-BAD', 'Already ready'], {101}, 101) ]) #READY from last of the (initial) players trigger start of the game env.room.processMsg(ClientRxMsg(["READY"], 202)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'jg2', {'numConns': 1, 'ready': True, 'turnsPlayed': 0}], {101, 102, 201, 202}), ClientTxMsg(["WAIT-FOR-KICKOFF", 1, "jg1"], {101, 102, 201, 202}, None), ]) self.assertEqual(env.room.state, TabooRoom.GameState.RUNNING) #A late-joinee is connected in READY state when it joins self.setUpTeamPlayer(env, 1, "sb3", [103]) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["READY"], 103)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['READY-BAD', 'Already ready'], {103}, 103) ])
def testJoin(self): env = self.setUpTabooRoom() ws1 = 101 env.room.processConnect(ws1) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["JOIN"], ws1)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["JOIN-BAD", "Invalid message length"], {ws1}, ws1), ]) env.room.processMsg(ClientRxMsg(["JOIN", "sb1", -1], ws1)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["JOIN-BAD", "Invalid team number", -1], {ws1}, ws1), ]) env.room.processMsg(ClientRxMsg(["JOIN", "sb1", 3], ws1)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["JOIN-BAD", "Invalid team number", 3], {ws1}, ws1), ]) env.room.processMsg(ClientRxMsg(["JOIN", "#$H", 1], ws1)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["JOIN-BAD", "Invalid player name", "#$H"], {ws1}, ws1), ]) #Good join - specified team number env.room.processMsg(ClientRxMsg(["JOIN", "sb1", 1], ws1)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'sb1', {'numConns': 0, 'ready': False, 'turnsPlayed': 0}], {101}), ClientTxMsg(['PLAYER-STATUS', 'sb1', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {101}), ClientTxMsg(["TEAM-STATUS", 1, ["sb1"]], {101}), ClientTxMsg(["JOIN-OKAY", "sb1", 1], {ws1}, ws1), ], anyOrder=True) #Join more players self.setUpTeamPlayer(env, 1, "sb2", [102]) self.setUpTeamPlayer(env, 2, "jg1", [201]) self.setUpTeamPlayer(env, 2, "jg2", [202]) #Join one more player in team 2 self.setUpTeamPlayer(env, 2, "jg3", [203]) self.drainGiTxQueue(env.txq) #A random-team join (team = 0) should lead to "water-fill" ws2 = 1001 env.room.processConnect(ws2) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["JOIN", "xx", 0], ws2)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["TEAM-STATUS", 1, ["sb1", "sb2", "xx"]], {101, 102, 201, 202, 203, 1001}), ClientTxMsg(['PLAYER-STATUS', 'xx', {'numConns': 0, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001}), ClientTxMsg(['PLAYER-STATUS', 'xx', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001}), ClientTxMsg(["JOIN-OKAY", "xx", 1], {ws2}, ws2), ], anyOrder=True) #Now run a bunch of new JOINs on team 2 self.setUpTeamPlayer(env, 2, "jg4", [204]) self.setUpTeamPlayer(env, 2, "jg5", [205]) # The next two random JOINs should be assgnd team 1 ws2 = ws2 + 1 env.room.processConnect(ws2) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["JOIN", "yy", 0], ws2)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["TEAM-STATUS", 1, ["sb1", "sb2", "xx", "yy"]], {101, 102, 201, 202, 203, 1001, 204, 205, 1002}), ClientTxMsg(['PLAYER-STATUS', 'yy', {'numConns': 0, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001, 204, 205, 1002}), ClientTxMsg(['PLAYER-STATUS', 'yy', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001, 204, 205, 1002}), ClientTxMsg(["JOIN-OKAY", "yy", 1], {ws2}, ws2), ], anyOrder=True) ws2 = ws2 + 1 env.room.processConnect(ws2) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["JOIN", "zz", 0], ws2)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["TEAM-STATUS", 1, ["sb1", "sb2", "xx", "yy", "zz"]], {101, 102, 201, 202, 203, 1001, 204, 205, 1002, 1003}), ClientTxMsg(['PLAYER-STATUS', 'zz', {'numConns': 0, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001, 204, 205, 1002, 1003}), ClientTxMsg(['PLAYER-STATUS', 'zz', {'numConns': 1, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001, 204, 205, 1002, 1003}), ClientTxMsg(["JOIN-OKAY", "zz", 1], {ws2}, ws2), ], anyOrder=True) #A JOIN from an unrecognized ws ofc leads to assert with self.assertRaises(AssertionError): env.room.processMsg(ClientRxMsg(["JOIN", "zz", 0], ws2+1)) #A re-JOIN from a plyr forces it to its original team env.room.processConnect(2001) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["JOIN", "jg1", 1], 2001)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'jg1', {'numConns': 2, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001, 204, 205, 1002, 1003, 2001}), ClientTxMsg(["JOIN-OKAY", "jg1", 2], {2001}, 2001), ]) #A re-JOIN from a plyr forces it to its original team env.room.processConnect(2002) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["JOIN", "jg1", 0], 2002)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(['PLAYER-STATUS', 'jg1', {'numConns': 3, 'ready': False, 'turnsPlayed': 0}], {101, 102, 201, 202, 203, 1001, 204, 205, 1002, 1003, 2001, 2002}), ClientTxMsg(["JOIN-OKAY", "jg1", 2], {2002}, 2002), ])
def testBasic(self): turn = self.setUpWord() self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg(["TEAM-STATUS", 1, []], {101, 102, 103}), ClientTxMsg(["TEAM-STATUS", 1, ['sb']], {101, 102, 103}), ClientTxMsg(["TEAM-STATUS", 2, []], {101, 102, 103}), ClientTxMsg(["TURN", 1, 1, {"team": 1, "player": "sb", "state": "IN_PLAY", "utcTimeout": 30}], {101, 102, 103}), ClientTxMsg(["TURN", 1, 1, {"team": 1, "player": "sb", "state": "IN_PLAY", "utcTimeout": 30, "secret": "a", "disallowed": ["a1", "a2"]}], {101}), ClientTxMsg(["TURN", 1, 1, {"team": 1, "player": "sb", "state": "IN_PLAY", "utcTimeout": 30, "secret": "a", "disallowed": ["a1", "a2"]}], {102}), ClientTxMsg(["PLAYER-STATUS", "sb", {"numConns": 1, "ready": False, "turnsPlayed": 0}], {101, 102, 103}), ], anyOrder=True) self.assertIsNotNone(turn._privateMsgSrc) turn._state = WordState.COMPLETED turn._score = [1] turn.updateMsgs() self.assertIsNone(turn._privateMsgSrc) # privateMsgSrc should be deleted self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg(["TURN", 1, 1, {"team": 1, "player": "sb", "state": "COMPLETED", "secret": "a", "disallowed": ["a1", "a2"], "score": [1]}], {101, 102, 103}), ], anyOrder=True) turn._state = WordState.DISCARDED turn._score = [2] turn.updateMsgs() self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg(["TURN", 1, 1, {"team": 1, "player": "sb", "state": "DISCARDED", "secret": "a", "disallowed": ["a1", "a2"], "score": [2]}], {101, 102, 103}), ], anyOrder=True) turn._state = WordState.TIMED_OUT turn._score = [2] turn.updateMsgs() self.assertGiTxQueueMsgs(self.txq, [ ClientTxMsg(["TURN", 1, 1, {"team": 1, "player": "sb", "state": "TIMED_OUT", "secret": "a", "disallowed": ["a1", "a2"], "score": [2]}], {101, 102, 103}), ], anyOrder=True)
def testCompleted(self): env = self.setUpTabooRoom() self.drainGiTxQueue(env.txq) with stubs([(SupportedWordSets["test"], "nextWord", self.mockNextWord), (Taboo.TurnManager, "expiryEpoch", stubExpiryEpochGen())]): env.room.processMsg(ClientRxMsg(["COMPLETED"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Invalid message length"], {101}, 101), ]) env.room.processMsg(ClientRxMsg(["COMPLETED", "foo", 1], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Invalid message type"], {101}, 101), ]) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 1], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Game not running"], {101}, 101), ]) self.setUpTeamPlayer(env, 1, "sb1", [101]) self.setUpTeamPlayer(env, 1, "sb2", [102]) self.setUpTeamPlayer(env, 2, "jg1", [201]) self.setUpTeamPlayer(env, 2, "jg2", [202]) def mockFindNextPlayer(remainingPlayers=[ env.room.playerByWs[201], env.room.playerByWs[101], env.room.playerByWs[202], ]): if remainingPlayers: return remainingPlayers.pop(0) return None with stub(env.room.turnMgr, "_findNextPlayer", mockFindNextPlayer): env.room._allPlayersReady() self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 1], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "It is not your turn"], {101}, 101), ]) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 1], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Can't COMPLETED right now"], {201}, 201), #sic ]) # KICKOFF turn env.room.processMsg(ClientRxMsg(["KICKOFF"], 201)) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["COMPLETED", 0, 1], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Invalid turn"], {201}, 201), ]) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 0], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Invalid word"], {201}, 201), ]) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 1], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["TURN", 1, 1, {"team": 2, "player": "jg1", "state": "COMPLETED", "secret": "c", "disallowed": ["c1", "c2"], "score": [2]}], {101, 102, 201, 202}), ClientTxMsg(["SCORE", {1: 0, 2: 1}], {101, 102, 201, 202}), ClientTxMsg(["TURN", 1, 2, {"team": 2, "player": "jg1", "state": "IN_PLAY", "utcTimeout": 30}], {101, 102, 201, 202}), ClientTxMsg(["TURN", 1, 2, {"team": 2, "player": "jg1", "state": "IN_PLAY", "utcTimeout": 30, "secret": "a", "disallowed": ["a1", "a2"]}], {201}), ClientTxMsg(["TURN", 1, 2, {"team": 2, "player": "jg1", "state": "IN_PLAY", "utcTimeout": 30, "secret": "a", "disallowed": ["a1", "a2"]}], {101, 102}), ], anyOrder=True) self.assertEqual(env.room.teams[2].members['jg1'].turnsPlayed, 0) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 2], 201)) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 3], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["TURN", 1, 3, {"team": 2, "player": "jg1", "state": "COMPLETED", "secret": "b", "disallowed": ["b1", "b2"], "score": [2]}], {101, 102, 201, 202}), ClientTxMsg(["SCORE", {1: 0, 2: 3}], {101, 102, 201, 202}), ClientTxMsg(["GAME-OVER", [2]], {101, 102, 201, 202}), InternalGiStatus([ {"hostParameters": {"numTeams": 2, "turnDurationSec": 30, "wordSets": ["test"], "numTurns": 1}, "gameState": "GAME_OVER", "clientCount": {1: {'sb1': 1, 'sb2': 1}, 2: {'jg1': 1, 'jg2': 1}}, "winners": [2] } ], "taboo:1"), ], anyOrder=True) self.assertEqual(env.room.teams[2].members['jg1'].turnsPlayed, 1) env.room.processMsg(ClientRxMsg(["COMPLETED", 1, 3], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["COMPLETED-BAD", "Game not running"], {201}, 201), ])
def testTurnTimeOut(self): env = self.setUpTabooRoom() env.room.state = TabooRoom.GameState.RUNNING self.setUpTeamPlayer(env, 1, "sb1", [101]) self.setUpTeamPlayer(env, 1, "sb2", [102]) self.setUpTeamPlayer(env, 2, "jg1", [201]) self.setUpTeamPlayer(env, 2, "jg2", [202]) self.drainGiTxQueue(env.txq) def mockFindNextPlayer(remainingPlayers=[ env.room.playerByWs[201], env.room.playerByWs[101], env.room.playerByWs[202], ]): if remainingPlayers: return remainingPlayers.pop(0) return None with stubs([(SupportedWordSets["test"], "nextWord", self.mockNextWord), (Taboo.TurnManager, "expiryEpoch", stubExpiryEpochGen()), (env.room.turnMgr, "_findNextPlayer", mockFindNextPlayer)]): env.room.turnMgr.startNewTurn() self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["WAIT-FOR-KICKOFF", 1, "jg1"], {101, 102, 201, 202}, None), ], anyOrder=True) # Start the timer by issuing a KICKOFF env.room.processMsg(ClientRxMsg(["KICKOFF"], 201)) secretMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'IN_PLAY', 'utcTimeout': 30, 'secret': 'c', 'disallowed': ['c1', 'c2']}] publicMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'IN_PLAY', 'utcTimeout': 30}] self.assertGiTxQueueMsgs(env.txq, [ TimerRequest(30, env.room.turnMgr.timerExpiredCb, { "turnId": 1, }), ClientTxMsg(secretMsg, {201}), ClientTxMsg(secretMsg, {101, 102}), ClientTxMsg(publicMsg, {101, 102, 201, 202}), ], anyOrder=True) self.assertEqual(env.room.teams[2].members['jg1'].turnsPlayed, 0) # Invalid turnId env.room.turnMgr.timerExpiredCb({"turnId": 5}) # Valid timer expiry, starts the next turn env.room.turnMgr.timerExpiredCb({"turnId": 1}) publicMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'TIMED_OUT', 'secret': 'c', 'disallowed': ['c1', 'c2'], 'score': [1]}] self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(publicMsg, {101, 102, 201, 202}), ClientTxMsg(["WAIT-FOR-KICKOFF", 2, "sb1"], {101, 102, 201, 202}, None), ClientTxMsg(["SCORE", {1: 1, 2: 0}], {101, 102, 201, 202}), ], anyOrder=True) self.assertEqual(env.room.teams[2].members['jg1'].turnsPlayed, 1) # KICKOFF new turn, discard 1st word, let timer expire on the last word env.room.processMsg(ClientRxMsg(["KICKOFF"], 101)) secretMsg = ['TURN', 2, 1, {'team': 1, 'player': 'sb1', 'state': 'IN_PLAY', 'utcTimeout': 31, 'secret': 'a', 'disallowed': ['a1', 'a2']}] publicMsg = ['TURN', 2, 1, {'team': 1, 'player': 'sb1', 'state': 'IN_PLAY', 'utcTimeout': 31}] self.assertGiTxQueueMsgs(env.txq, [ TimerRequest(30, env.room.turnMgr.timerExpiredCb, { "turnId": 2, }), ClientTxMsg(secretMsg, {101}), ClientTxMsg(secretMsg, {201, 202}), ClientTxMsg(publicMsg, {101, 102, 201, 202}), ], anyOrder=True) env.room.processMsg(ClientRxMsg(["DISCARD", 2, 1], 101)) self.drainGiTxQueue(env.txq) with stub(env.room.turnMgr._wordSet, "areWordsAvailable", lambda path: False): env.room.turnMgr.timerExpiredCb({"turnId": 2}) publicMsg = ['TURN', 2, 2, {'team': 1, 'player': 'sb1', 'state': 'TIMED_OUT', 'secret': 'b', 'disallowed': ['b1', 'b2'], 'score': [2]}] self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(publicMsg, {101, 102, 201, 202}), ClientTxMsg(["SCORE", {1: 1, 2: 2}], {101, 102, 201, 202}), ClientTxMsg(["GAME-OVER", [2]], {101, 102, 201, 202}), InternalGiStatus([ {"hostParameters": {"numTeams": 2, "turnDurationSec": 30, "wordSets": ["test"], "numTurns": 1}, "gameState": "GAME_OVER", "clientCount": {1: {'sb1': 1, 'sb2': 1}, 2: {'jg1': 1, 'jg2': 1}}, "winners": [2] } ], "taboo:1"), ], anyOrder=True) self.assertEqual(env.room.teams[1].members['sb1'].turnsPlayed, 1) # Test timer fire after the game is over env.room.turnMgr.timerExpiredCb({"turnId": 2}) self.assertGiTxQueueMsgs(env.txq, []) self.assertEqual(env.room.teams[1].members['sb1'].turnsPlayed, 1)
def testKickoff(self): env = self.setUpTabooRoom() self.drainGiTxQueue(env.txq) with stubs([(SupportedWordSets["test"], "nextWord", self.mockNextWord), (Taboo.TurnManager, "expiryEpoch", stubExpiryEpochGen())]): env.room.processMsg(ClientRxMsg(["KICKOFF", 2], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["KICKOFF-BAD", "Invalid message length"], {101}, 101), ]) env.room.processMsg(ClientRxMsg(["KICKOFF"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["KICKOFF-BAD", "Game not running"], {101}, 101), ]) env.room.state = TabooRoom.GameState.RUNNING self.setUpTeamPlayer(env, 1, "sb1", [101]) self.setUpTeamPlayer(env, 1, "sb2", [102]) self.setUpTeamPlayer(env, 2, "jg1", [201]) self.setUpTeamPlayer(env, 2, "jg2", [202]) self.drainGiTxQueue(env.txq) env.room.processMsg(ClientRxMsg(["KICKOFF"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["KICKOFF-BAD", "It is not your turn"], {101}, 101), ]) def mockFindNextPlayer(remainingPlayers=[ env.room.playerByWs[201], env.room.playerByWs[101], env.room.playerByWs[202], ]): if remainingPlayers: return remainingPlayers.pop(0) return None with stub(env.room.turnMgr, "_findNextPlayer", mockFindNextPlayer): env.room.turnMgr.startNewTurn() env.room.processMsg(ClientRxMsg(["KICKOFF"], 101)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["WAIT-FOR-KICKOFF", 1, "jg1"], {101, 102, 201, 202}, None), ClientTxMsg(["KICKOFF-BAD", "It is not your turn"], {101}, 101), ]) env.room.processMsg(ClientRxMsg(["KICKOFF"], 201)) secretMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'IN_PLAY', 'utcTimeout': 30, 'secret': 'c', 'disallowed': ['c1', 'c2']}] publicMsg = ['TURN', 1, 1, {'team': 2, 'player': 'jg1', 'state': 'IN_PLAY', 'utcTimeout': 30}] self.assertGiTxQueueMsgs(env.txq, [ TimerRequest(30, env.room.turnMgr.timerExpiredCb, { "turnId": 1, }), ClientTxMsg(secretMsg, {201}), ClientTxMsg(secretMsg, {101, 102}), ClientTxMsg(publicMsg, {101, 102, 201, 202}), ], anyOrder=True) env.room.processMsg(ClientRxMsg(["KICKOFF"], 201)) self.assertGiTxQueueMsgs(env.txq, [ ClientTxMsg(["KICKOFF-BAD", "Can't kickoff a turn"], {201}, 201), ])