1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/zacker330-BlackjackGame

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Это зеркальный репозиторий, синхронизируется ежедневно с исходного репозитория.
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Игра в 21 очко версия 0.1.0

Это азартная карточная игра.

Основные правила игры: каждый игрок соревнуется со столом за наибольшее количество очков, но при этом не может превысить 21 очко. Это самые первоначальные и простые правила. Однако развитие до современного уровня сделали правила более сложными. Например, удвоение ставки, разделение карт, пять карт и другие специфические правила появились.

Для получения полной информации о правилах можно найти статью "21 очко" на Википедии.

Цели архитектуры

Архитектура и функционал полностью независимы друг от друга. Это значит, что при построении системы мы должны выбрать наиболее подходящую архитектуру для текущих потребностей и реализовать функционал в рамках этой архитектуры — "Пропорциональная архитектура программного обеспечения".

Хотя игра в 21 очко кажется простой, это не так. Конечно, если ваша цель — создание самой базовой версии игры в 21 очко, вы можете использовать большое количество условий if-else. Это также будет считаться одной из возможных архитектур.

Если же вашей целью является возможность добавления игры в 21 очко в QQ-игре путём настройки основного модуля игры, или если онлайн-казино хочет запустить игру в 21 очко после некоторого расширения и конфигурирования этого основного модуля игры.

Более подробно, некоторые казино предлагают трикратное вознаграждение для BlackJack, в то время как другие предлагают двукратное. Некоторые казино позволяют делить карты ещё раз после первого разделения, некоторые нет. Некоторые казино разрешают увеличивать ставку только после первой пары карт, другие разрешают делать это в любое время.

Моя цель заключается в том, чтобы этот основной модуль мог поддерживать все эти различные ситуации.

Сложности и решения

Сложность заключается в том, что правила игры в 21 очко не стандартизированы, каждое казино имеет свои собственные правила. Программа должна быть способна адаптироваться ко всем этим изменениям.

Решением являются:

  1. Реализация предметно-ориентированного языка для игры в 21 очко, где каждое казино может просто изменить свой предметно-ориентированный язык, не беспокоясь о конкретной технологии реализации.
  2. Устойчивые части игры (например, игровой процесс, начальная раздача двух карт каждому игроку) могут быть закреплены и реализованы на Java, тогда как изменяемые части могут быть реализованы на динамическом языке.

Я выбрал второй вариант решения.

Анализ и проектирование

Первым шагом является понимание того, что игровой процесс в игре в 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.

Zorluklar ve Çözümler

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:

  1. 21'lik oyun için nesne yönelimli dil uygulamasını gerçekleştirebilirsiniz. Her kasanın kendi nesne yönelimli dili var ve teknolojik uygulama konusunda endişelenmemelerini sağlayabilirsiniz.
  2. Oyunun sabit parçaları (örneğin, oyun akışı, her oyuncuya iki kart verme) Java'da sabitlenebilir ve uygulanabilirken, değişen parçalar dinamik bir dil olarak uygulanabilir.

İkinci çözüm seçeneğini tercih ettim.

Analiz ve Tasarım

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:

  1. Oyun kurallarının (özellikle özel durumlar) değiştirilmesi, sunucuyu yeniden başlatmadan yapılabilir.
  2. Projenin genişletilebilirliğini artırır.
  3. Oyunun ayarlanabilirligini artırır.

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:

  1. XML'in ifade gücü, oyun kurallarını tanımlamak için yetersizdir.
  2. Kendi yorumlayıcınızı oluşturmaya ihtiyacınız olacak, bu ek çaba gerektirir.
  3. Neden doğrudan yürütülebilen bir yapıyı kullanmayalım? Modern sistemler, örneğin ROR, Gradle ve Vagrant, tam olarak bu yaklaşımı kullanırlar.
  4. Ben XML'in çok kelimesiyle karşı karşıya olduğumu söyleyebilirim.

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

Ö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.

Uygulanmış1. Страхование (insurance):

Страхование является отдельной ставкой и рассчитывается отдельно. Замечание: При разделении карт страховая ставка не делится

  1. BlackJack: Первые две карты составляют 21 очко (одна карта достоинством 10 и одна A).

  2. Разделение карт (split): Если первые две карты игрока имеют одинаковое значение, он может разделить карты и сделать дополнительную ставку. Каждый разделённый набор карт должен иметь ту же сумму ставок, что и оригинальная ставка. Замечание: Некоторые казино разрешают повторное разделение карт, некоторые нет

  3. Удвоение ставки (double down): После получения первых двух карт суммарное значение которых равно 11, игрок может удвоить свою ставку. Однако после удвоения ставки игрок может получить только одну дополнительную карту.

  4. Пять карт (five-card trick): Если у игрока или дилера больше пяти карт, но они не превышают 21 очко.

  5. Стрит (straight flush): У игрока три карты одного цвета с значениями 6, 7 и 8 соответственно.

На самом деле, вы можете использовать своё воображение для создания своих собственных особых случаев. Например, если у дилера BlackJack, а у игрока пять карт, это может быть ничья и т.д.

Не реализованные

  1. Отказ (surrender): Отказ возможен только после получения первых двух карт, в других случаях невозможен. При отказе игрок получает половину своей ставки обратно.

  2. Разделение A (ace split): Это специальный случай разделения карт. Если первые две карты игрока — это A, он может разделить карты, но после разделения сможет получить только одну карту.

  3. Выход

Слои

Вы не найдете здесь никаких слоёв MVC. Это потому, что наша задача состоит в реализации самого центрального бизнес-логического в игре в 21 очко. Так что использование слоёв MVC не подходит. Также, пока нет информации о долговременном хранении данных, поскольку это задача клиента, а также у меня нет времени для реализации такого функционала.

Blackjack-core основной пакет

Это ядро. Цель реализации — сосредоточиться на бизнес-логике игры в 21 очко внутри этого ядра.

Что оно может делать:

  • Технические возможности:

    1. Предоставляет удобные и простые интерфейсы.
    2. Дозволяет настраивать ваши особые случаи и коэффициенты выплат в реальном времени без перезапуска сервера.
    3. Поддерживает замену алгоритма перемешивания колоды.
  • Бизнес-возможности:

    1. Дилер может быть как человек, так и машина.
    2. Поддерживает возможность делать несколько ставок одним игроком.
    3. Не ограничено размером ставки или лимитом ставок, поскольку считаю, что это не является ключевой частью игры в 21 очко.
    4. Можно установить коэффициент выплат для страховки.

Что оно не может делать:

  1. Не поддерживает долговременное хранение данных: данные игроков и денег находятся в оперативной памяти.
  2. Не предоставляет графический интерфейс пользователя.
  3. Не предлагает эффект показа карт по одной за раз. Это считается не ключевой частью бизнес-логики.

Blackjack-server серверный компонент

Пока нет времени для реализации.

О долговременном хранении данных

На данный момент работа над долговременным хранением данных практически отсутствует, поскольку у меня нет опыта работы с играми и есть вопросы, которые я не могу ответить.

  1. Необходимо ли хранить данные процесса игры? Процесс игры включает запись каждой карты, которую получил игрок, и каждого действия. Этот процесс, на мой взгляд, либо совершенно не нужен, либо вызывает проблемы производительности.
  2. Вопросы денежных средств в игре Данные о деньгах, конечно, должны быть сохранены. Но дизайн таких систем требует высокого уровня знаний, которыми я пока не располагаю. Мне придётся учиться на примерах других систем.

Проблемы для решения

  1. Как реализовать автоматическое тестирование? То есть тестирование без использования mock объектов. Игроки будут играть автоматически.

  2. Без автоматического тестирования невозможно протестировать ситуацию с конкурентным доступом.

  3. Вопрос безопасности при использовании скриптов на 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 - *******Конец

Приложение

  1. Конфигурация правил страхования

    • Файл конфигурации находится в пути классов: Insurance.groovy

    • Пример

        // При выигрыше игрока он получает выигрыш
        winMoney = playerFirstBet * 2
  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
        }
  3. Конфигурация типов карт

    • Это конфигурация для определения типа карт игрока на основе его карт

    • Файл конфигурации находится в пути классов: 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]
        }
  4. Переменные, доступные в конфигурационных скриптах:

      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 )

Вы можете оставить комментарий после Вход в систему

Введение

Дизайн и реализация игры с 21 очком. Развернуть Свернуть
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://gitlife.ru/oschina-mirror/zacker330-BlackjackGame.git
git@gitlife.ru:oschina-mirror/zacker330-BlackjackGame.git
oschina-mirror
zacker330-BlackjackGame
zacker330-BlackjackGame
master