Jak zaprojektować bazę danych wystarczająco elastyczną, aby pomieścić kilka bardzo różnych gier karcianych.
Niedawno pokazaliśmy, jak można wykorzystać bazę danych do przechowywania wyników gier planszowych. Gry planszowe są fajne, ale nie są jedyną dostępną wersją klasycznych gier online. Bardzo popularne są również gry karciane. Wprowadzają do rozgrywki element szczęścia, a w dobrej grze karcianej nie chodzi tylko o szczęście!
W tym artykule skupimy się na budowaniu modelu danych do przechowywania meczów, wyników, graczy i wyników. Głównym wyzwaniem jest tutaj przechowywanie danych związanych z wieloma różnymi grami karcianymi. Możemy również rozważyć analizę tych danych, aby określić zwycięskie strategie, poprawić własne umiejętności gry lub zbudować lepszego przeciwnika AI.
Cztery gry karciane, których użyjemy w naszej bazie danych
Ponieważ gracze nie mogą kontrolować rozdania, w których są rozdawane, gry karciane łączą strategię, umiejętności i szczęście. Ten czynnik szczęścia daje początkującemu szansę na pokonanie doświadczonego gracza i sprawia, że gry karciane są uzależniające. (Różni się to od gier takich jak szachy, które w dużej mierze opierają się na logice i strategii. Słyszałem od wielu graczy, że nie są zainteresowani grą w szachy, ponieważ nie mogą znaleźć przeciwników na ich poziomie umiejętności.)
Skoncentrujemy się na czterech dobrze znanych grach karcianych:pokerze, blackjacku, belot (lub belote) i préférence. Każda z nich ma stosunkowo złożone zasady i wymaga trochę czasu do opanowania. Stosunek szczęścia do wiedzy jest również inny dla każdej gry.
Przyjrzymy się pokrótce uproszczonym zasadom i szczegółom wszystkich czterech poniższych gier. Opisy gier są dość rzadkie, ale zamieściliśmy wystarczająco dużo, aby pokazać różne tryby rozgrywki i różnorodne zasady, które napotkamy podczas procesu projektowania bazy danych.
Blackjack:
- Pokład: Od jednej do ośmiu talii po 52 karty każda; bez kart jokera
- Gracze: Krupier i 1 lub więcej przeciwników
- Używana jednostka: Zwykle pieniądze
- Podstawowe zasady: Gracze otrzymują 2 karty, które tylko oni widzą; krupier otrzymuje dwie karty, jedną odkrytą, a drugą zakrytą; każdy gracz decyduje się dobrać więcej kart (lub nie); krupier dobiera ostatni. Karty mają przypisane wartości punktowe od 1 do 11.
- Możliwe działania gracza: Uderz, stój, podziel, poddaj się
- Warunek celu i zwycięstwa: Suma kart gracza jest większa niż krupiera; jeśli któryś z graczy przekroczy 21, ten gracz przegrywa.
Poker (Texas Hold’Em):
- Pokład: Standardowa (znana również jako kolor francuski) talia 52 kart; bez kart jokera. Karty są najczęściej w kolorze czerwonym i czarnym.
- Gracze: Dwa do dziewięciu; gracze na zmianę rozdają
- Używana jednostka:zwykle żetony
- Podstawowe zasady: Każdy gracz zaczyna od otrzymania dwóch kart; gracze stawiają zakłady; trzy karty są odkryte na środku stołu; gracze ponownie stawiają swoje zakłady; czwarta karta jest umieszczana na środku i gracze ponownie obstawiają; następnie kładzie się piątą i ostatnią kartę i kończy się ostatnia runda licytacji.
- Możliwe działania gracza: Pas, sprawdzenie, podbicie, mała ciemna, duża ciemna, przebicie
- Cel: Połącz najlepszą możliwą rękę z pięciu kart (z dwóch kart w ręce gracza i pięciu kart na środku stołu)
- Warunek zwycięstwa:zwykle wygranie wszystkich żetonów na stole
Belot (chorwacka odmiana Belote):
- Pokład: Zwykle tradycyjna niemiecka lub węgierska talia 32-kartowa; bez kart jokera
- Gracze: Dwa do czterech; zwykle czterech graczy w parach po dwie
- Używana jednostka: Punkty
- Podstawowe zasady: W grze czteroosobowej każdy gracz otrzymuje sześć kart w ręce i dwie karty zakryte; gracze jako pierwszy licytują kolor atutowy; po ustaleniu atu, biorą dwie zakryte karty i kładą je na ręce; następuje runda deklaracji, podczas której określone kombinacje kart są ogłaszane za dodatkowe punkty; gra jest kontynuowana, dopóki wszystkie karty nie zostaną wykorzystane.
- Możliwe działania gracza: Podaj, licytuj, deklaruj, rzuć kartę
- Cel dla ręki: Aby zdobyć więcej niż połowę punktów
- Stan zwycięstwa: Bądź pierwszą drużyną, która zdobędzie 1001 punktów lub więcej
Preferencje:
- Pokład: Najczęściej tradycyjna niemiecka lub węgierska talia 32-kartowa; bez kart jokera
- Gracze: trzy
- Jednostki: Punkty
- Podstawowe zasady: Wszyscy gracze otrzymują 10 kart; dwie karty „kotek” lub „szpon” kładzie się na środku stołu; gracze decydują, czy chcą licytować kolor; gracze decydują się grać, czy nie.
- Możliwe działania gracza: Podaj, licytuj, graj, nie graj, rzucaj kartą
- Cel: Zależy od odtwarzanego wariantu Préférence; w wersji standardowej licytujący musi wygrać łącznie sześć lew.
- Stan zwycięstwa: Gdy suma wyników wszystkich trzech graczy wynosi 0, wygrywa gracz z najmniejszą liczbą punktów.
Po co łączyć bazy danych i gry karciane?
Naszym celem tutaj jest zaprojektowanie modelu bazy danych, który mógłby przechowywać wszystkie istotne dane dla tych czterech gier karcianych. Baza danych może być wykorzystywana przez aplikację internetową jako miejsce do przechowywania wszystkich istotnych danych. Chcemy przechowywać początkowe ustawienia gry, uczestników gry, działania podjęte podczas gry oraz wynik pojedynczej rozdania, rozdania lub lewy. Musimy również pamiętać, że z meczem może być powiązana jedna lub więcej transakcji.
Z tego, co przechowujemy w naszej bazie danych, powinniśmy być w stanie odtworzyć wszystkie akcje, które miały miejsce podczas gry. Użyjemy pól tekstowych do opisania warunków zwycięstwa, działań w grze i ich wyników. Są one specyficzne dla każdej gry, a logika aplikacji internetowej zinterpretuje tekst i przekształci go w razie potrzeby.
Szybkie wprowadzenie do modelu
Ten model pozwala nam przechowywać wszystkie istotne dane gry, w tym:
- Właściwości gry
- Lista gier i meczów
- Uczestnicy
- Działania w grze
Ponieważ gry różnią się pod wieloma względami, często używamy varchar(256) typ danych do opisu właściwości, ruchów i wyników.
Gracze, mecze i uczestnicy
Ta sekcja modelu składa się z trzech tabel i służy do przechowywania danych o zarejestrowanych graczach, rozegranych meczach i graczach, którzy brali udział.
player
tabela przechowuje dane o zarejestrowanych graczach. username
i email
atrybuty są unikalnymi wartościami. nick_name
atrybut przechowuje nazwy ekranowe graczy.
match
tabela zawiera wszystkie istotne dane meczowe. Ogólnie mecz składa się z jednej lub więcej rozdań kart (znanych również jako rundy, ręce lub lewy). Wszystkie mecze mają ustalone zasady przed rozpoczęciem gry. Atrybuty są następujące:
game_id
– odwołuje się do tabeli zawierającej listę gier (w tym przypadku poker, blackjack, belot i préférence).start_time
iend_time
to rzeczywiste godziny rozpoczęcia i zakończenia meczu. Zauważ, żeend_time
może być NULL; nie będziemy mieli jego wartości, dopóki gra się nie skończy. Ponadto, jeśli dopasowanie zostanie porzucone przed zakończeniem,end_time
wartość może pozostać NULL.number_of_players
– czy liczba uczestników jest wymagana do rozpoczęcia grydeck_id
– odnosi się do talii używanej w grze.decks_used
– to liczba talii używanych do gry. Zwykle wartość ta wynosi 1, ale w niektórych grach używa się wielu talii.unit_id
– czy jednostka (punkty, żetony, pieniądze itp.) użyta do zdobycia punktów w grze.entrance_fee
– liczba jednostek potrzebnych do dołączenia do gry; może to być NULL, jeśli gra nie wymaga od każdego gracza rozpoczęcia z określoną liczbą jednostek.victory_conditions
– określa, który zawodnik wygrał mecz. Użyjemy varcharu typ danych, aby opisać warunki zwycięstwa w każdej grze (tj. pierwsza drużyna, która zdobędzie 100 punktów) i pozostawić aplikacji, aby to zinterpretowała. Ta elastyczność pozostawia miejsce na dodawanie wielu gier.match_result
– przechowuje wynik meczu w formacie tekstowym. Tak jak w przypadkuvictory_conditions
, pozwolimy aplikacji zinterpretować wartość. Ten atrybut może mieć wartość NULL, ponieważ wypełnimy tę wartość w tym samym czasie, w którym wstawimyend_time
wartość.
participant
tabela przechowuje dane o wszystkich uczestnikach meczu. match_id
i player_id
atrybuty są odniesieniami do match
i player
tabele. Razem te wartości tworzą klucz alternatywny tabeli.
Większość gier zmienia się, który gracz licytuje lub gra jako pierwszy. Zwykle w pierwszej rundzie gracz, który gra jako pierwszy (gracz otwierający), jest określony przez zasady gry. W następnej rundzie gracz po lewej (lub czasami po prawej) od pierwszego gracza otwierającego będzie pierwszy. Użyjemy initial_player_order
atrybut do przechowywania liczby porządkowej gracza otwierającego pierwszą rundę. match_id
i initial_player_order
atrybuty tworzą kolejny klucz alternatywny, ponieważ dwóch graczy nie może grać jednocześnie.
score
atrybut jest aktualizowany, gdy gracz kończy mecz. Czasami będzie to miało miejsce w tym samym momencie dla wszystkich graczy (np. w belot lub préférence), a czasami, gdy mecz jest nadal w toku (np. poker lub blackjack).
Akcje i typy akcji
Kiedy myślimy o działaniach, które gracze mogą wykonywać w grze karcianej, zdajemy sobie sprawę, że musimy przechowywać:
- Jaka była akcja
- Kto wykonał to działanie
- Kiedy (w której rozdaniu) miała miejsce akcja
- Które karty zostały użyte w tej akcji
action_type
table to prosty słownik, który zawiera nazwy działań graczy. Niektóre możliwe wartości obejmują dobranie karty, zagranie karty, przekazanie karty innemu graczowi, sprawdzenie i podbicie.
W action
tabeli, będziemy przechowywać wszystkie zdarzenia, które miały miejsce podczas transakcji. deal_id
, card_id
, participant_id
i action_type_id
są odniesieniami do tabel, które zawierają wartości deal, card part i action_type. Zauważ, że participant_id
i card_id
mogą być wartościami NULL. Wynika to z faktu, że niektóre akcje nie są wykonywane przez graczy (np. krupier dobiera kartę i kładzie ją odkrytą), a niektóre nie zawierają kart (np. podbicie w pokerze). Musimy zapisać wszystkie te działania, aby móc odtworzyć całe dopasowanie.
action_order
atrybut przechowuje liczbę porządkową akcji w grze. Na przykład oferta otwierająca otrzyma wartość 1; następna oferta miałaby wartość 2 itd. W tym samym czasie nie może być wykonywana więcej niż jedna akcja. Dlatego deal_id
i action_order
atrybuty razem tworzą klucz alternatywny.
action_notation
atrybut zawiera szczegółowy opis akcji. Na przykład w pokerze możemy zapisać podbicie działanie i dowolną kwotę. Niektóre działania mogą być bardziej skomplikowane, więc dobrze jest przechowywać te wartości jako tekst i pozostawić aplikacji do ich interpretacji.
Okazy i zamówienia
Mecz składa się z co najmniej jednego rozdania kart. Omówiliśmy już participant
i match
tabele, ale uwzględniliśmy je na obrazku, aby pokazać ich związek z deal
i deal_order
tabele.
deal
tabela przechowuje wszystkie potrzebne nam dane dotyczące pojedynczej instancji dopasowania.
match_id
atrybut wiąże tę instancję z odpowiednim dopasowaniem, podczas gdy start_time
i end_time
wskaż dokładny czas rozpoczęcia i zakończenia tej instancji.
move_time_limit
i deal_result
atrybuty to zarówno pola tekstowe używane do przechowywania limitów czasowych (jeśli ma to zastosowanie), jak i opis wyniku tej transakcji.
W participant
tabela, initial_player_order
atrybut przechowuje kolejność graczy dla instancji meczu otwierającego. Przechowywanie zamówień na kolejne tury wymaga zupełnie nowej tabeli – deal_order
tabela.
Oczywiście deal_id
i participant_id
są odniesieniami do instancji meczu i uczestnika. Razem tworzą pierwszy klucz alternatywny w deal_order
stół. player_order
atrybut zawiera wartości oznaczające rozkazy, jakie gracze uczestniczyli w danej instancji meczu. Wraz z deal_id
, tworzy drugi klucz alternatywny w tej tabeli. deal_result
atrybut to pole tekstowe opisujące wynik meczu dla pojedynczego gracza. score
atrybut przechowuje wartość liczbową związaną z wynikiem transakcji.
Garnitury, rangi i karty
Ta sekcja modelu opisuje karty, których będziemy używać we wszystkich obsługiwanych grach. Każda karta ma kolor i rangę.
suit_type
table to słownik zawierający wszystkie używane przez nas typy kolorów. Dla suit_type_name
, użyjemy wartości takich jak „Garnitury francuskie”, „Garnitury niemieckie”, „Garnitury szwajcarsko-niemieckie” i „Garnitury łacińskie”.
suit
tabela zawiera nazwy wszystkich kolorów zawartych w poszczególnych typach talii. Na przykład francuska talia ma kolory o nazwach „Spades”, „Hearts”, „Diamonds” i „Clubs”.
W rank
słownika, znajdziemy dobrze znane wartości kart, takie jak „As”, „Król”, „Dama” i „Walet”.
card
tabela zawiera listę wszystkich możliwych kart. Każda karta pojawi się w tej tabeli tylko raz. To jest powód, dla którego suit_id
i rank_id
atrybuty tworzą klucz alternatywny tej tabeli. Wartości obu atrybutów mogą być NULL, ponieważ niektóre karty nie mają koloru lub rangi (np. karty jokera). is_joker_card
to oczywista wartość logiczna. card_name
Atrybut opisuje kartę tekstem:„As pik”.
Karty i talie
Karty należą do talii. Ponieważ jedna karta może występować w wielu taliach, będziemy potrzebować n:n relacja między card
i deck
tabele.
W deck
tabeli, będziemy przechowywać nazwy wszystkich talii kart, których chcemy użyć. Przykład wartości przechowywanych w deck_name
atrybutami są:„Standardowa talia 52-kartowa (francuska)” lub „Talia 32-kartowa (niemiecka)”.
card_in_deck
Relacja służy do przypisywania kart do odpowiednich talii. card_id
– deck_id
para jest alternatywnym kluczem deck
stół.
Dopasuj właściwości, używane talie i jednostki
Ta sekcja modelu zawiera kilka podstawowych parametrów do rozpoczęcia nowej gry.
Główną częścią tej sekcji jest game
stół. Ta tabela przechowuje dane o grach obsługiwanych przez aplikacje. game_name
atrybut zawiera wartości takie jak „poker”, „blackjack”, „belot” i „préférence”.
min_number_of_players
i max_number_of_players
to minimalna i maksymalna liczba uczestników meczu. Te atrybuty służą jako granice gry i są wyświetlane na ekranie na początku meczu. Osoba inicjująca dopasowanie musi wybrać wartość z tego zakresu.
min_entrance_fee
i max_entrance_fee
atrybuty oznaczają zakres opłat za wstęp. Ponownie, jest to oparte na rozgrywanej grze.
W possible_victory_condition
, zachowamy wszystkie warunki zwycięstwa, które można przypisać do meczu. Wartości są oddzielone ogranicznikiem.
unit
Słownik służy do przechowywania każdej jednostki używanej we wszystkich naszych grach. unit_name
atrybut będzie zawierał wartości takie jak „punkt”, „dolar”, „euro” i „chip”.
game_deck
i game_unit
tabele używają tej samej logiki. Zawierają one listę wszystkich talii i jednostek, których można użyć w meczu. Dlatego game_id
– deck_id
para i game_id
– unit_id
sparuj klucze alternatywne w odpowiednich tabelach.
Wyniki
W naszej aplikacji będziemy chcieli przechowywać wyniki wszystkich graczy, którzy brali udział w naszych grach karcianych. Dla każdej gry obliczana i zapisywana jest pojedyncza wartość liczbowa. (Obliczenia są oparte na wynikach gracza we wszystkich grach jednego typu.) Ten wynik gracza jest podobny do rangi; pozwala użytkownikom z grubsza wiedzieć, jak dobry jest gracz.
Wróćmy do procesu kalkulacji. Utworzymy n:n relacja między player
i game
tabele. To jest player_score
stół w naszym modelu. player_id
i score_id
” razem tworzą alternatywny klucz tabeli. „score
atrybut jest używany do przechowywania wspomnianej wcześniej wartości liczbowej.
Istnieje wiele gier karcianych, które wykorzystują bardzo różne zasady, karty i talie. Aby stworzyć bazę danych przechowującą dane dla więcej niż jednej gry karcianej, musimy dokonać pewnych uogólnień. Jednym ze sposobów, w jaki możemy to zrobić, jest użycie opisowych pól tekstowych i pozwolenie aplikacji na ich interpretację. Moglibyśmy wymyślić sposoby na pokrycie najczęstszych sytuacji, ale to wykładniczo skomplikowałoby projekt bazy danych.
Jak pokazał ten artykuł, możesz używać jednej bazy danych dla wielu gier. Dlaczego miałbyś to zrobić? Trzy powody:1) możesz ponownie wykorzystać tę samą bazę danych; 2) uprościłoby analitykę; a to doprowadziłoby do 3) budowania lepszych przeciwników AI.