Para me divertir, estou tentando escrever um dos jogos de tabuleiro favoritos do meu filho como um software. Eventualmente, espero construir uma IU do WPF sobre ele, mas agora estou construindo a máquina que modela os jogos e suas regras.
Enquanto faço isso, continuo vendo problemas que considero comuns a muitos jogos de tabuleiro e talvez outros já os tenham resolvido melhor do que eu.
(Observe que a IA para jogar o jogo e os padrões de alto desempenho não são interessantes para mim.)
Até agora, meus padrões são:
Vários tipos imutáveis que representam entidades na caixa do jogo, por exemplo, dados, damas, cartas, um tabuleiro, espaços no tabuleiro, dinheiro, etc.
Um objeto para cada jogador, que contém os recursos dos jogadores (por exemplo, dinheiro, pontuação), seu nome, etc.
Um objeto que representa o estado do jogo: os jogadores, quem é a vez, o layout das peças no tabuleiro, etc.
Uma máquina de estado que gerencia a sequência de curvas. Por exemplo, muitos jogos têm um pequeno pré-jogo onde cada jogador rola para ver quem vai primeiro; esse é o estado inicial. Quando o turno de um jogador começa, primeiro eles rolam, depois se movem, então eles têm que dançar no lugar, então os outros jogadores adivinham que raça de frango eles são e então recebem pontos.
Existe alguma arte anterior da qual eu possa aproveitar?
EDIT: Uma coisa que percebi recentemente é que o estado do jogo pode ser dividido em duas categorias:
Estado do artefato do jogo . "Eu tenho $ 10" ou "minha mão esquerda está azul".
Estado da sequência do jogo . "Já rolei duplas duas vezes; a próxima me põe na prisão". Uma máquina de estado pode fazer sentido aqui.
EDIT: O que estou realmente procurando aqui é a melhor maneira de implementar jogos multiplayer baseados em turnos, como xadrez, Scrabble ou Banco Imobiliário. Tenho certeza de que poderia criar esse jogo apenas trabalhando nele do início ao fim, mas, como outros Design Patterns, provavelmente há algumas maneiras de fazer as coisas correrem muito mais suavemente que não são óbvias sem um estudo cuidadoso. É isso que estou esperando.
fonte
Respostas:
parece que este é um tópico de 2 meses que acabei de notar agora, mas que diabos. Eu projetei e desenvolvi a estrutura de jogo para um jogo de tabuleiro comercial em rede antes. Nós tivemos uma experiência muito agradável trabalhando com ele.
Seu jogo pode provavelmente estar em uma quantidade (perto de) infinita de estados por causa das permutações de coisas como quanto dinheiro o jogador A tem, quanto dinheiro o jogador B tem, e etc ... Portanto, tenho certeza que você deseja para ficar longe das máquinas de estado.
A ideia por trás do nosso framework era representar o estado do jogo como uma estrutura com todos os campos de dados que, juntos, fornecem o estado completo do jogo (ou seja: se você quiser salvar o jogo no disco, você escreve essa estrutura).
Usamos o Padrão de Comando para representar todas as ações válidas do jogo que um jogador poderia realizar. Aqui estaria um exemplo de ação:
Então você vê que para decidir se um movimento é válido, você pode construir essa ação e então chamar sua função IsLegal, passando no estado de jogo atual. Se for válido e o jogador confirmar a ação, você pode chamar a função Aplicar para modificar o estado do jogo. Ao garantir que seu código de jogo só pode modificar o estado do jogo criando e enviando ações legais (em outras palavras, a família de métodos Action :: Apply é a única coisa que modifica diretamente o estado do jogo), então você garante que seu jogo estado nunca será inválido. Além disso, usando o padrão de comando, você torna possível serializar os movimentos desejados de seu jogador e enviá-los por uma rede para serem executados nos estados de jogo de outro jogador.
Acabou sendo um problema com esse sistema que acabou sendo uma solução bastante elegante. Às vezes, as ações teriam duas ou mais fases. Por exemplo, o jogador pode pousar em uma propriedade no Monopólio e agora deve tomar uma nova decisão. Qual é o estado do jogo entre o momento em que o jogador lança os dados e o momento em que decide comprar ou não uma propriedade? Gerenciamos situações como essa apresentando um membro do "Contexto de Ação" do nosso estado de jogo. O contexto de ação normalmente seria nulo, indicando que o jogo não está em nenhum estado especial. Quando o jogador lança os dados e a ação de lançamento dos dados é aplicada ao estado do jogo, ele perceberá que o jogador pousou em uma propriedade não pertencente a ele e pode criar um novo "PlayerDecideToPurchaseProperty" contexto de ação que contém o índice do jogador do qual estamos aguardando uma decisão. No momento em que a ação RollDice é concluída, nosso estado de jogo representa que ele está aguardando que o jogador especificado decida se deseja comprar uma propriedade, não. Agora é fácil para o método IsLegal de todas as outras ações retornar falso, exceto para as ações "BuyProperty" e "PassPropertyPurchaseOpportunity", que só são legais quando o estado do jogo tem o contexto de ação "PlayerDecideToPurchaseProperty".
Por meio do uso de contextos de ação, nunca há um único ponto na vida do jogo de tabuleiro em que a estrutura do estado do jogo não represente EXATAMENTE o que está acontecendo no jogo naquele momento. Esta é uma propriedade muito desejável em seu sistema de jogo de tabuleiro. Será muito mais fácil para você escrever código quando puder encontrar tudo o que deseja saber sobre o que está acontecendo no jogo examinando apenas uma estrutura.
Além disso, ele se estende muito bem a ambientes de rede, onde os clientes podem enviar suas ações através de uma rede para uma máquina host, que pode aplicar a ação ao estado de jogo "oficial" do host e, em seguida, ecoar essa ação de volta para todos os outros clientes para peça-lhes que o apliquem aos seus estados de jogo replicados.
Espero que tenha sido conciso e útil.
fonte
A estrutura básica do seu motor de jogo usa o padrão de estado . Os itens de sua caixa de jogo são singletons de várias classes. A estrutura de cada estado pode usar o Strategy Pattern ou o Template Method .
Uma fábrica é usada para criar os jogadores que são inseridos em uma lista de jogadores, outro singleton. A GUI manterá a vigilância no Game Engine usando o padrão Observer e interagirá com ele usando um dos vários objetos Command criados usando o Command Pattern . O uso de Observador e Comando pode ser usado no contexto de uma Visualização Passiva, mas praticamente qualquer padrão MVP / MVC pode ser usado, dependendo de suas preferências. Quando você salva o jogo você precisa pegar uma lembrança de seu estado atual
Eu recomendo examinar alguns dos padrões neste site e ver se algum deles te pega como ponto de partida. Mais uma vez, o coração do seu tabuleiro de jogo será uma máquina de estados. A maioria dos jogos será representada por dois estados pré-jogo / configuração e o jogo real. Mas você pode ter mais estados se o jogo que está modelando tiver vários modos distintos de jogo. Os estados não precisam ser sequenciais, por exemplo, o jogo de guerra Axis & Battles tem um tabuleiro de batalha que os jogadores podem usar para resolver as batalhas. Portanto, há três estados pré-jogo, tabuleiro principal e tabuleiro de batalha, com o jogo alternando continuamente entre o tabuleiro principal e o tabuleiro de batalha. É claro que a sequência de curvas também pode ser representada por uma máquina de estados.
fonte
Acabei de projetar e implementar um jogo baseado em estado usando polimorfismo.
Usando uma classe base abstrata chamada
GamePhase
que tem um método importanteIsso significa que cada
GamePhase
objeto mantém o estado atual do jogo, e uma chamada para verificaturn()
seu estado atual e retorna o próximoGamePhase
.Cada concreto
GamePhase
possui construtores que mantêm todo o estado do jogo. Cadaturn()
método contém um pouco das regras do jogo. Enquanto isso espalha as regras, mantém as regras relacionadas próximas. O resultado final de cada umturn()
é apenas criar o próximoGamePhase
e passar no estado completo para a próxima fase.Isso permite
turn()
ser muito flexível. Dependendo do seu jogo, um determinado estado pode se ramificar para muitos tipos diferentes de fases. Isso forma um gráfico de todas as fases do jogo.No nível mais alto, o código para conduzi-lo é muito simples:
Isso é extremamente útil, pois agora posso criar facilmente qualquer estado / fase do jogo para teste
Agora, para responder à segunda parte da sua pergunta, como isso funciona no modo multijogador? Dentro de certos
GamePhase
s que requerem entrada do usuário, uma chamada deturn()
perguntaria à correntePlayer
seuStrategy
estado / fase atual.Strategy
é apenas uma interface de todas as decisões possíveis que vocêPlayer
pode tomar. Esta configuração também permiteStrategy
ser implementada com AI!Andrew Top também disse:
Acho que essa afirmação é muito enganosa, embora seja verdade que existem muitos estados de jogo diferentes, existem apenas algumas fases do jogo. Para lidar com seu exemplo, tudo que seria um parâmetro inteiro para os construtores de meus
GamePhase
s concretos .Monopólio
Exemplo de alguns
GamePhase
s seria:E alguns estados da base
GamePhase
são:E então algumas fases registrariam seu próprio estado conforme necessário, por exemplo PlayerRolls registraria o número de vezes que um jogador jogou duplas consecutivas. Assim que deixarmos a fase PlayerRolls, não nos importamos mais com lançamentos consecutivos.
Muitas fases podem ser reutilizadas e vinculadas. Por exemplo, o
GamePhase
CommunityChestAdvanceToGo
criaria a próxima fasePlayerLandsOnGo
com o estado atual e o retornaria. No construtor doPlayerLandsOnGo
jogador atual seria movido para Go e seu dinheiro seria incrementado em $ 200.fonte
Claro que existem muitos, muitos, muitos, muitos, muitos, muitos, muitos recursos sobre este tópico. Mas acho que você está no caminho certo dividindo os objetos e deixando que eles tratem de seus próprios eventos / dados e assim por diante.
Ao fazer jogos de tabuleiro baseados em blocos, você achará bom ter rotinas para mapear entre a matriz do tabuleiro e a linha / coluna e atrás, junto com outros recursos. Lembro-me do meu primeiro jogo de tabuleiro (muito tempo atrás) quando me esforcei para obter uma linha / cor do boardarray 5.
Nostalgia. ;)
De qualquer forma, http://www.gamedev.net/ é um bom lugar para informações. http://www.gamedev.net/reference/
fonte
Muitos dos materiais que posso encontrar online são listas de referências publicadas. A seção de publicações do Game Design Patterns possui links para versões em PDF dos artigos e teses. Muitos deles parecem trabalhos acadêmicos, como Design Patterns for Games . Há também pelo menos um livro disponível na Amazon, Patterns in Game Design .
fonte
Three Rings oferece bibliotecas Java LGPL. Nenya e Vilya são as bibliotecas para coisas relacionadas a jogos.
Claro, ajudaria se sua pergunta mencionasse restrições de plataforma e / ou idioma que você possa ter.
fonte
Eu concordo com a resposta de Pyrolistic e prefiro sua maneira de fazer as coisas (no entanto, apenas dei uma olhada nas outras respostas).
Coincidentemente, também usei seu nome "GamePhase". Basicamente, o que eu faria no caso de um jogo de tabuleiro baseado em turnos é que sua classe GameState contivesse um objeto da GamePhase abstrata, conforme mencionado por Pyrolistics.
Digamos que os estados do jogo sejam:
Você poderia ter classes derivadas concretas para cada estado. Tenha funções virtuais pelo menos para:
Na função StartPhase () você pode definir todos os valores iniciais para um estado, por exemplo, desabilitando a entrada do outro jogador e assim por diante.
Quando roll.EndPhase () é chamado, certifique-se de que o ponteiro GamePhase seja definido para o próximo estado.
Neste MovePhase :: StartPhase () você, por exemplo, definiria os movimentos restantes do jogador ativo para a quantidade rolada na fase anterior.
Agora, com esse design implementado, você pode resolver seu problema "3 x double = jail" dentro da fase Roll. A classe RollPhase pode controlar seu próprio estado. Por exemplo
Eu difiro do Pirolístico porque deve haver uma fase para tudo, incluindo quando o jogador cai no baú da Comunidade ou algo assim. Eu lidaria com tudo isso no MovePhase. Isso ocorre porque se você tiver muitas fases sequenciais, o jogador provavelmente se sentirá muito "guiado". Por exemplo, se houver uma fase em que o jogador pode SOMENTE comprar propriedades e depois SOMENTE comprar hotéis e SÓ comprar casas, é como se não houvesse liberdade. Basta colocar todas essas partes em uma fase de compra e dar ao jogador a liberdade de comprar o que quiser. A classe BuyPhase pode controlar facilmente quais compras são legais.
Finalmente, vamos abordar o tabuleiro do jogo. Embora um array 2D seja bom, eu recomendo ter um gráfico de ladrilhos (onde um ladrilho é uma posição no tabuleiro). No caso de monopólia, seria preferível uma lista duplamente ligada. Então, cada bloco teria um:
Portanto, seria muito mais fácil fazer algo como:
A função AdvanceTo pode controlar suas animações passo a passo ou o que você quiser. E também diminui os movimentos restantes, é claro.
O conselho de RS Conley sobre o padrão do observador para a GUI é bom.
Eu não postei muito antes. Espero que isso ajude alguém.
fonte
Se sua pergunta não for específica ao idioma ou à plataforma. então eu recomendo que você considere os padrões AOP para estado, memento, comando, etc.
Qual é a resposta .NET para AOP ??
Tente também encontrar alguns sites interessantes, como http://www.chessbin.com
fonte