Este projeto é uma implementação do jogo de dedução social e blefe "The Resistance", adaptado para ser jogado em uma rede local (LAN). Ele proporciona uma experiência interativa onde os jogadores assumem papéis de Resistência ou Espiões e participam de diversas fases do jogo, como seleção de equipes, votação e sabotagem de missões.
"The Resistance" é um jogo de dedução social e blefe para 5 jogadores. O objetivo é para a equipe da Resistência (maioria) completar missões, enquanto os Espiões (minoria) tentam sabotá-las.
Papéis:
- Resistência: Trabalhadores leais; votam SUCESSO.
- Espiões: Infiltrados; podem votar SUCESSO ou FALHA.
- O Líder propõe uma equipe.
- Os jogadores votam para aprovar ou rejeitar a equipe.
- Se a equipe for aprovada, os membros da equipe votam SUCESSO ou FALHA.
- Resistência: Vence se 3 missões forem bem-sucedidas.
- Espiões: Vencem se 3 missões falharem OU se 5 propostas de equipe forem rejeitadas consecutivamente.
O projeto segue o padrão arquitetural MVC para uma clara separação de responsabilidades, facilitando o desenvolvimento, a manutenção e a escalabilidade.
- Modelo (
src/models/model.py): Gerencia o estado do jogo e a lógica de negócios. Não possui conhecimento da interface do usuário ou da rede. - Visão (
src/views/view.py,src/models/dialogs.py,src/main.py): Responsável pela interface gráfica do usuário, exibindo o estado do jogo e coletando entradas do jogador. - Controlador (
src/controllers/controller.py): Atua como intermediário, processando entradas do usuário, atualizando o Modelo e a Visão, e gerenciando a comunicação de rede. - Rede (
src/utils/network.py,src/models/messages.py): Camada responsável pela comunicação cliente-servidor, definindo o protocolo de mensagens.
A classe GameModel encapsula todo o estado do jogo e suas regras. É o coração lógico do sistema.
- Gerencia a atribuição de papéis (Resistência e Espião) aos jogadores.
- Controla o placar das missões (sucessos e falhas).
- Acompanha a rodada atual, o líder e o contador de propostas de equipe rejeitadas.
- Processa votos de aprovação de equipe e escolhas de sabotagem.
- Verifica as condições de vitória e determina o vencedor.
- Oferece métodos para serializar e desserializar o estado do jogo (
to_dictefrom_dict), permitindo persistência.
Utiliza a biblioteca tkinter para construir a interface gráfica.
GameView(src/views/view.py): Exibe o placar, a rodada atual, o líder e um log de eventos do jogo.src/models/dialogs.py: Contém classes para diálogos modais interativos (TeamSelectionDialog,YesNoDialog,MissionOutcomeDialog,GameOverDetailsDialog) que permitem aos jogadores realizar ações como selecionar equipes, votar e decidir sobre sabotagens.- As configurações de estilo (cores, fontes, etc.) são definidas em
src/utils/settings.pye aplicadas aos widgetstkinterettk.
Orquestra o fluxo do jogo, agindo como um ponto central de controle.
- Inicializa o Modelo e a Visão.
- Atua como servidor ou cliente de rede, dependendo da configuração.
- Despacha mensagens de rede para os manipuladores apropriados.
- Coordena as fases do jogo, solicitando entradas dos jogadores (via View) e atualizando o Modelo.
- Responsável por salvar e carregar o estado do jogo, permitindo a retomada de partidas.
Gerencia a comunicação entre os diferentes jogadores (clientes) e o servidor.
Network(classe base): Define a funcionalidade comum para enviar e receber mensagens via sockets TCP.GameServer: Responsável por ouvir conexões, aceitar novos clientes e enviar mensagens para todos ou para clientes específicos.GameClient: Responsável por conectar-se ao servidor e enviar/receber mensagens.src/models/messages.py: Define as dataclasses para os diferentes tipos de mensagens trocadas no protocolo (ex:ConnectAckMessage,GameStateUpdateMessage,RequestTeamSelectionMessage, etc.).- As mensagens são serializadas/desserializadas em formato JSON para transmissão.
O projeto faz uso extensivo de threads para garantir que a aplicação permaneça responsiva e para lidar com operações de I/O bloqueantes (como rede) de forma assíncrona. Isso evita o congelamento da interface do usuário.
- Recebimento de Mensagens (
Network._receive_messages): Tanto o servidor quanto o cliente dedicam uma thread para ouvir continuamente por mensagens de entrada. Isso impede que a espera por dados de rede bloqueie a execução de outras partes do programa. - Aceitação de Conexões (
GameServer._accept_connections): No servidor, uma thread separada é responsável por aceitar novas conexões de clientes e iniciar um thread para lidar com a configuração inicial de cada um. Isso permite que o servidor continue operando enquanto aguarda novos jogadores. - Lógica Principal do Jogo (
GameController._run_game_logic_server): No lado do servidor, a orquestração das fases do jogo (proposta de equipe, votação, missão) ocorre em uma thread dedicada (self._game_logic_thread). Isso é crucial para que os timeouts e esperas por respostas dos jogadores não bloqueiem o thread principal da interface gráfica. - Conexão do Cliente (
GameClient._connect_client_loop): A tentativa de conexão do cliente ao servidor também é realizada em uma thread separada para evitar que a GUI congele durante o processo de estabelecimento da conexão. - Atualizações da GUI (
root.after): Embora a lógica e a rede rodem em threads separadas, todas as interações com a interface gráfica (widgetstkinter) são agendadas para serem executadas no thread principal da GUI usandoself.root.after(). Isso garante a segurança e consistência das operações de UI, prevenindo erros de concorrência.
Para gerenciar o acesso a recursos compartilhados e garantir a integridade dos dados em um ambiente multithreaded, o projeto emprega locks, semáforos e filas (queues) thread-safe.
threading.Lock(GameServer._lock): Utilizado emsrc/utils/network.pypara proteger o dicionárioself.clients, que armazena os sockets conectados. Isso impede condições de corrida quando múltiplos threads (ex: o thread que aceita conexões e o thread que envia mensagens) tentam modificar a lista de clientes simultaneamente, garantindo a integridade dos dados da conexão.threading.Lock(GameController._current_phase_lock): Emsrc/controllers/controller.py, este lock é fundamental para sincronizar o acesso e a modificação doGameModel. Ele garante que apenas uma operação (seja da thread de lógica do jogo ou de um manipulador de mensagens de rede) possa modificar o estado do jogo em um dado momento. Isso previne condições de corrida e garante a consistência lógica do jogo.threading.Semaphore(GameServer._client_setup_semaphore): Adicionado emsrc/utils/network.py, este semáforo é utilizado para limitar o número de threads que podem realizar a configuração inicial de novas conexões de clientes simultaneamente (definido por_max_concurrent_client_setup, por exemplo, 3). Cada thread que lida com uma nova conexão (_handle_new_client_connection) adquire uma permissão do semáforo antes de prosseguir com a configuração, bloqueando se o limite for atingido. Isso ajuda a prevenir a sobrecarga do servidor em caso de muitas conexões simultâneas.- Filas Thread-Safe (
queue.Queue):self.message_queue(emsrc/utils/network.py): Usada por ambas as classesGameServereGameClientpara armazenar mensagens de rede recebidas de forma thread-safe.self.team_selection_response_queue,self.vote_response_queues,self.sabotage_response_queues(emsrc/controllers/controller.py): Estas filas são usadas para coletar as respostas dos diálogos da GUI (que são executados no thread principal) e passá-las de volta para a thread de lógica do jogo. Elas garantem que as respostas sejam transmitidas de forma ordenada e thread-safe.
Para executar o jogo, siga os passos abaixo:
- Certifique-se de ter Python 3.12 instalado.
- Navegue até o diretório raiz do projeto no terminal.
-
Para iniciar o Servidor:
python -m src.mainSelecione a opção "Servidor (Host)" no menu principal. O servidor ficará ouvindo na porta
12345(definida emsrc/utils/settings.py). -
Para conectar como Cliente:
python -m src.mainSelecione a opção "Cliente (Entrar)" e digite o IP do servidor (Ex:
127.0.0.1para jogar na mesma máquina, ou o IP da máquina host na rede). O cliente tentará conectar na porta12345. - O jogo requer 5 jogadores (definido em
NUM_PLAYERSemsrc/utils/settings.py). Conecte os clientes necessários. - No servidor, o botão "Iniciar Jogo" ficará ativo quando
NUM_PLAYERSjogadores estiverem conectados.