Hoe een database te ontwerpen die flexibel genoeg is voor verschillende zeer verschillende kaartspellen.
Onlangs hebben we laten zien hoe een database kan worden gebruikt om bordspelresultaten op te slaan. Bordspellen zijn leuk, maar ze zijn niet de enige online versie van klassieke spellen. Kaartspellen zijn ook erg populair. Ze introduceren een element van geluk in het spel, en er komt veel meer bij kijken dan geluk bij een goed kaartspel!
In dit artikel zullen we ons concentreren op het bouwen van een datamodel om wedstrijdwedstrijden, resultaten, spelers en scores op te slaan. De grootste uitdaging hier is het opslaan van gegevens met betrekking tot veel verschillende kaartspellen. We kunnen ook overwegen om deze gegevens te analyseren om winnende strategieën te bepalen, onze eigen speelvaardigheden te verbeteren of een betere AI-tegenstander op te bouwen.
De vier kaartspellen die we in onze database zullen gebruiken
Omdat spelers geen controle hebben over de hand die ze krijgen, combineren kaartspellen strategie, vaardigheid en geluk. Die geluksfactor geeft een beginner de kans om een ervaren speler te verslaan, en het maakt kaartspellen verslavend. (Dit verschilt van spellen als schaken, die sterk afhankelijk zijn van logica en strategie. Ik heb van veel spelers gehoord dat ze niet geïnteresseerd zijn in schaken omdat ze geen tegenstanders op hun vaardigheidsniveau kunnen vinden.)
We concentreren ons op vier bekende kaartspellen:poker, blackjack, belot (of belote) en préférence. Elk van hen heeft relatief complexe regels en vereist enige tijd om onder de knie te krijgen. De verhouding tussen geluk en kennis is ook voor elk spel anders.
We zullen de vereenvoudigde regels en details voor alle vier de spellen hieronder kort bekijken. Spelbeschrijvingen zijn vrij schaars, maar we hebben genoeg toegevoegd om de verschillende spelmodi en de diverse regels te laten zien die we tegenkomen tijdens het ontwerpproces van de database.
Blackjack:
- Deck: Een tot acht decks van elk 52 kaarten; geen jokerkaarten
- Spelers: Dealer en 1 of meer tegenstanders
- Eenheid gebruikt: Meestal geld
- Basisregels: Spelers krijgen 2 kaarten die alleen zij kunnen zien; de dealer krijgt twee kaarten, een met de afbeelding naar boven en de andere met de afbeelding naar beneden; elke speler besluit om meer kaarten te trekken (of niet); de dealer trekt als laatste. Kaarten hebben een puntwaarde van 1 tot 11 toegewezen.
- Mogelijke acties van spelers: Slaan, staan, splitsen, overgeven
- Doelpunt en overwinningsvoorwaarde: De som van de kaarten van een speler is groter dan die van de dealer; als een speler over de 21 gaat, verliest die speler.
Poker (Texas Hold'Em):
- Deck: Standaard (ook bekend als Frans pak) 52-kaartendek; geen jokerkaarten. Kaarten zijn meestal rood en zwart van kleur.
- Spelers: Twee tot negen; spelers delen om de beurt
- Gebruikte eenheid:meestal chips
- Basisregels: Elke speler begint met het krijgen van twee kaarten; spelers plaatsen hun weddenschappen; drie kaarten worden open in het midden van de tafel gedeeld; spelers plaatsen opnieuw hun weddenschappen; een vierde kaart wordt in het midden geplaatst en spelers zetten opnieuw in; dan wordt de vijfde en laatste kaart geplaatst en is de laatste inzetronde voltooid.
- Mogelijke acties van spelers: Fold, Call, Raise, Small Blind, Big Blind, Reraise
- Doel: Combineer de best mogelijke hand van vijf kaarten (van de twee kaarten in de hand van de speler en de vijf kaarten in het midden van de tafel)
- Overwinningsvoorwaarde:meestal om alle fiches op tafel te winnen
Belot (Kroatische variant van Belote):
- Deck: Meestal het traditionele Duitse of Hongaarse kaartspel met 32 kaarten; geen jokerkaarten
- Spelers: Twee tot vier; meestal vier spelers in paren van twee
- Eenheid gebruikt: Punten
- Basisregels: Bij een spel met vier spelers krijgt elke speler zes kaarten in de hand en twee gesloten kaarten; spelers bieden eerst op troefkleur; nadat de troef is bepaald, nemen ze de twee gesloten kaarten en leggen ze in hun hand; er volgt een declaratieronde, waarin bepaalde kaartcombinaties worden aangekondigd voor extra punten; het spel gaat door totdat alle kaarten zijn gebruikt.
- Mogelijke acties van spelers: Pass, Biedkleur, Verklaring, Gooikaart
- Doelpunt voor de hand: Om meer dan de helft van de punten te winnen
- Overwinningsvoorwaarde: Wees het eerste team dat 1001 punten of meer scoort
Voorkeur:
- Deck: Meestal een traditioneel Duits of Hongaars kaartspel met 32 kaarten; geen jokerkaarten
- Spelers: Drie
- Eenheden: Punten
- Basisregels: Alle spelers krijgen 10 kaarten; twee "kitty"- of "talon" -kaarten worden in het midden van de tafel gelegd; spelers bepalen of ze op een kleur willen bieden; spelers beslissen om te spelen of niet.
- Mogelijke acties van spelers: Passen, Bieden, Spelen, Niet Spelen, Kaart gooien
- Doel: Hangt af van de variant van Préférence die wordt gespeeld; in de standaardversie moet de bieder in totaal zes slagen winnen.
- Overwinningsvoorwaarde: Wanneer de som van de scores van alle drie de spelers 0 is, wint de speler met het laagste aantal punten.
Waarom databases en kaartspellen combineren?
Ons doel hier is om een databasemodel te ontwerpen waarin alle relevante gegevens voor deze vier kaartspellen kunnen worden opgeslagen. De database zou door een webapplicatie kunnen worden gebruikt als een plek om alle relevante gegevens op te slaan. We willen de initiële spelinstellingen, speldeelnemers, acties die tijdens het spel worden ondernomen en de uitkomst van een enkele deal, hand of trick opslaan. We moeten ook rekening houden met het feit dat een match een of meer deals kan hebben.
Van wat we in onze database opslaan, zouden we in staat moeten zijn om alle acties die tijdens het spel hebben plaatsgevonden, opnieuw te creëren. We gebruiken tekstvelden om overwinningsvoorwaarden, spelacties en hun resultaten te beschrijven. Deze zijn specifiek voor elk spel en de logica van de webtoepassing interpreteert de tekst en transformeert ze indien nodig.
Een korte introductie tot het model
Dit model stelt ons in staat om alle relevante spelgegevens op te slaan, waaronder:
- Spel-eigenschappen
- Lijst met games en wedstrijden
- Deelnemers
- In-game acties
Omdat games op veel manieren verschillen, gebruiken we vaak de varchar(256) gegevenstype om eigenschappen, zetten en resultaten te beschrijven.
Spelers, wedstrijden en deelnemers
Dit gedeelte van het model bestaat uit drie tabellen en wordt gebruikt om gegevens op te slaan over geregistreerde spelers, de gespeelde wedstrijden en de spelers die hebben deelgenomen.
De player
tabel slaat gegevens op over geregistreerde spelers. De username
en email
attributen zijn unieke waarden. De nick_name
attribute slaat de schermnamen van spelers op.
De match
tabel bevat alle relevante wedstrijdgegevens. Over het algemeen bestaat een wedstrijd uit een of meer kaartdeals (ook bekend als rondes, handen of trucs). Alle wedstrijden hebben vaste regels voordat het spel begint. De attributen zijn als volgt:
game_id
– verwijst naar de tabel met de lijst met spellen (in dit geval poker, blackjack, belot en préférence).start_time
enend_time
zijn de werkelijke tijden waarop een wedstrijd begint en eindigt. Merk op dat deend_time
kan NULL zijn; we zullen zijn waarde pas hebben als het spel eindigt. Als een wedstrijd wordt afgebroken voordat deze is afgelopen, wordt deend_time
waarde kan NULL blijven.number_of_players
– is het aantal deelnemers dat nodig is om het spel te startendeck_id
- verwijst naar het kaartspel dat in het spel wordt gebruikt.decks_used
– is het aantal kaartspellen dat wordt gebruikt om het spel te spelen. Meestal is deze waarde 1, maar sommige spellen gebruiken meerdere kaartspellen.unit_id
– is de eenheid (punten, chips, geld, enz.) die wordt gebruikt om het spel te scoren.entrance_fee
– is het aantal eenheden dat nodig is om deel te nemen aan het spel; dit kan NULL zijn als het spel niet vereist dat elke speler met een bepaald aantal eenheden begint.victory_conditions
– bepaalt welke speler de wedstrijd heeft gewonnen. We gebruiken de varchar gegevenstype om de overwinningsvoorwaarde van elk spel te beschrijven (d.w.z. eerste team dat 100 punten bereikt) en verlaat de applicatie om het te interpreteren. Deze flexibiliteit laat ruimte voor veel games die kunnen worden toegevoegd.match_result
– slaat het resultaat van de wedstrijd op in tekstformaat. Net als bijvictory_conditions
, laten we de toepassing de waarde interpreteren. Dit attribuut kan NULL zijn omdat we die waarde invullen op hetzelfde moment dat we deend_time
invoegen waarde.
De participant
tabel slaat gegevens op over alle deelnemers aan een wedstrijd. De match_id
en player_id
attributen zijn verwijzingen naar de match
en player
tafels. Samen vormen deze waarden de alternatieve sleutel van de tabel.
De meeste spellen roteren welke speler als eerste biedt of speelt. Meestal wordt in de eerste ronde de speler die als eerste speelt (de openingsspeler) bepaald door de spelregels. In de volgende ronde gaat de speler links (of soms rechts) van de oorspronkelijke openingsspeler als eerste. We gebruiken de initial_player_order
attribuut om het volgnummer van de openingsspeler van de eerste ronde op te slaan. De match_id
en de initial_player_order
attributen vormen een andere alternatieve sleutel omdat twee spelers niet tegelijkertijd kunnen spelen.
De score
kenmerk wordt bijgewerkt wanneer een speler een wedstrijd beëindigt. Soms is dit op hetzelfde moment voor alle spelers (bijvoorbeeld in belot of préférence) en soms terwijl de wedstrijd nog aan de gang is (bijvoorbeeld poker of blackjack).
Acties en actietypes
Als we denken aan acties die spelers kunnen uitvoeren in een kaartspel, realiseren we ons dat we het volgende moeten opslaan:
- Wat de actie was
- Wie heeft die actie uitgevoerd
- Wanneer (in welke deal) de actie plaatsvond
- Welke kaart(en) werden bij die actie gebruikt
Het action_type
table is een eenvoudig woordenboek dat de namen van speleracties bevat. Enkele mogelijke waarden zijn:kaart trekken, kaart spelen, kaart doorgeven aan een andere speler, controleren en verhogen.
In de action
tabel, slaan we alle gebeurtenissen op die tijdens een deal hebben plaatsgevonden. De deal_id
, card_id
, participant_id
en action_type_id
zijn verwijzingen naar de tabellen die waarden voor deal, kaartdeelnemer en action_type bevatten. Merk op dat de participant_id
en card_id
kunnen NULL-waarden zijn. Dit komt door het feit dat sommige acties niet door spelers worden gedaan (bijv. de dealer trekt een kaart en legt deze open), terwijl andere geen kaarten bevatten (bijv. een verhoging bij poker). We moeten al deze acties opslaan om de hele wedstrijd opnieuw te kunnen maken.
De action_order
attribuut slaat het volgnummer van een in-game actie op. Een openingsbod zou bijvoorbeeld een 1-waarde krijgen; het volgende bod zou een waarde 2 hebben, enz. Er kan niet meer dan één actie tegelijkertijd plaatsvinden. Daarom is de deal_id
en action_order
attributen vormen samen de alternatieve sleutel.
De action_notation
attribuut bevat een gedetailleerde beschrijving van een actie. In poker kunnen we bijvoorbeeld een raise . opslaan actie en een willekeurig bedrag. Sommige acties kunnen ingewikkelder zijn, dus het is verstandig om deze waarden als tekst op te slaan en de toepassing de interpretatie ervan over te laten.
Deals en dealvolgorde
Een wedstrijd bestaat uit een of meer kaartdeals. We hebben het al gehad over de participant
en de match
tabellen, maar we hebben ze in de afbeelding opgenomen om hun relatie tot de deal
en deal_order
tabellen.
De deal
tabel slaat alle gegevens op die we nodig hebben over een enkele overeenkomstinstantie.
De match_id
kenmerk relateert die instantie aan de juiste overeenkomst, terwijl start_time
en end_time
geven de exacte tijd aan waarop die instantie begon en wanneer deze was voltooid.
De move_time_limit
en de deal_result
attributen zijn zowel tekstvelden die worden gebruikt om tijdslimieten op te slaan (indien van toepassing) als een beschrijving van het resultaat van die deal.
In de participant
tabel, de initial_player_order
attribuut slaat de spelersvolgorde op voor de instantie van de openingswedstrijd. Het opslaan van de orders voor volgende beurten vereist een geheel nieuwe tafel - de deal_order
tafel.
Uiteraard, deal_id
en participant_id
zijn verwijzingen naar een match-instantie en een deelnemer. Samen vormen ze de eerste alternatieve sleutel in de deal_order
tafel. De player_order
attribuut bevat waarden die de bestellingen aangeven die spelers hebben deelgenomen aan die wedstrijdinstantie. Samen met deal_id
, vormt het de tweede alternatieve sleutel in deze tabel. De deal_result
attribuut is een tekstveld dat het resultaat van de wedstrijd voor een individuele speler beschrijft. De score
attribuut slaat een numerieke waarde op die gerelateerd is aan het dealresultaat.
Saturen, rangen en kaarten
Dit gedeelte van het model beschrijft de kaarten die we in alle ondersteunde spellen zullen gebruiken. Elke kaart heeft een kleur en rang.
De suit_type
table is een woordenboek dat alle soorten kostuums bevat die we zullen gebruiken. Voor suit_type_name
, gebruiken we waarden als "Franse pakken", "Duitse pakken", "Zwitsers-Duitse pakken" en "Latijnse pakken".
Het suit
tabel bevat de namen van alle kleuren die zijn opgenomen in specifieke kaartsoorten. Het Franse kaartspel heeft bijvoorbeeld kleuren genaamd "Spades", "Hearts", "Diamonds" en "Clubs".
In de rank
woordenboek, vinden we bekende kaartwaarden zoals "Aas", "Koning", "Koningin" en "Jack".
De card
tabel bevat een lijst van alle mogelijke kaarten. Elke kaart komt maar één keer in deze tabel voor. Dat is de reden dat de suit_id
en rank_id
attributen vormen de alternatieve sleutel van deze tabel. De waarden van beide attributen kunnen NULL zijn omdat sommige kaarten geen reeks of rang hebben (bijv. jokerkaarten). De is_joker_card
is een zelfverklarende Booleaanse waarde. De card_name
attribuut beschrijft een kaart met de tekst:"Schoppenaas".
Kaarten en kaartspellen
Kaarten horen bij kaartspellen. Omdat één kaart in meerdere kaartspellen kan voorkomen, hebben we een n:n . nodig relatie tussen de card
en deck
tabellen.
In het deck
tabel, slaan we de namen op van alle kaartspellen die we willen gebruiken. Een voorbeeld van waarden die zijn opgeslagen in de deck_name
attributen zijn:"Standaard kaartspel met 52 kaarten (Frans)" of "spel met 32 kaarten (Duits)".
Het card_in_deck
relatie wordt gebruikt om kaarten toe te wijzen aan de juiste kaartspellen. De card_id
– deck_id
pair is de alternatieve sleutel van het deck
tafel.
Overeenkomsteigenschappen, gebruikte decks en eenheden
Dit gedeelte van het model bevat enkele basisparameters voor het starten van een nieuw spel.
Het belangrijkste onderdeel van deze sectie is het game
tafel. In deze tabel worden gegevens opgeslagen over door applicaties ondersteunde spellen. De game_name
attribuut bevat waarden zoals “poker”, “blackjack”, “belot” en “préférence”.
De min_number_of_players
en max_number_of_players
zijn het minimale en maximale aantal deelnemers aan een wedstrijd. Deze attributen dienen als grenzen voor het spel en worden aan het begin van een wedstrijd op het scherm getoond. De persoon die de wedstrijd start, moet een waarde uit dit bereik selecteren.
De min_entrance_fee
en de max_entrance_fee
attributen geeft het bereik van de toegangsprijs aan. Nogmaals, dit is gebaseerd op het spel dat wordt gespeeld.
In possible_victory_condition
, slaan we alle overwinningsvoorwaarden op die aan een wedstrijd kunnen worden toegewezen. Waarden worden gescheiden door een scheidingsteken.
De unit
woordenboek wordt gebruikt om elke eenheid op te slaan die in al onze spellen wordt gebruikt. De unit_name
attribuut zal waarden bevatten zoals “point”, “dollar”, “euro” en “chip”.
Het game_deck
en game_unit
tabellen gebruiken dezelfde logica. Ze bevatten lijsten met alle kaartspellen en eenheden die in een wedstrijd kunnen worden gebruikt. Daarom is de game_id
– deck_id
paar en de game_id
– unit_id
koppel alternatieve sleutels in hun respectievelijke tabellen.
Scores
In onze applicatie willen we de scores opslaan van alle spelers die hebben deelgenomen aan onze kaartspellen. Voor elk spel wordt een enkele numerieke waarde berekend en opgeslagen. (De berekening is gebaseerd op de resultaten van de speler in alle spellen van een enkel type.) Deze spelersscore is vergelijkbaar met een rangorde; het laat gebruikers ongeveer weten hoe goed een speler is.
Terug naar het rekenproces. We maken een n:n relatie tussen de player
en game
tafels. Dat is de player_score
tafel in ons model. De player_id
en de score_id
” vormen samen de alternatieve sleutel van de tafel. De “score
attribuut wordt gebruikt om de eerder genoemde numerieke waarde op te slaan.
Er zijn verschillende kaartspellen die zeer verschillende regels, kaarten en kaartspellen gebruiken. Om een database te maken die gegevens voor meer dan één kaartspel opslaat, moeten we enkele generalisaties maken. Een manier waarop we dit doen is door beschrijvende tekstvelden te gebruiken en de toepassing deze te laten interpreteren. We zouden manieren kunnen bedenken om de meest voorkomende situaties te dekken, maar dat zou het databaseontwerp exponentieel compliceren.
Zoals dit artikel heeft laten zien, kun je voor veel spellen één database gebruiken. Waarom zou je dit doen? Drie redenen:1) u kunt dezelfde database hergebruiken; 2) het zou de analyse vereenvoudigen; en dit zou leiden tot 3) het bouwen van betere AI-tegenstanders.