Como projetar um banco de dados flexível o suficiente para acomodar vários jogos de cartas muito diferentes.
Recentemente, mostramos como um banco de dados pode ser usado para armazenar resultados de jogos de tabuleiro. Os jogos de tabuleiro são divertidos, mas não são a única versão online dos jogos clássicos. Os jogos de cartas também são muito populares. Eles introduzem um elemento de sorte na jogabilidade, e há muito mais do que sorte envolvido em um bom jogo de cartas!
Neste artigo, vamos nos concentrar na construção de um modelo de dados para armazenar partidas de jogos, resultados, jogadores e pontuações. O principal desafio aqui é armazenar dados relacionados a muitos jogos de cartas diferentes. Também poderíamos considerar a análise desses dados para determinar estratégias vencedoras, melhorar nossas próprias habilidades de jogo ou construir um oponente de IA melhor.
Os quatro jogos de cartas que usaremos em nosso banco de dados
Como os jogadores não podem controlar a mão que recebem, os jogos de cartas combinam estratégia, habilidade e sorte. Esse fator de sorte dá ao iniciante a chance de vencer um jogador experiente e torna os jogos de cartas viciantes. (Isso difere de jogos como xadrez, que dependem muito de lógica e estratégia. Ouvi de muitos jogadores que eles não estão interessados em jogar xadrez porque não conseguem encontrar oponentes em seu nível de habilidade.)
Vamos nos concentrar em quatro jogos de cartas bem conhecidos:poker, blackjack, belot (ou belote) e preferência. Cada um deles tem regras relativamente complexas e requer algum tempo para dominar. A proporção de sorte x conhecimento também é diferente para cada jogo.
Vamos dar uma olhada rápida nas regras e especificidades simplificadas para todos os quatro jogos abaixo. As descrições dos jogos são bastante escassas, mas incluímos o suficiente para mostrar os diferentes modos de jogo e as diversas regras que encontraremos durante o processo de design do banco de dados.
Vinte-e-um:
- Baralho: Um a oito baralhos de 52 cartas cada; sem cartas de palhaço
- Jogadores: Dealer e 1 ou mais oponentes
- Unidade usada: Geralmente dinheiro
- Regras básicas: Os jogadores recebem 2 cartas que só eles podem ver; o dealer recebe duas cartas, uma virada para cima e outra virada para baixo; cada jogador decide comprar mais cartas (ou não); o dealer empata por último. Os cartões têm valores de pontos atribuídos que variam de 1 a 11.
- Possíveis ações do jogador: Bater, ficar de pé, dividir, render-se
- Condição de meta e vitória: A soma das cartas de um jogador é maior que a do dealer; se algum jogador ultrapassar 21, esse jogador perde.
Poker (Texas Hold'Em):
- Baralho: Baralho de 52 cartas padrão (também conhecido como naipe francês); sem cartas de coringa. Os cartões geralmente são de cor vermelha e preta.
- Jogadores: Dois a nove; os jogadores se revezam na distribuição
- Unidade usada:geralmente chips
- Regras básicas: Cada jogador começa por receber duas cartas; os jogadores fazem suas apostas; três cartas são distribuídas viradas para cima no meio da mesa; os jogadores voltam a fazer as suas apostas; uma quarta carta é colocada no meio e os jogadores apostam novamente; então a quinta e última carta é colocada e a última rodada de apostas é concluída.
- Possíveis ações do jogador: Fold, Call, Raise, Small Blind, Big Blind, Reraise
- Meta: Combine a melhor mão possível de cinco cartas (das duas cartas na mão do jogador e as cinco cartas no meio da mesa)
- Condição de vitória:geralmente para ganhar todas as fichas da mesa
Belot (variante croata de Belote):
- Baralho: Geralmente o tradicional baralho de 32 cartas alemão ou húngaro; sem cartas de palhaço
- Jogadores: Dois a quatro; geralmente quatro jogadores em pares de dois
- Unidade usada: Pontos
- Regras básicas: Para um jogo de quatro jogadores, cada jogador recebe seis cartas na mão e duas cartas viradas para baixo; os jogadores primeiro lancem para o naipe de trunfo; depois que o trunfo é determinado, eles pegam as duas cartas viradas para baixo e as colocam em sua mão; segue-se uma rodada de declaração, durante a qual certas combinações de cartas são anunciadas para pontos adicionais; o jogo continua até que todas as cartas tenham sido usadas.
- Possíveis ações do jogador: Passe, Terno de Licitação, Declaração, Cartão de Lançamento
- Gol para a mão: Para ganhar mais da metade dos pontos
- Condição de vitória: Seja a primeira equipe a marcar 1.001 pontos ou mais
Preferência:
- Baralho: Na maioria das vezes, um baralho de 32 cartas tradicional alemão ou húngaro; sem cartas de palhaço
- Jogadores: Três
- Unidades: Pontos
- Regras básicas: Todos os jogadores recebem 10 cartas; duas cartas “gatinho” ou “garra” são colocadas no meio da mesa; os jogadores determinam se querem apostar em um naipe; os jogadores decidem jogar ou não.
- Possíveis ações do jogador: Passe, lance naipe, jogue, não jogue, jogue cartão
- Meta: Depende da variante de Preférence que está sendo tocada; na versão padrão, o licitante deve ganhar um total de seis vazas.
- Condição de vitória: Quando a soma das pontuações dos três jogadores for 0, o jogador com o menor número de pontos vence.
Por que combinar bancos de dados e jogos de cartas?
Nosso objetivo aqui é projetar um modelo de banco de dados que possa armazenar todos os dados relevantes para esses quatro jogos de cartas. O banco de dados pode ser usado por um aplicativo da Web como um local para armazenar todos os dados relevantes. Queremos armazenar as configurações iniciais do jogo, os participantes do jogo, as ações realizadas durante o jogo e o resultado de um único acordo, mão ou vaza. Também devemos ter em mente o fato de que uma partida pode ter um ou mais negócios associados a ela.
A partir do que armazenamos em nosso banco de dados, devemos ser capazes de recriar todas as ações que ocorreram durante o jogo. Usaremos campos de texto para descrever as condições de vitória, ações do jogo e seus resultados. Estes são específicos para cada jogo e a lógica da aplicação web irá interpretar o texto e transformá-lo conforme necessário.
Uma rápida introdução ao modelo
Este modelo nos permite armazenar todos os dados relevantes do jogo, incluindo:
- Propriedades do jogo
- Lista de jogos e partidas
- Participantes
- Ações no jogo
Como os jogos diferem de várias maneiras, usaremos frequentemente o varchar(256) tipo de dados para descrever propriedades, movimentos e resultados.
Jogadores, Partidas e Participantes
Esta seção do modelo consiste em três tabelas e é usada para armazenar dados sobre os jogadores registrados, as partidas disputadas e os jogadores que participaram.
O player
tabela armazena dados sobre jogadores registrados. O username
e email
atributos são valores únicos. O nick_name
atributo armazena os nomes de tela dos jogadores.
A match
tabela contém todos os dados de correspondência relevantes. Geralmente, uma partida é composta por uma ou mais cartas (também conhecidas como rodadas, mãos ou vazas). Todas as partidas têm regras definidas antes do início do jogo. Os atributos são os seguintes:
game_id
– referencia a tabela que contém a lista de jogos (poker, blackjack, belot e preferência, neste caso).start_time
eend_time
são os horários reais em que uma partida começa e termina. Observe que oend_time
pode ser NULO; não teremos seu valor até que o jogo termine. Além disso, se uma partida for abandonada antes de terminar, oend_time
valor pode permanecer NULL.number_of_players
– é o número de participantes necessários para iniciar o jogodeck_id
– faz referência ao baralho usado no jogo.decks_used
– é o número de baralhos usados para jogar o jogo. Normalmente, esse valor será 1, mas alguns jogos usam vários baralhos.unit_id
– é a unidade (pontos, fichas, dinheiro, etc.) usada para pontuar o jogo.entrance_fee
– é o número de unidades necessárias para entrar no jogo; isso pode ser NULL se o jogo não exigir que cada jogador comece com um número definido de unidades.victory_conditions
– determina qual jogador venceu a partida. Usaremos o varchar tipo de dados para descrever a condição de vitória de cada jogo (ou seja, primeiro time a atingir 100 pontos) e deixar o aplicativo para interpretá-lo. Essa flexibilidade deixa espaço para muitos jogos serem adicionados.match_result
– armazena o resultado da partida em formato de texto. Assim como emvictory_conditions
, deixaremos o aplicativo interpretar o valor. Este atributo pode ser NULL porque preencheremos esse valor ao mesmo tempo em que inserimos oend_time
valor.
O
participant
A tabela armazena dados sobre todos os participantes de uma partida. O match_id
e player_id
atributos são referências à match
e player
mesas. Juntos, esses valores formam a chave alternativa da tabela. A maioria dos jogos alterna qual jogador dá lances ou joga primeiro. Normalmente na primeira rodada, o jogador que joga primeiro (o jogador de abertura) é determinado pelas regras do jogo. Na próxima rodada, o jogador à esquerda (ou às vezes à direita) do jogador de abertura original será o primeiro. Usaremos o
initial_player_order
atributo para armazenar o número ordinal do jogador de abertura da primeira rodada. O match_id
e o initial_player_order
atributos formam outra chave alternativa porque dois jogadores não podem jogar ao mesmo tempo. A
score
atributo é atualizado quando um jogador termina uma partida. Às vezes, isso será no mesmo momento para todos os jogadores (por exemplo, em belot ou preferência) e, às vezes, enquanto a partida ainda estiver em andamento (por exemplo, pôquer ou blackjack). Ações e tipos de ação
Quando pensamos nas ações que os jogadores podem fazer em um jogo de cartas, percebemos que devemos armazenar:
- Qual foi a ação
- Quem executou essa ação
- Quando (em qual negócio) a ação ocorreu
- Quais cartas foram usadas nessa ação
O
action_type
table é um dicionário simples que contém os nomes das ações do jogador. Alguns valores possíveis incluem comprar carta, jogar carta, passar carta para outro jogador, check e raise. Na
action
tabela, armazenaremos todos os eventos que aconteceram durante um negócio. O deal_id
, card_id
, participant_id
e action_type_id
são referências às tabelas que contêm os valores de negócio, participante do cartão e tipo_de_ação. Observe que o participant_id
e card_id
podem ser valores NULL. Isso se deve ao fato de que algumas ações não são feitas pelos jogadores (por exemplo, o dealer compra uma carta e a coloca virada para cima), enquanto algumas não incluem cartas (por exemplo, um aumento no pôquer). Precisamos armazenar todas essas ações para poder recriar a partida inteira. O
action_order
O atributo armazena o número ordinal de uma ação no jogo. Por exemplo, um lance de abertura receberia o valor 1; o próximo lance teria um valor 2 etc. Não pode haver mais de uma ação acontecendo ao mesmo tempo. Portanto, o deal_id
e action_order
atributos juntos formam a chave alternativa. A
action_notation
O atributo contém uma descrição detalhada de uma ação. No poker, por exemplo, podemos armazenar um raise ação e uma quantia arbitrária. Algumas ações podem ser mais complicadas, por isso é aconselhável armazenar esses valores como texto e deixar que o aplicativo os interprete. Ofertas e Ordem de Negociação
Uma partida é composta por uma ou mais ofertas de cartas. Já discutimos o participant
e a match
tabelas, mas nós as incluímos na imagem para mostrar sua relação com o deal
e deal_order
mesas.
O deal
table armazena todos os dados que precisamos sobre uma única instância de correspondência.
O match_id
O atributo relaciona essa instância à correspondência apropriada, enquanto start_time
e end_time
denotam a hora exata em que essa instância foi iniciada e quando foi concluída.
O move_time_limit
e o deal_result
atributos são campos de texto usados para armazenar limites de tempo (se aplicável) e uma descrição do resultado desse negócio.
No participant
tabela, o initial_player_order
O atributo armazena a ordem do jogador para a instância da partida de abertura. Armazenar as ordens para os turnos subsequentes requer uma tabela totalmente nova – a deal_order
tabela.
Obviamente, deal_id
e participant_id
são referências a uma instância de correspondência e a um participante. Juntos, eles formam a primeira chave alternativa no deal_order
tabela. O player_order
O atributo contém valores que denotam as ordens que os jogadores participaram naquela instância da partida. Junto com deal_id
, ela forma a segunda chave alternativa nesta tabela. O deal_result
atributo é um campo de texto que descreve o resultado da partida para um jogador individual. A score
O atributo armazena um valor numérico relacionado ao resultado do negócio.
Naipes, Ranks e Cartas
Esta seção do modelo descreve os cartões que usaremos em todos os jogos suportados. Cada carta tem um naipe e um valor.
O suit_type
table é um dicionário que contém todos os tipos de naipes que usaremos. Para suit_type_name
, usaremos valores como "trajes franceses", "trajes alemães", "trajes suíço-alemães" e "trajes latinos".
O suit
table contém os nomes de todos os naipes contidos em tipos de baralhos específicos. Por exemplo, o baralho francês tem naipes chamados “Espadas”, “Corações”, “Diamantes” e “Paus”.
Na rank
dicionário, encontraremos valores de cartas bem conhecidos como “Ás”, “Rei”, “Rainha” e “Valete”.
O card
tabela contém uma lista de todas as cartas possíveis. Cada carta aparecerá nesta tabela apenas uma vez. Essa é a razão pela qual o suit_id
e rank_id
atributos formam a chave alternativa desta tabela. Os valores de ambos os atributos podem ser NULL porque algumas cartas não têm naipe ou valor (por exemplo, cartas curinga). O is_joker_card
é um valor booleano autoexplicativo. O card_name
O atributo descreve uma carta pelo texto:“Ace of Spades”.
Cartas e Baralhos
As cartas pertencem aos baralhos. Como uma carta pode aparecer em vários baralhos, precisaremos de um n:n relação entre o card
e deck
mesas.
Na deck
tabela, armazenaremos os nomes de todos os baralhos de cartas que queremos usar. Um exemplo de valores armazenados no deck_name
atributos são:“Baralho padrão de 52 cartas (francês)” ou “baralho de 32 cartas (alemão)”.
O card_in_deck
A relação é usada para atribuir cartas aos baralhos apropriados. O card_id
– deck_id
par é a chave alternativa do deck
tabela.
Correspondência de propriedades, baralhos e unidades usadas
Esta seção do modelo contém alguns parâmetros básicos para iniciar um novo jogo.
A parte principal desta seção é o game
tabela. Esta tabela armazena dados sobre jogos suportados por aplicativos. O game_name
O atributo contém valores como “poker”, “blackjack”, “belot” e “préférence”.
Os min_number_of_players
e max_number_of_players
são o número mínimo e máximo de participantes em uma partida. Esses atributos servem como limites para o jogo e são mostrados na tela no início de uma partida. A pessoa que inicia a correspondência deve selecionar um valor desse intervalo.
A min_entrance_fee
e a max_entrance_fee
atributos denota o intervalo da taxa de entrada. Novamente, isso é baseado no jogo que está sendo jogado.
Em possible_victory_condition
, armazenaremos todas as condições de vitória que podem ser atribuídas a uma partida. Os valores são separados por um delimitador.
A unit
dicionário é usado para armazenar todas as unidades usadas em todos os nossos jogos. O unit_name
O atributo vai abrigar valores como “ponto”, “dólar”, “euro” e “chip”.
O game_deck
e game_unit
tabelas usam a mesma lógica. Eles contêm listas de todos os baralhos e unidades que podem ser usados em uma partida. Portanto, o game_id
– deck_id
par e o game_id
– unit_id
par formam chaves alternativas em suas respectivas tabelas.
Pontuações
Em nosso aplicativo, queremos armazenar as pontuações de todos os jogadores que participaram de nossos jogos de cartas. Para cada jogo, um único valor numérico é calculado e armazenado. (O cálculo é baseado nos resultados do jogador em todos os jogos de um único tipo.) Essa pontuação do jogador é semelhante a uma classificação; ele permite que os usuários saibam aproximadamente o quão bom é um jogador.
De volta ao processo de cálculo. Criaremos um n:n relação entre o
player
e game
mesas. Esse é o player_score
tabela em nosso modelo. O player_id
e o score_id
” juntos formam a chave alternativa da tabela. A “score
O atributo é usado para armazenar o valor numérico mencionado anteriormente. Há uma variedade de jogos de cartas que usam regras, cartas e baralhos muito diferentes. Para criar um banco de dados que armazene dados para mais de um jogo de cartas, precisamos fazer algumas generalizações. Uma maneira de fazer isso é usando campos de texto descritivos e deixando o aplicativo interpretá-los. Poderíamos encontrar maneiras de cobrir as situações mais comuns, mas isso complicaria exponencialmente o design do banco de dados.
Como este artigo mostrou, você pode usar um banco de dados para muitos jogos. Por que você faria isso? Três razões:1) você pode reutilizar o mesmo banco de dados; 2) simplificaria a análise; e isso levaria a 3) a construção de melhores oponentes de IA.