Cómo diseñar una base de datos lo suficientemente flexible para acomodar varios juegos de cartas muy diferentes.
Recientemente, mostramos cómo se puede usar una base de datos para almacenar resultados de juegos de mesa. Los juegos de mesa son divertidos, pero no son la única versión en línea de los juegos clásicos. Los juegos de cartas también son muy populares. Introducen un elemento de suerte en el juego, ¡y hay mucho más que suerte en un buen juego de cartas!
En este artículo, nos centraremos en crear un modelo de datos para almacenar partidos, resultados, jugadores y puntajes. El principal desafío aquí es almacenar datos relacionados con muchos juegos de cartas diferentes. También podríamos considerar analizar estos datos para determinar estrategias ganadoras, mejorar nuestras propias habilidades de juego o construir un mejor oponente de IA.
Los cuatro juegos de cartas que usaremos en nuestra base de datos
Debido a que los jugadores no pueden controlar la mano que reciben, los juegos de cartas combinan estrategia, habilidad y suerte. Ese factor suerte le da a un principiante la oportunidad de vencer a un jugador experimentado y hace que los juegos de cartas sean adictivos. (Esto difiere de juegos como el ajedrez, que se basan en gran medida en la lógica y la estrategia. He escuchado de muchos jugadores que no están interesados en jugar al ajedrez porque no pueden encontrar oponentes en su nivel de habilidad).
Nos centraremos en cuatro juegos de cartas muy conocidos:póquer, blackjack, belot (o belote) y préférence. Cada uno de ellos tiene reglas relativamente complejas y requiere algún tiempo para dominar. La relación entre suerte y conocimiento también es diferente para cada juego.
Echaremos un vistazo rápido a las reglas simplificadas y los detalles de los cuatro juegos a continuación. Las descripciones de los juegos son bastante escasas, pero hemos incluido suficientes para mostrar los diferentes modos de juego y las diversas reglas que encontraremos durante el proceso de diseño de la base de datos.
Veintiuna:
- Mazo: De uno a ocho mazos de 52 cartas cada uno; sin cartas de comodín
- Jugadores: Repartidor y 1 o más oponentes
- Unidad utilizada: Generalmente dinero
- Reglas básicas: Los jugadores obtienen 2 cartas que solo ellos pueden ver; el crupier recibe dos cartas, una boca arriba y la otra boca abajo; cada jugador decide sacar más cartas (o no); el crupier saca el último. Las tarjetas tienen valores de puntos asignados que van del 1 al 11.
- Posibles acciones del jugador: Golpear, Plantarse, Dividir, Rendirse
- Objetivo y condición de victoria: La suma de las cartas de un jugador es mayor que la del crupier; si algún jugador pasa de 21, ese jugador pierde.
Póker (Texas Hold'Em):
- Mazo: Mazo estándar (también conocido como palo francés) de 52 cartas; sin cartas de comodín. Las tarjetas suelen ser de color rojo y negro.
- Jugadores: de dos a nueve; los jugadores se turnan para repartir
- Unidad utilizada:generalmente chips
- Reglas básicas: Cada jugador comienza recibiendo dos cartas; los jugadores hacen sus apuestas; se reparten tres cartas boca arriba en el centro de la mesa; los jugadores vuelven a hacer sus apuestas; se coloca una cuarta carta en el medio y los jugadores vuelven a apostar; luego se coloca la quinta y última carta y se completa la última ronda de apuestas.
- Posibles acciones del jugador: Retirarse, Igualar, Subir, Ciega pequeña, Ciega grande, Resubir
- Objetivo: Combina la mejor mano posible de cinco cartas (de las dos cartas en la mano del jugador y las cinco cartas en el medio de la mesa)
- Condición de victoria:normalmente para ganar todas las fichas de la mesa
Belot (variante croata de Belote):
- Mazo: Por lo general, la baraja tradicional alemana o húngara de 32 cartas; sin cartas de comodín
- Jugadores: dos a cuatro; normalmente cuatro jugadores en parejas de dos
- Unidad utilizada: Puntos
- Reglas básicas: Para un juego de cuatro jugadores, cada jugador recibe seis cartas en la mano y dos cartas boca abajo; los jugadores ofertan primero por el palo de triunfo; después de determinar el triunfo, toman las dos cartas boca abajo y las colocan en su mano; sigue una ronda de declaración, durante la cual se anuncian ciertas combinaciones de cartas para obtener puntos adicionales; el juego continúa hasta que se hayan usado todas las cartas.
- Posibles acciones del jugador: Pase, palo de oferta, declaración, tarjeta de lanzamiento
- Gol de la mano: Ganar más de la mitad de los puntos
- Condición de victoria: Sé el primer equipo en anotar 1001 puntos o más
Preferencia:
- Mazo: La mayoría de las veces, una baraja tradicional alemana o húngara de 32 cartas; sin cartas de comodín
- Jugadores: tres
- Unidades: Puntos
- Reglas básicas: Todos los jugadores reciben 10 cartas; se colocan dos cartas de "gatito" o "garra" en el centro de la mesa; los jugadores determinan si quieren pujar por un palo; los jugadores deciden jugar o no.
- Posibles acciones del jugador: Pasar, Decir Palo, Jugar, No Jugar, Tirar Carta
- Objetivo: Depende de la variante de Préférence que se esté reproduciendo; en la versión estándar, el postor debe ganar un total de seis bazas.
- Condición de victoria: Cuando la suma de las puntuaciones de los tres jugadores es 0, gana el jugador con el menor número de puntos.
¿Por qué combinar bases de datos y juegos de cartas?
Nuestro objetivo aquí es diseñar un modelo de base de datos que pueda almacenar todos los datos relevantes para estos cuatro juegos de cartas. La base de datos podría ser utilizada por una aplicación web como un lugar para almacenar todos los datos relevantes. Queremos almacenar la configuración inicial del juego, los participantes del juego, las acciones realizadas durante el juego y el resultado de un solo trato, mano o baza. También debemos tener en cuenta el hecho de que un partido puede tener una o más ofertas asociadas.
A partir de lo que almacenamos en nuestra base de datos, deberíamos poder recrear todas las acciones que tuvieron lugar durante el juego. Usaremos campos de texto para describir las condiciones de victoria, las acciones del juego y sus resultados. Estos son específicos para cada juego y la lógica de la aplicación web interpretará el texto y lo transformará según sea necesario.
Una introducción rápida al modelo
Este modelo nos permite almacenar todos los datos relevantes del juego, incluidos:
- Propiedades del juego
- Lista de juegos y partidos
- Participantes
- Acciones en el juego
Dado que los juegos difieren en muchos aspectos, usaremos con frecuencia el varchar(256) tipo de datos para describir propiedades, movimientos y resultados.
Jugadores, Partidos y Participantes
Esta sección del modelo consta de tres tablas y se utiliza para almacenar datos sobre los jugadores registrados, los partidos jugados y los jugadores que participaron.
El player
tabla almacena datos sobre los jugadores registrados. El username
y email
Los atributos son valores únicos. El nick_name
El atributo almacena los nombres de pantalla de los jugadores.
El match
la tabla contiene todos los datos de coincidencia relevantes. Generalmente, una partida se compone de uno o más repartos de cartas (también conocidos como rondas, manos o bazas). Todos los partidos tienen reglas establecidas antes de que comience el juego. Los atributos son los siguientes:
game_id
– hace referencia a la tabla que contiene la lista de juegos (póker, blackjack, belot y préférence, en este caso).start_time
yend_time
son los tiempos reales cuando comienza y termina un partido. Observe que elend_time
puede ser NULL; no tendremos su valor hasta que termine el juego. Además, si se abandona una partida antes de que finalice, elend_time
el valor puede permanecer NULL.number_of_players
– es el número de participantes necesarios para iniciar el juegodeck_id
– hace referencia al mazo utilizado en el juego.decks_used
– es el número de barajas utilizadas para jugar el juego. Por lo general, este valor será 1, pero algunos juegos usan varios mazos.unit_id
– es la unidad (puntos, fichas, dinero, etc.) utilizada para puntuar el juego.entrance_fee
– es el número de unidades necesarias para unirse al juego; esto puede ser NULL si el juego no requiere que cada jugador comience con un número determinado de unidades.victory_conditions
– determina qué jugador ganó el partido. Usaremos el varchar tipo de datos para describir la condición de victoria de cada juego (es decir, el primer equipo en alcanzar los 100 puntos) y dejar que la aplicación lo interprete. Esta flexibilidad deja espacio para que se agreguen muchos juegos.match_result
– almacena el resultado del partido en formato de texto. Al igual que convictory_conditions
, dejaremos que la aplicación interprete el valor. Este atributo puede ser NULL porque llenaremos ese valor al mismo tiempo que insertamos elend_time
valor.
El participant
tabla almacena datos sobre todos los participantes en un partido. El match_id
y player_id
los atributos son referencias a la match
y player
mesas. Juntos, estos valores forman la clave alternativa de la tabla.
La mayoría de los juegos rotan qué jugador hace una oferta o juega primero. Por lo general, en la primera ronda, el jugador que juega primero (el jugador de apertura) está determinado por las reglas del juego. En la siguiente ronda, el jugador a la izquierda (oa veces a la derecha) del jugador inicial original irá primero. Usaremos el initial_player_order
atributo para almacenar el número ordinal del jugador de apertura de la primera ronda. El match_id
y el initial_player_order
los atributos forman otra clave alternativa porque dos jugadores no pueden jugar al mismo tiempo.
La score
El atributo se actualiza cuando un jugador termina un partido. A veces, esto será en el mismo momento para todos los jugadores (p. ej., en belot o préférence) y, a veces, mientras la partida aún está en curso (p. ej., póquer o blackjack).
Acciones y tipos de acciones
Cuando pensamos en las acciones que pueden realizar los jugadores en un juego de cartas, nos damos cuenta de que debemos almacenar:
- Cuál fue la acción
- Quién realizó esa acción
- Cuándo (en qué trato) tuvo lugar la acción
- Qué carta(s) se usaron en esa acción
El action_type
table es un diccionario simple que contiene los nombres de las acciones del jugador. Algunos valores posibles incluyen robar carta, jugar carta, pasar carta a otro jugador, pasar y subir.
En la action
tabla, almacenaremos todos los eventos que ocurrieron durante un trato. El deal_id
, card_id
, participant_id
y action_type_id
son referencias a las tablas que contienen valores de trato, participante de tarjeta y tipo de acción. Observe que el participant_id
y card_id
pueden ser valores NULL. Esto se debe al hecho de que algunas acciones no las realizan los jugadores (p. ej., el crupier saca una carta y la coloca boca arriba), mientras que otras no incluyen cartas (p. ej., una subida en el póquer). Necesitamos almacenar todas estas acciones para poder recrear todo el partido.
El action_order
El atributo almacena el número ordinal de una acción en el juego. Por ejemplo, una oferta de apertura recibiría un valor de 1; la siguiente oferta tendría un valor de 2, etc. No puede haber más de una acción al mismo tiempo. Por lo tanto, el deal_id
y action_order
los atributos juntos forman la clave alternativa.
La action_notation
El atributo contiene una descripción detallada de una acción. En el póquer, por ejemplo, podemos almacenar un aumento acción y una cantidad arbitraria. Algunas acciones pueden ser más complicadas, por lo que es aconsejable almacenar estos valores como texto y dejar que la aplicación los interprete.
Ofertas y orden de ofertas
Un partido se compone de uno o más repartos de cartas. Ya hemos discutido el participant
y la match
tablas, pero las hemos incluido en la imagen para mostrar su relación con el deal
y deal_order
mesas.
El deal
table almacena todos los datos que necesitamos sobre una única instancia de coincidencia.
El match_id
El atributo relaciona esa instancia con la coincidencia apropiada, mientras que start_time
y end_time
indicar la hora exacta en que se inició esa instancia y cuando finalizó.
El move_time_limit
y el deal_result
los atributos son campos de texto utilizados para almacenar límites de tiempo (si corresponde) y una descripción del resultado de ese trato.
En el participant
tabla, el initial_player_order
El atributo almacena el orden de los jugadores para la instancia del partido de apertura. Almacenar las órdenes para turnos posteriores requiere una tabla completamente nueva:la deal_order
mesa.
Obviamente, deal_id
y participant_id
son referencias a una instancia de coincidencia y un participante. Juntos, forman la primera clave alternativa en el deal_order
mesa. El player_order
El atributo contiene valores que indican los órdenes en los que los jugadores participaron en esa instancia de partido. Junto con deal_id
, forma la segunda clave alternativa en esta tabla. El deal_result
El atributo es un campo de texto que describe el resultado del partido para un jugador individual. La score
El atributo almacena un valor numérico relacionado con el resultado del trato.
Palos, Rangos y Cartas
Esta sección del modelo describe las tarjetas que usaremos en todos los juegos compatibles. Cada carta tiene un palo y un rango.
El suit_type
table es un diccionario que contiene todos los tipos de palos que usaremos. Para suit_type_name
, usaremos valores como "palos franceses", "palos alemanes", "palos suizo-alemanes" y "palos latinos".
El suit
La tabla contiene los nombres de todos los palos contenidos en tipos de mazos específicos. Por ejemplo, la baraja francesa tiene palos llamados “Picas”, “Corazones”, “Diamantes” y “Tréboles”.
En el rank
diccionario, encontraremos valores de cartas conocidos como “As”, “King”, “Queen” y “Jota”.
La card
La tabla contiene una lista de todas las cartas posibles. Cada carta aparecerá en esta tabla solo una vez. Esa es la razón por la que suit_id
y rank_id
los atributos forman la clave alternativa de esta tabla. Los valores de ambos atributos pueden ser NULOS porque algunas cartas no tienen un palo o un rango (por ejemplo, cartas de comodín). La is_joker_card
es un valor booleano que se explica por sí mismo. El card_name
El atributo describe una carta con el texto:"As of Spades".
Cartas y Barajas
Las cartas pertenecen a los mazos. Debido a que una carta puede aparecer en varios mazos, necesitaremos un n:n relación entre la card
y deck
mesas.
En el deck
tabla, almacenaremos los nombres de todos los mazos de cartas que queremos usar. Un ejemplo de valores almacenados en el deck_name
Los atributos son:"Mazo estándar de 52 cartas (francés)" o "Mazo de 32 cartas (alemán)".
La card_in_deck
La relación se utiliza para asignar cartas a los mazos apropiados. El card_id
– deck_id
par es la clave alternativa del deck
mesa.
Propiedades de coincidencia, mazos y unidades utilizadas
Esta sección del modelo contiene algunos parámetros básicos para comenzar un nuevo juego.
La parte principal de esta sección es el game
mesa. Esta tabla almacena datos sobre juegos compatibles con aplicaciones. El game_name
El atributo contiene valores como "poker", "blackjack", "belot" y "préférence".
El min_number_of_players
y max_number_of_players
son el número mínimo y máximo de participantes en un partido. Estos atributos sirven como límites para el juego y se muestran en la pantalla al comienzo de un partido. La persona que inicia la coincidencia debe seleccionar un valor de este rango.
La min_entrance_fee
y la max_entrance_fee
atributos denota el rango de tarifa de entrada. Nuevamente, esto se basa en el juego que se está jugando.
En possible_victory_condition
, almacenaremos todas las condiciones de victoria que podrían asignarse a un partido. Los valores están separados por un delimitador.
La unit
El diccionario se utiliza para almacenar cada unidad utilizada en todos nuestros juegos. El unit_name
El atributo albergará valores como "punto", "dólar", "euro" y "chip".
El game_deck
y game_unit
las tablas usan la misma lógica. Contienen listas de todos los mazos y unidades que se pueden usar en una partida. Por lo tanto, el game_id
– deck_id
par y el game_id
– unit_id
par formar claves alternativas en sus respectivas tablas.
Puntuaciones
En nuestra aplicación, querremos almacenar las puntuaciones de todos los jugadores que participaron en nuestros juegos de cartas. Para cada juego, se calcula y almacena un único valor numérico. (El cálculo se basa en los resultados del jugador en todos los juegos de un solo tipo). La puntuación de este jugador es similar a un rango; permite a los usuarios saber aproximadamente qué tan bueno es un jugador.
Volvamos al proceso de cálculo. Crearemos un n:n relación entre el player
y game
mesas. Ese es el player_score
mesa en nuestro modelo. El player_id
y el score_id
” juntos forman la clave alternativa de la tabla. La “score
El atributo se utiliza para almacenar el valor numérico mencionado anteriormente.
Hay variedad de juegos de cartas que utilizan reglas, cartas y barajas muy diferentes. Para crear una base de datos que almacene datos para más de un juego de cartas, necesitamos hacer algunas generalizaciones. Una forma de hacer esto es usando campos de texto descriptivos y dejando que la aplicación los interprete. Podríamos encontrar formas de cubrir las situaciones más comunes, pero eso complicaría exponencialmente el diseño de la base de datos.
Como se muestra en este artículo, puede usar una base de datos para muchos juegos. ¿Por qué harías esto? Tres razones:1) puede reutilizar la misma base de datos; 2) simplificaría el análisis; y esto conduciría a 3) la construcción de mejores oponentes de IA.