Это азартная карточная игра.
Основные правила игры: каждый игрок соревнуется со столом за наибольшее количество очков, но при этом не может превысить 21 очко. Это самые первоначальные и простые правила. Однако развитие до современного уровня сделали правила более сложными. Например, удвоение ставки, разделение карт, пять карт и другие специфические правила появились.
Для получения полной информации о правилах можно найти статью "21 очко" на Википедии.
Архитектура и функционал полностью независимы друг от друга. Это значит, что при построении системы мы должны выбрать наиболее подходящую архитектуру для текущих потребностей и реализовать функционал в рамках этой архитектуры — "Пропорциональная архитектура программного обеспечения".
Хотя игра в 21 очко кажется простой, это не так. Конечно, если ваша цель — создание самой базовой версии игры в 21 очко, вы можете использовать большое количество условий if-else. Это также будет считаться одной из возможных архитектур.
Если же вашей целью является возможность добавления игры в 21 очко в QQ-игре путём настройки основного модуля игры, или если онлайн-казино хочет запустить игру в 21 очко после некоторого расширения и конфигурирования этого основного модуля игры.
Более подробно, некоторые казино предлагают трикратное вознаграждение для BlackJack, в то время как другие предлагают двукратное. Некоторые казино позволяют делить карты ещё раз после первого разделения, некоторые нет. Некоторые казино разрешают увеличивать ставку только после первой пары карт, другие разрешают делать это в любое время.
Моя цель заключается в том, чтобы этот основной модуль мог поддерживать все эти различные ситуации.
Сложность заключается в том, что правила игры в 21 очко не стандартизированы, каждое казино имеет свои собственные правила. Программа должна быть способна адаптироваться ко всем этим изменениям.
Решением являются:
Я выбрал второй вариант решения.
Первым шагом является понимание того, что игровой процесс в игре в 21 очко остаётся постоянным, а меняются лишь особые случаи (такие как BlackJack) и их коэффициенты выплат.
Поэтому, игровой процесс можно сделать жестким, а особые случаи реализовать на динамическом языке Groovy.
Проектирование включает несколько важных концепций, которые помогут лучше понять моё проектирование и архитектуру:
Игрок (player): Стол может быть управляемым человеком или машиной. Таким образом, любой человек или машина на столе является игроком. Когда стол случайный, это означает, что любой игрок может быть дилером.
Представитель игрока (playerProxy): Некоторые правила позволяют одному игроку делать несколько ставок. Поэтому, мы абстрагируем концепцию "представителя игрока". На самом деле, представитель игрока контролирует карты и ставки. Все действия игрока выполняются через его представителя. Это позволяет решить проблему множества ставок одного игрока и деления карт.
Представитель дилера (dealerProxy): Когда игрок становится дилером, он выполняет все операции через своего представителя дилера.
Категория карт (cardCategory): Категория карт указывает на тип карт, который находится у игрока. Например, если первые две карты составляют Yöntemler ve yapılar tamamen bağımsızdır. Bu nedenle, sistemi inşa ederken mevcut ihtiyaçlara en uygun mimariyi seçmek ve bu mimarin içinde işlevleri gerçekleştirmek gereklidir - "Program Mimarisi".
Oyun 21'lik olsa da, bu kadar basit değildir. Oyuncuların, en temel versiyonunu oluşturmak için büyük sayıda if-else koşulları kullanmaları mümkündür. Ancak, QQ oyununa eklenmesi veya çevrimiçi kasinaların genişletilmiş ve yapılandırılmış bir temel modül üzerinden oynatılması gibi hedeflerde, daha karmaşık olabilir.
Kasino kurumları arasında Blackjack'te üç katlı ödül, bazılarının iki katlı olduğunu görebiliriz. Bazı kasinolar, ilk bölünme işleminden sonra ikinci bir bölünme yapmayı izin verirken, diğerleri bunu yasaktır. Bazı kasinolar, oyuncunun sadece ilk kart çiftinden sonra para yatırmasını, diğerleri ise herhangi bir zamanda yapmasını sağlar.
Bu temel modülün, tüm bu durumları desteklemesi gerekmektedir.
Zorluk, 21'lik oyunun kurallarının standartlaştırılmamış olması ve her kasinin kendi kuralları olmasıdır. Program, tüm bu değişikliklere uyabilmeli ve adapte olmalıdır.
Çözümler şunlardır:
İkinci çözüm seçeneğini tercih ettim.
Birincil adım, 21'lik oyunun oyun akışının sabit kalacağını ve özel durumların (örneğin, Blackjack) ve kazanma oranlarının değiştiğini anlamaktır.
Bu nedenle, oyun akışını sabit yapabilir ve özel durumları Groovy gibi dinamik bir dil ile uygulayabiliriz.
Tasarım, tasarımın ve mimarinizi daha iyi anlama yardımcı olan birkaç önemli kavram içerir:
Oyuncu (oyuncu): Masada bir insan veya makine yönetilebilir. Bu nedenle, masadaki her insan veya makine bir oyuncudur. Eğer masa rastgele ise, bu her oyuncunun dealer olabileceğini gösterir.
Oyuncu Proxy'si (oyuncuProxy): Bazı kurallar, bir oyuncunun birden fazla bahis yapmasını izin verir. Bu yüzden, "oyuncu proxy'si" kavramını soyutlamalıyız. Gerçekten de, oyuncu proxy'si, oyuncunun kartlarını ve bahislerini kontrol eder. Tüm oyuncu hareketleri, oyuncunun proxy'si aracılığıyla yapılır. Bu, tek bir oyuncunun birden fazla bahis yapması ve kartları bölmek sorununu çözer.
Dealer Proxy'si (dealerProxy): Bir oyuncu dealer olunca, tüm işlemleri dealer proxy'si aracılığıyla yapar.
Kart Kategorileri (kartKategori): Kart kategorileri, oyuncunun elindeki kart türünü belirtir. Örneğin, eğer ilk iki kart 21 puan veriyorsa, kart kategorisi Blackjack olacaktır. Çünkü kazanma oranları, kart kategorileri ve oyuncu hareketleri gibi faktörlerden ve kasino-lardan farklılık gösterir, bu bölüm Groovy ile uygulanır.
Oyuncu Hareketleri (oyuncuHareketleri): Oyuncular, oyun sırasında sınırlı sayıda hareket yapabilir: kart al (hit), kart almaya devam etme (stand) vb. Bu bölüm Java ile uygulanır.
Bank (bank): Bu, kasada para yönetimini simüle eden bir soyutlamadır. Tüm parayı yönetir, bahisler için yeterli miktarda para olup olmadığını denetler vb.
Masalar (masalar): Oyuncular, oyun oynamak için masalara katılabilirler. Masalar, tüm oyuncuların kart oyunlarını oynaması için bir arka plandır.
Groovy'nin Kullanımı Farklılıklarına:
XML Konfigürasyonu Neden Kullanılmıyor?
XML konfigürasyon dosyası kullanmanın birçok kişi tarafından tercih edildiği bilinmekle birlikte, şu nedenlerden dolayı tercih etmedim:
Elbette, XML kullanarak yapılandırma oluşturmayı tercih ederseniz, bunu yapabilirsiniz. Bu depolamanın genişletilebilirliği çok iyi. Sadece WinnerLoserCalculateEngine
'i değiştirmeniz yeterlidir, kazananlar ve kaybedicilerin hesaplanması için hesaplama motorunu.
Özel durumlar, 21'lik oyunun en büyük değişiklik kaynağıdır, bu yüzden bu durumların hangilerinin already uygulanmış ve test edilmiş olduğunu açıklamanın önemini belirtmek istiyorum.
Страхование является отдельной ставкой и рассчитывается отдельно. Замечание: При разделении карт страховая ставка не делится
BlackJack: Первые две карты составляют 21 очко (одна карта достоинством 10 и одна A).
Разделение карт (split): Если первые две карты игрока имеют одинаковое значение, он может разделить карты и сделать дополнительную ставку. Каждый разделённый набор карт должен иметь ту же сумму ставок, что и оригинальная ставка. Замечание: Некоторые казино разрешают повторное разделение карт, некоторые нет
Удвоение ставки (double down): После получения первых двух карт суммарное значение которых равно 11, игрок может удвоить свою ставку. Однако после удвоения ставки игрок может получить только одну дополнительную карту.
Пять карт (five-card trick): Если у игрока или дилера больше пяти карт, но они не превышают 21 очко.
Стрит (straight flush): У игрока три карты одного цвета с значениями 6, 7 и 8 соответственно.
На самом деле, вы можете использовать своё воображение для создания своих собственных особых случаев. Например, если у дилера BlackJack, а у игрока пять карт, это может быть ничья и т.д.
Отказ (surrender): Отказ возможен только после получения первых двух карт, в других случаях невозможен. При отказе игрок получает половину своей ставки обратно.
Разделение A (ace split): Это специальный случай разделения карт. Если первые две карты игрока — это A, он может разделить карты, но после разделения сможет получить только одну карту.
Выход
Вы не найдете здесь никаких слоёв MVC. Это потому, что наша задача состоит в реализации самого центрального бизнес-логического в игре в 21 очко. Так что использование слоёв MVC не подходит. Также, пока нет информации о долговременном хранении данных, поскольку это задача клиента, а также у меня нет времени для реализации такого функционала.
Это ядро. Цель реализации — сосредоточиться на бизнес-логике игры в 21 очко внутри этого ядра.
Технические возможности:
Бизнес-возможности:
Пока нет времени для реализации.
На данный момент работа над долговременным хранением данных практически отсутствует, поскольку у меня нет опыта работы с играми и есть вопросы, которые я не могу ответить.
Как реализовать автоматическое тестирование? То есть тестирование без использования mock объектов. Игроки будут играть автоматически.
Без автоматического тестирования невозможно протестировать ситуацию с конкурентным доступом.
Вопрос безопасности при использовании скриптов на Groovy.
Есть тестовый класс CasinoTest
. Этот тестовый класс моделирует простую игру. Из него можно узнать, как использовать этот основной модуль.Ниже представлен вывод тестового запуска.12:33:54.568 [main] INFO c.z.blackjack.core.CasinoTest - Три игрока внесли деньги в казино
12:33:54.573 [main] INFO c.z.blackjack.core.CasinoTest - Игрок A внес: 400.0
12:33:54.576 [main] INFO c.z.blackjack.core.CasinoTest - Игрок B внес: 400.0
12:33:54.577 [main] INFO c.z.blackjack.core.CasinoTest - Игрок C внес: 400.0
12:33:54.580 [main] WARN c.z.blackjack.core.CasinoTest - Три игрока присоединились к столу: 1
12:33:54.584 [main] INFO c.z.blackjack.core.CasinoTest - Выбор дилера: Игрок A
12:33:54.586 [main] INFO c.z.blackjack.core.CasinoTest - Игрок B сделал ставку: 20
12:33:54.586 [main] INFO c.z.blackjack.core.CasinoTest - Игрок C сделал ставку: 20
12:33:56.154 [main] INFO c.z.blackjack.core.CasinoTest - Начало
12:33:56.154 [main] INFO c.z.blackjack.core.CasinoTest - У игрока B карты с суммой: [20], состоящие из [{SPADE-Q}, {SPADE-K}]
12:33:56.154 [main] INFO c.z.blackjack.core.CasinoTest - У игрока C карты с суммой: [17], состоящие из [{HEART-J}, {HEART-7}]
12:33:56.236 [main] INFO c.z.blackjack.core.CasinoTest - Игрок B остановился, сумма: 20, состоит из [{SPADE-Q}, {SPADE-K}]
12:33:56.319 [main] INFO c.z.blackjack.core.CasinoTest - Игрок C остановился, сумма: 17, состоит из [{HEART-J}, {HEART-7}]
12:33:56.320 [main] INFO c.z.blackjack.core.CasinoTest - Дилер A имеет сумму 14, что меньше 17
12:33:56.474 [main] INFO c.z.blackjack.core.CasinoTest - Дилер взял ещё одну карту, последняя сумма: [24], состоит из [{HEART-6}, {HEART-8}, {DIAMOND-K}]
12:33:56.474 [main] INFO c.z.blackjack.core.CasinoTest - **********Все игроки остановились, началась фиксация счета
12:33:56.737 [main] INFO c.z.blackjack.core.CasinoTest - Игрок B сделал ставку 20.0, победитель: PLAYER, конечный баланс: 420.0
12:33:56.817 [main] INFO c.z.blackjack.core.CasinoTest - Игрок C сделал ставку 20.0, победитель: PLAYER, конечный баланс: 420.0
12:33:56.817 [main] INFO c.z.blackjack.core.CasinoTest - Дилер A имеет конечный баланс: 360.0
12:33:56.818 [main] WARN c.z.blackjack.core.CasinoTest - *******Конец
Конфигурация правил страхования
Файл конфигурации находится в пути классов: Insurance.groovy
Пример
// При выигрыше игрока он получает выигрыш
winMoney = playerFirstBet * 2
Конфигурация расчета выигрыша
На самом деле это конфигурация для расчета выигрыша/проигрыша игрока и дилера на основе типов карт игрока и дилера, а также возврат коэффициента выплаты
Файл конфигурации находится в пути классов: Settlement.groovy
Пример
// Если игрок выигрывает, то сколько он выигрывает
winMoney = 0
switch (playerCardCategory) {
// Royal Flush
case "royal":
winner = PLAYER
winMoney = playerBetSum * 3
break
case "Blackjack":
switch (playerLatestAction) {
case stand:
if (isDealerBlackjack) {
winner = PUSH
} else if (!isDealerBlackjack) {
winner = PLAYER
winMoney = playerBetSum * 3
}
default:
winner = PLAYER
winMoney = playerBetSum * 3
break
}
break
case "fiveCard":
winner = PLAYER
winMoney = playerBetSum * 3
break
case "bust":
winner = DEALER
break
default:
// Если у дилера больше 21 очка, считается проигрышем
if (dealerSumPoint > 21) {
if(playerSumPoint <= 21){
winner = PLAYER
winMoney = playerBetSum * 1;
break
}
if (playerSumPoint > 21) {
winner = PUSH
winMoney = 0
break;
}
}else{
if (playerSumPoint > dealerSumPoint) {
winner = PLAYER
winMoney = playerBetSum * 1
} else if (playerSumPoint < dealerSumPoint) {
winner = DEALER
} else {
winner = PUSH
}
}
break
}
Конфигурация типов карт
Это конфигурация для определения типа карт игрока на основе его карт
Файл конфигурации находится в пути классов: Special.groovy
Пример
switch (_headCard.cardCount) {
case 2:
if (_headCard.sumPoint == 21) { // Первые две карты имеют общую сумму 21, это Black Jack
name = "Blackjack"
actions = [_report]
} else if (_headCard.sumPoint == 11) { // Первые две карты имеют общую сумму 11, можно сделать двойную ставку
name = "doubleDown"
actions = [_hit, _stand, _doubleDown]
} else if (_headCard.isFirstTwoCardPointEquals()) { //Первые две карты имеют одинаковое значение, можно разделить карты
// После разделения карт нельзя снова делить
if (_headCard.isSplit()) {
return
}
name = "split"
actions = [_hit, _stand, _split]
}
break
case 3:
// Royal Flush
if (_headCard.containsAllCardFace(6, 7, 8) && _headCard.isCardFaceIdentical()) {
name = "royal"
actions = [_report]
}
break
case 5:
if (_headCard.sumPoint <= 21){
name = "fiveCard"
actions = [_report]
}
break
}
// Проигрыш
if (_headCard.sumPoint > 21) {
name = "bust"
actions = [_stop]
}
Переменные, доступные в конфигурационных скриптах:
binding.setVariable("PLAYER", Winner.PLAYER);
binding.setVariable("DEALER", Winner.DEALER);
binding.setVariable("PUSH", Winner.PUSH);
binding.setVariable("playerBetAmounts", playerProxy.getBet().getBetAmounts());
binding.setVariable("playerFirstBet", playerProxy.getBet().getBetAmounts().get(0));
binding.setVariable("playerBetSum", playerProxy.getBet().getBetSum());
binding.setVariable("playerLatestAction", playerProxy.getLatestActionName());
binding.setVariable("isPlayerBlackjack", playerProxy.isBlackJack());
binding.setVariable("playerCardCategory", playerProxy.getCardCategoryName());
binding.setVariable("playerSumPoint", playerProxy.getSumPoint());
binding.setVariable("playerCardCount", playerProxy.getHeadCard().getCardCount());
binding.setVariable("dealerCardCategory", dealerProxy.getCardCategoryName());
binding.setVariable("dealerSumPoint", dealerProxy.getSumPoint());
binding.setVariable("dealerCardCount", dealerProxy.getHeadCard().getCardCount());
binding.setVariable("isDealerBlackjack", dealerProxy.isBlackJack());
binding.setVariable(StandAction._name, StandAction._name);
binding.setVariable(HitAction._name, HitAction._name);
binding.setVariable(ReportAction._name, ReportAction._name);
binding.setVariable(DoubleDownAction._name, DoubleDownAction._name);
binding.setVariable(SplitAction._name, SplitAction._name);
binding.setVariable(StopAction._name, StopAction._name);
binding.setVariable(SurrenderAction._name, SurrenderAction._name);
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )