Hur man designar en databas som är tillräckligt flexibel för att rymma flera väldigt olika kortspel.
Nyligen visade vi hur en databas kan användas för att lagra brädspelsresultat. Brädspel är roliga, men de är inte den enda onlineversionen av klassiska spel som finns. Kortspel är också mycket populära. De introducerar ett element av tur i spelet, och det är mycket mer än tur involverat i ett bra kortspel!
I den här artikeln kommer vi att fokusera på att bygga en datamodell för att lagra matcher, resultat, spelare och poäng. Den största utmaningen här är att lagra data relaterad till många olika kortspel. Vi kan också överväga att analysera dessa data för att bestämma vinnande strategier, förbättra våra egna spelfärdigheter eller bygga en bättre AI-motståndare.
De fyra kortspelen vi kommer att använda i vår databas
Eftersom spelare inte kan kontrollera handen de får, kombinerar kortspel strategi, skicklighet och tur. Den turfaktorn ger en nybörjare chansen att slå en erfaren spelare, och det gör kortspel beroendeframkallande. (Detta skiljer sig från spel som schack, som är mycket beroende av logik och strategi. Jag har hört från många spelare att de inte är intresserade av att spela schack eftersom de inte kan hitta motståndare på sin skicklighetsnivå.)
Vi kommer att fokusera på fyra välkända kortspel:poker, blackjack, belot (eller belote) och préférence. Var och en av dem har relativt komplexa regler och kräver lite tid att bemästra. Förhållandet mellan tur och kunskap är också olika för varje spel.
Vi tar en snabb titt på de förenklade reglerna och detaljerna för alla fyra spelen nedan. Spelbeskrivningarna är ganska sparsamma, men vi har inkluderat tillräckligt för att visa de olika spelsätten och de olika reglerna vi kommer att stöta på under designprocessen för databasen.
Blackjack:
- Däck: En till åtta kortlekar med vardera 52 kort; inga jokerkort
- Spelare: Dealer och 1 eller flera motståndare
- Använd enhet: Vanligtvis pengar
- Grundläggande regler: Spelare får 2 kort som bara de kan se; dealern får två kort, ett med framsidan upp och det andra med framsidan nedåt; varje spelare bestämmer sig för att dra fler kort (eller inte); återförsäljaren drar sist. Kort har tilldelat poängvärden från 1 till 11.
- Möjliga spelaråtgärder: Hit, Stand, Split, Surrender
- Mål- och segervillkor: Summan av en spelares kort är större än dealerns; om någon spelare går över 21, förlorar den spelaren.
Poker (Texas Hold’Em):
- Däck: Standard (även känd som fransk färg) 52-korts kortlek; inga jokerkort. Korten är oftast röda och svarta.
- Spelare: Två till nio; spelare turas om att dela ut
- Använd enhet:Vanligtvis chips
- Grundläggande regler: Varje spelare börjar med att få två kort; spelare lägger sina insatser; tre kort delas ut med framsidan upp i mitten av bordet; spelare lägger igen sina insatser; ett fjärde kort placeras i mitten och spelarna satsar igen; sedan placeras det femte och sista kortet och den sista satsningsrundan är klar.
- Möjliga spelaråtgärder: Vik, ring, höj, liten mörk, stor mörk, återhöjning
- Mål: Kombinera den bästa möjliga handen av fem kort (från de två korten i spelarens hand och de fem korten i mitten av bordet)
- Segervillkor:Vanligtvis för att vinna alla marker på bordet
Belot (kroatisk variant av Belote):
- Däck: Vanligtvis den traditionella tyska eller ungerska 32-kortsleken; inga jokerkort
- Spelare: Två till fyra; vanligtvis fyra spelare i par om två
- Använd enhet: Poäng
- Grundläggande regler: För ett spel för fyra spelare får varje spelare sex kort på handen och två kort med framsidan nedåt; spelare första bud för trumf färg; efter att trumf har bestämts tar de de två nedåtvända korten och lägger dem i sin hand; en deklarationsrunda följer, under vilken vissa kortkombinationer tillkännages för ytterligare poäng; spelet fortsätter tills alla kort har använts.
- Möjliga spelaråtgärder: Pass, bud färg, deklaration, kastkort
- Mål för handen: Att vinna mer än hälften av poängen
- Segervillkor: Bli det första laget som får 1001 poäng eller mer
Preferens:
- Däck: Oftast en traditionell tysk eller ungersk kortlek med 32 kort; inga jokerkort
- Spelare: Tre
- Enheter: Poäng
- Grundläggande regler: Alla spelare tilldelas 10 kort; två "katt"- eller "talon"-kort placeras i mitten av bordet; spelare avgör om de vill bjuda på en färg; spelare bestämmer sig för att spela eller inte.
- Möjliga spelaråtgärder: Passera, bjud färg, spela, spela inte, kasta kort
- Mål: Beror på vilken variant av Préférence som spelas; i standardversionen måste budgivaren vinna totalt sex stick.
- Segervillkor: När summan av alla tre spelarnas poäng är 0 vinner spelaren med lägst antal poäng.
Varför kombinera databaser och kortspel?
Vårt mål här är att designa en databasmodell som kan lagra all relevant data för dessa fyra kortspel. Databasen skulle kunna användas av en webbapplikation som en plats för att lagra all relevant data. Vi vill lagra initiala spelinställningar, speldeltagare, åtgärder som vidtas under spelet och resultatet av en enda affär, hand eller trick. Vi måste också tänka på att en match kan ha en eller flera affärer kopplade till sig.
Från det vi lagrar i vår databas ska vi kunna återskapa alla de handlingar som ägde rum under spelet. Vi kommer att använda textfält för att beskriva segerförhållanden, spelåtgärder och deras resultat. Dessa är specifika för varje spel och webbapplikationslogiken kommer att tolka texten och omvandla dem efter behov.
En snabb introduktion till modellen
Denna modell gör det möjligt för oss att lagra all relevant speldata, inklusive:
- Spelegenskaper
- Lista över spel och matcher
- Deltagare
- Åtgärder i spelet
Eftersom spel skiljer sig åt på många sätt kommer vi ofta att använda varchar(256) datatyp för att beskriva egenskaper, rörelser och resultat.
Spelare, matcher och deltagare
Denna del av modellen består av tre tabeller och används för att lagra data om registrerade spelare, de spelade matcherna och spelarna som deltog.
player
tabellen lagrar data om registrerade spelare. username
och email
attribut är unika värden. nick_name
attribut lagrar spelarnas skärmnamn.
match
tabellen innehåller alla relevanta matchningsdata. I allmänhet består en match av en eller flera kortutdelningar (även känd som rundor, händer eller trick). Alla matcher har fastställda regler innan spelet börjar. Attributen är följande:
game_id
– refererar till tabellen som innehåller listan över spel (poker, blackjack, belot och préférence, i det här fallet).start_time
ochend_time
är de faktiska tidpunkterna när en match startar och slutar. Lägg märke till attend_time
kan vara NULL; vi kommer inte att ha dess värde förrän spelet är slut. Dessutom, om en match överges innan den är klar, ärend_time
värdet kan förbli NULL.number_of_players
– är antalet deltagare som krävs för att starta speletdeck_id
– refererar till kortleken som används i spelet.decks_used
– är antalet kortlekar som används för att spela spelet. Vanligtvis är detta värde 1, men vissa spel använder flera kortlekar.unit_id
– är enheten (poäng, marker, pengar, etc.) som används för att göra poäng i spelet.entrance_fee
– är antalet enheter som behövs för att gå med i spelet; detta kan vara NULL om spelet inte kräver att varje spelare börjar med ett visst antal enheter.victory_conditions
– avgör vilken spelare som vann matchen. Vi använder varchar datatyp för att beskriva varje matchs segertillstånd (dvs. det första laget som når 100 poäng) och lämna applikationen för att tolka det. Denna flexibilitet ger utrymme för många spel som kan läggas till.match_result
– lagrar resultatet av matchen i textformat. Som medvictory_conditions
, låter vi applikationen tolka värdet. Det här attributet kan vara NULL eftersom vi fyller det värdet samtidigt som vi infogarend_time
värde.
participant
tabellen lagrar data om alla deltagare i en match. match_id
och player_id
attribut är referenser till match
och player
tabeller. Tillsammans bildar dessa värden tabellens alternativa nyckel.
De flesta spelen roterar vilken spelare som bjuder eller spelar först. Vanligtvis i den första omgången bestäms spelaren som spelar först (öppningsspelaren) av spelreglerna. I nästa omgång kommer spelaren till vänster (eller ibland till höger) om den ursprungliga öppningsspelaren att gå först. Vi använder initial_player_order
attribut för att lagra ordningsnumret för den första omgångens öppningsspelare. match_id
och initial_player_order
attribut bildar en annan alternativ nyckel eftersom två spelare inte kan spela samtidigt.
score
attribut uppdateras när en spelare avslutar en match. Ibland kommer detta att vara i samma ögonblick för alla spelare (t.ex. i belot eller préférence) och ibland medan matchen fortfarande pågår (t.ex. poker eller blackjack).
Åtgärder och åtgärdstyper
När vi tänker på handlingar som spelare kan göra i ett kortspel, inser vi att vi måste lagra:
- Vad åtgärden var
- Vem utförde den åtgärden
- När (i vilken affär) åtgärden ägde rum
- Vilka kort användes i den åtgärden
action_type
table är en enkel ordbok som innehåller namnen på spelarnas handlingar. Några möjliga värden inkluderar dra kort, spela kort, skicka kort till en annan spelare, checka och höja.
I action
tabell lagrar vi alla händelser som hände under en affär. deal_id
, card_id
, participant_id
och action_type_id
är referenser till tabellerna som innehåller värden för deal, kortdeltagare och action_type. Lägg märke till att participant_id
och card_id
kan vara NULL-värden. Detta beror på det faktum att vissa åtgärder inte görs av spelare (t.ex. dealern drar ett kort och lägger det uppåt), medan vissa inte inkluderar kort (t.ex. en höjning i poker). Vi måste lagra alla dessa åtgärder för att kunna återskapa hela matchen.
action_order
attribut lagrar ordningsnumret för en handling i spelet. Till exempel skulle ett öppningsbud få ett värde på 1; nästa bud skulle ha ett värde på 2, etc. Det kan inte hända mer än en åtgärd samtidigt. Därför är deal_id
och action_order
attribut tillsammans bildar den alternativa nyckeln.
action_notation
attribut innehåller en detaljerad beskrivning av en åtgärd. I poker, till exempel, kan vi lagra en höjning åtgärd och ett godtyckligt belopp. Vissa åtgärder kan vara mer komplicerade, så det är klokt att lagra dessa värden som text och låta programmet tolka det.
Erbjudanden och erbjudanden
En match består av en eller flera kortutdelningar. Vi har redan diskuterat participant
och match
tabeller, men vi har tagit med dem i bilden för att visa deras relation till deal
och deal_order
tabeller.
deal
Tabell lagrar all data vi behöver om en enda matchningsinstans.
match_id
attribut relaterar den instansen till lämplig matchning, medan start_time
och end_time
ange den exakta tidpunkten när den instansen började och när den var klar.
move_time_limit
och deal_result
attribut är både textfält som används för att lagra tidsgränser (om tillämpligt) och en beskrivning av resultatet av den affären.
I participant
tabellen, initial_player_order
attribut lagrar spelarorder för den inledande matchinstansen. Att lagra beställningarna för efterföljande vändningar kräver ett helt nytt bord – deal_order
bord.
Självklart, deal_id
och participant_id
är referenser till en matchinstans och en deltagare. Tillsammans bildar de den första alternativa nyckeln i deal_order
tabell. player_order
attributet innehåller värden som anger de order som spelarna deltog i den matchningsinstansen. Tillsammans med deal_id
, bildar den den andra alternativa nyckeln i denna tabell. deal_result
attribut är ett textfält som beskriver resultatet av matchen för en enskild spelare. score
attribut lagrar ett numeriskt värde relaterat till affärens resultat.
Suits, Ranks and Cards
Det här avsnittet av modellen beskriver korten vi kommer att använda i alla spel som stöds. Varje kort har en färg och rang.
suit_type
table är en ordbok som innehåller alla färgtyper vi kommer att använda. För suit_type_name
, kommer vi att använda värden som "franska kostymer", "tyska kostymer", "schweizisk-tyska kostymer" och "latinska kostymer".
suit
Tabellen innehåller namnen på alla färger som ingår i specifika däcktyper. Till exempel har den franska kortleken färger som heter "Spades", "Hearts", "Diamonds" och "Clubs".
I rank
ordbok, hittar vi välkända kortvärden som "ess", "kung", "drottning" och "knekt".
card
Tabellen innehåller en lista över alla möjliga kort. Varje kort kommer endast att visas i denna tabell en gång. Det är anledningen till att suit_id
och rank_id
attribut bildar den här tabellens alternativa nyckel. Båda attributens värden kan vara NULL eftersom vissa kort inte har en färg eller en rang (t.ex. jokerkort). is_joker_card
är ett självförklarande booleskt värde. card_name
attribut beskriver ett kort med text:"Spades ess".
Kort och kortlekar
Kort hör till kortlekar. Eftersom ett kort kan visas i flera kortlekar behöver vi ett n:n förhållandet mellan card
och deck
tabeller.
I deck
tabell lagrar vi namnen på alla kortlekar som vi vill använda. Ett exempel på värden lagrade i deck_name
attribut är:"Standard 52-kortslek (franska)" eller "32-kortslek (tyska)".
card_in_deck
relation används för att tilldela kort till lämpliga kortlekar. card_id
– deck_id
par är den alternativa nyckeln för deck
tabell.
Matchegenskaper, däck och använda enheter
Det här avsnittet av modellen innehåller några grundläggande parametrar för att starta ett nytt spel.
Huvuddelen av det här avsnittet är game
tabell. Den här tabellen lagrar data om applikationsstödda spel. game_name
attributet innehåller värden som "poker", "blackjack", "belot" och "préférence".
min_number_of_players
och max_number_of_players
är det minimala och maximala antalet deltagare i en match. Dessa attribut fungerar som gränser för spelet, och de visas på skärmen i början av en match. Personen som initierar matchningen måste välja ett värde från detta intervall.
min_entrance_fee
och max_entrance_fee
attribut anger intervallet för entréavgifter. Återigen, detta är baserat på spelet som spelas.
I possible_victory_condition
, vi lagrar alla segervillkor som kan tilldelas en match. Värden separeras med en avgränsare.
unit
ordboken används för att lagra varje enhet som används i alla våra spel. unit_name
attribut kommer att innehålla värden som "point", "dollar", "euro" och "chip".
game_deck
och game_unit
tabeller använder samma logik. De innehåller listor över alla kortlekar och enheter som kan användas i en match. Därför är game_id
– deck_id
paret och game_id
– unit_id
par bildar alternativa nycklar i sina respektive tabeller.
Poäng
I vår applikation vill vi lagra poängen för alla spelare som deltog i våra kortspel. För varje spel beräknas och lagras ett enda numeriskt värde. (Beräkningen baseras på spelarens resultat i alla spel av en enda typ.) Denna spelarpoäng liknar en ranking; det låter användarna veta ungefär hur bra en spelare är.
Tillbaka till beräkningsprocessen. Vi skapar en n:n relation mellan player
och game
tabeller. Det är player_score
bord i vår modell. player_id
och score_id
” bildar tillsammans tabellens alternativa nyckel. "score
attribut används för att lagra det tidigare nämnda numeriska värdet.
Det finns olika kortspel som använder väldigt olika regler, kort och kortlekar. För att skapa en databas som lagrar data för mer än ett kortspel måste vi göra några generaliseringar. Ett sätt vi gör detta är genom att använda beskrivande textfält och låta applikationen tolka dem. Vi skulle kunna komma på sätt att täcka de vanligaste situationerna, men det skulle exponentiellt komplicera databasdesignen.
Som den här artikeln har visat kan du använda en databas för många spel. Varför skulle du göra det här? Tre skäl:1) du kan återanvända samma databas; 2) det skulle förenkla analysen; och detta skulle leda till 3) byggandet av bättre AI-motståndare.