def Join(self, player): if self.IsFull: # can't really be hit by normal users, this can only get hit by trying to add agents LogUtility.Warning( f"'{player.Name}' cannot join the game, the game is full!", self) return # TODO: This is kind of nasty and we should probably have a server player # and a game instance player entity, but this will do as a first pass. This # implies that the server is a "common world" instead of having a meta-player identity player._Player__isReady = False self.__players.add(player) LogUtility.CreateGameMessage(f"'{player.Name}' has joined.", self) return
def Vote(self, vote): if not vote: LogUtility.Error("Vote cannot be casted, it is null.", self) return if self.TimeOfDay == TimeOfDayEnum.Day: self.VoteDay(vote) return if self.TimeOfDay == TimeOfDayEnum.Night: self.VoteNight(vote) return LogUtility.Error("Time of day is not Day/Night", self) return
def Run(self): try: self.__connection.bind(NetConstants.ADDRESS); self.__connection.listen(); LogUtility.Information(f"Server successfully started at {NetConstants.IP}:{NetConstants.PORT}"); self.ShowActiveConnections(); except socket.error as error: trace = traceback.format_exc(); LogUtility.Error(str(error) + "\n\n" + trace); while True: connection, address = self.__connection.accept(); threading.Thread(target = self.ClientHandle, args = (connection, address)).start(); return;
def VoteNight(self, vote): alivePlayers = [ap for ap in self.Players if ap.IsAlive] playerIdentifiers = [p.Identifier for p in alivePlayers] if not vote.Player.Identifier in self.__playerIdentifiersThatCanVote: playerDetails = "'" + vote.Player.Name + "' - " + vote.Player.Identifier LogUtility.Error(f"{playerDetails} cannot act in the night.", self) return VoteResultTypeEnum.InvalidAction if vote.VotedPlayer\ and not vote.VotedPlayer.Identifier in playerIdentifiers\ and not vote.Player.Role.CanTargetDeadPlayers: # the seer can check dead players playerDetails = vote.VotedPlayer.Name + " - " + vote.VotedPlayer.Identifier LogUtility.Error( f"Invalid vote, target player {playerDetails} is not in the game", self) return VoteResultTypeEnum.DeadPlayerTargeted player = self.GetPlayerByIdentifier(vote.Player.Identifier) targetPlayer = self.GetPlayerByIdentifier( vote.VotedPlayer.Identifier) if vote.VotedPlayer else None voteResult = VoteResultTypeEnum.WaitAction if vote.PlayerType == PlayerTypeEnum.Werewolf: voteResult = self.Attack(player, targetPlayer) elif vote.PlayerType == PlayerTypeEnum.Seer: voteResult = self.Divine(player, targetPlayer) elif vote.PlayerType == PlayerTypeEnum.Guard: voteResult = self.Guard(player, targetPlayer) else: # I know this should semantically be before the actual addition of # the vote. However, we rely on the previous security checks LogUtility.Error( f"'{player.Name}' does not have a valid night role - {player.Role.Type}", self) return VoteResultTypeEnum.CannotActDuringTimeOfDay self.Votes.add(vote) self.__playerIdentifiersThatCanVote.remove(player.Identifier) if not self.__playerIdentifiersThatCanVote: self.CountNightVotesAndEvents() return voteResult
def DoVillagersWin(game): werewolves = [w for w in game.Players if w.IsAlive\ and w.Role.Type == PlayerTypeEnum.Werewolf] if werewolves: return False; # if all werewolves are dead, the villagers win LogUtility.CreateGameMessage("\n\n\n\t\t\tVillagers win!\n\n", game); return True;
def Connect(self, connection, packet): dto = packet.Data; player = Player(dto.ClientName, dto.ClientIdentifier); connectionKey = connection.getpeername(); self.Connections[connectionKey] = player; LogUtility.Information(f"Connected to server (player {player}) - {connectionKey}"); self.ShowActiveConnections(); connection.sendall(pickle.dumps(player)); return;
def ClientHandle(self, connection, address): while True: try: packetStream = connection.recv(4 * NetConstants.KILOBYTE); if not packetStream: # connection is interrupted/closed by client because we get null back break; packet = pickle.loads(packetStream); if not packet or not packet.PacketType: # This is really just a sanity check and making sure nothing # unknown is coming that could potentially break the server break; LogUtility.Request(f"Packet type - {packet.PacketType}"); if packet.PacketType in self.ValidPacketTypes: self.HandlerContext.RedirectPacket(connection, packet); else: clientKey = connection.getpeername(); client = self.Connections[clientKey]; # this has caught me off a few times LogUtility.Error(f"Packet type is not supported - {packet.PacketType}, " +\ f"sent from {client.Name} - {clientKey}. Add to valid packet types!"); connection.sendall(pickle.dumps(False)); except Exception as error: trace = traceback.format_exc(); LogUtility.Error(str(error) + "\n\n" + trace); break; self.Disconnect(connection); return;
def __init__(self): self.__connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM); self.__handlerContext = HandlerContext(self); self.__validPacketTypes = PacketTypeEnum.Values(); self.__connections = dict(); self.__games = dict(); self.__handlerContext.CreateGame("Game Alpha"); self.__handlerContext.CreateGame("Game Beta"); self.__handlerContext.CreateGame("Game Gamma"); self.__handlerContext.CreateGame("Game Delta"); LogUtility.Information("Server start up"); return;
def DoWerewolvesWin(game): werewolves = [w for w in game.Players if w.IsAlive\ and w.Role.Type == PlayerTypeEnum.Werewolf]; villagers = [w for w in game.Players if w.IsAlive\ and w.Role.Type != PlayerTypeEnum.Werewolf]; # this just makes debugging easier since this gets called many times if len(werewolves) < len(villagers): return False; werewolfNames = "\n\t\t\t "+ "\n\t\t\t "\ .join([w.Name for w in game.Players if w.Role.Type == PlayerTypeEnum.Werewolf]); LogUtility.CreateGameMessage(f"\n\n\n\t\t\tWerewolves win!{werewolfNames}\n\n", game); return True;
def CheckWinCondition(self): if not self.HasStarted: return (False, FactionTypeEnum._None) if len(self.Players) < GameConstants.MINIMAL_PLAYER_COUNT: LogUtility.CreateGameMessage( f"Minimum of {GameConstants.MINIMAL_PLAYER_COUNT} players required.", self) return (True, FactionTypeEnum._None) # this will be defined in GameRules.py if GameRules.DoVillagersWin(self): return (True, FactionTypeEnum.Villagers) if GameRules.DoWerewolvesWin(self): return (True, FactionTypeEnum.Werewolves) return (False, FactionTypeEnum._None)
def VotePlayer(self, connection, packet): dto = packet.Data game = self.HandlerContext.GetGameWithIdentifier(dto.GameIdentifier) if not self.HandlerContext.IsGameActionValid(game, dto): connection.sendall(pickle.dumps(False)) player = game.GetPlayerByIdentifier(dto.Player.Identifier) targetPlayer = game.GetPlayerByIdentifier(dto.TargetPlayerIdentifier) if game.HasPlayerVotedAlready(player.Identifier): LogUtility.Error( f"'{player.Name}' - {player.Identifier} has voted already", game) connection.sendall(pickle.dumps(False)) return vote = Vote(player, targetPlayer) game.Vote(vote) connection.sendall(pickle.dumps(True)) return
def Disconnect(self, connection): connectionKey = connection.getpeername(); player = None; if connectionKey in self.Connections.keys(): player = self.Connections[connectionKey]; gameIdentifier = self.HandlerContext.IsPlayerAlreadyInAGame(player); if gameIdentifier: game = self.HandlerContext.GetGameWithIdentifier(gameIdentifier); game.Leave(player); self.Connections.pop(connectionKey); LogUtility.Information(f"Lost connection (player {player}) to server - {connectionKey}"); connection.shutdown(socket.SHUT_RDWR); connection.close(); self.ShowActiveConnections(); return;
def Whisper(self, connection, packet): messageRequestDto = packet.Data if not messageRequestDto or not messageRequestDto.IsValid: connection.sendall(pickle.dumps(False)) return game = self.HandlerContext.GetGameWithIdentifier( messageRequestDto.GameIdentifier) talkMessage = messageRequestDto.TalkMessage player = game.GetPlayerByIdentifier(messageRequestDto.PlayerIdentifier) targetPlayer = None if messageRequestDto.TargetPlayerIdentifier: targetPlayer = game.GetPlayerByIdentifier( messageRequestDto.TargetPlayerIdentifier) messageMetaDataDto = MessageMetaDataDto(\ messageRequestDto.PlayerIdentifier,\ messageRequestDto.TargetPlayerIdentifier,\ messageRequestDto.TargetPlayerRole,\ talkMessage.MessageType) messageText = self.ConstructMessageText(\ talkMessage,\ targetPlayer,\ messageMetaDataDto.TargetRole) # this is automatically added to the game, no need to serialize # it and send it back the game loop will handle all of the magic messageDto = LogUtility.CreateWhisperGameMessage(\ player,\ messageText,\ messageMetaDataDto,\ game,\ player.Role.Type) connection.sendall(pickle.dumps(True)) return
def Start(self): if (len(self.__players) < GameConstants.MINIMAL_PLAYER_COUNT): LogUtility.Error( f"Cannot start game without having at least {GameConstants.MINIMAL_PLAYER_COUNT} players.", self) return for player in self.__players: player._Player__isAlive = True player._Player__isReady = False GameRules.DistributeRolesBaseGame(self) self.__hasStarted = True self.__turn = 0 for agent in self.AgentPlayers: agent.PreGameSetup() self.StartDay() return
def Restart(self): LogUtility.CreateGameMessage("Restarting game lobby", self) for player in self.__players: player._Player__isAlive = True player._Player__isReady = False player._Player__role = None for agent in self.AgentPlayers: agent._Player__isReady = True self.__hasStarted = False self.__votes = set() messageCutOffPoint = datetime.datetime.utcnow() -\ datetime.timedelta(minutes = 1) self.__messages = { m for m in self.__messages if m.TimeUtc > messageCutOffPoint } self.__turn = int() self.__timeOfDay = TimeOfDayEnum._None
def CountNightVotesAndEvents(self): # remove the "wait" calls actualVotes = [v for v in self.Votes if v.VotedPlayer] # actual votes votesToKill = [ v for v in actualVotes if v.PlayerType == PlayerTypeEnum.Werewolf ] # independent actions votesToGuard = [ v for v in actualVotes if v.PlayerType == PlayerTypeEnum.Guard ] votesToDivine = [ v for v in actualVotes if v.PlayerType == PlayerTypeEnum.Seer ] (playerToKill, werewolfAttackTimes) = self.GetPlayerAndTimesVoted(votesToKill) if not playerToKill or not werewolfAttackTimes: LogUtility.CreateGameMessage( f"No one gets attacked during the night.", self) else: # we only want to make the fact known that someone was guarded if an attack on them # had occurred during the night. Otherwise it would advantage the werewolves. guardsForAttackedPlayer = [v.Player for v in votesToGuard \ if playerToKill.Identifier == v.VotedPlayer.Identifier] if not guardsForAttackedPlayer: self.WerewolfKill(playerToKill) else: LogUtility.CreateGameMessage("'" + playerToKill.Name +\ "' was attacked by werewolves in the night but was guarded and lives to see another day.", self) for vote in votesToDivine: divinedPlayer = vote.VotedPlayer isDivinedPlayerWereolf = divinedPlayer.Role.Type == PlayerTypeEnum.Werewolf negator = str() if isDivinedPlayerWereolf else "NOT" message = f"'{divinedPlayer.Name}' was divined, they are {negator} a werewolf." LogUtility.CreatePrivateGameMessage(message, self, vote.Player.Identifier) pass # Get votes for seer (these are independent from everything else) gameIsOver, winningFaction = self.CheckWinCondition() if gameIsOver: for agent in self.AgentPlayers: agent.PreGameSetup() if self.__agentsAutomaticallyPlay: # don't go to other turn and don't start the day if the game is over self.Restart() return self.StartDay() return
def GetGameWithIdentifier(self, gameIdentifier): if not gameIdentifier in self.Server.Games.keys(): LogUtility.Error(f"Game {gameIdentifier} does not exist."); return None; return self.Server.Games[gameIdentifier];
def ShowActiveConnections(self): LogUtility.Information(f"Active connections - {len(self.__connections)}");
def ShowTurnAndTime(self): capitalGameTime = str(self.TimeOfDay).capitalize() LogUtility.CreateGameMessage( f"\n\n\n\t\tTurn: {self.Turn}\t\tTime: {capitalGameTime}\n\n", self) return