Здравствуйте.
Продолжаем тему написания простых и забавных программ, используя ПТК «Квинт 7» российской разработки. В предыдущих постах показывалось как можно просто и быстро написать игрушку вроде змейки или сапера. Но не смотря на то, что это две совершенно разные игрушки, с точки зрения программирования на CFC (в результате спора с одним товарищем пришли к мнению, что данный язык все-таки ближе к CFC чем к FBD) это была фактически одна и та же программа с небольшими косметическими изменениями. В этой статье мы с вами рассмотрим новый пример программирования, написав с нуля простенькую игрушку «БлэкДжек». Если Вас заинтересовала эта тема, добро пожаловать под кат.
Постановка задачи
Задача довольно простая — написать с помощью средств ПТК «Квинт 7» игру «Блэкджек». Но в процессе изучения правил игры оказалось, что разновидностей блэкджека огромное количество. Начиная от того сколько колод используется в игре и заканчивая всевозможными опциями вида «страхования от проигрыша», «разделения карт», «удвоения ставки» и т.д. и т.п. Остановимся на самом простом варианте:
- одна колода
- без возможностей удвоения, страхования, разбиения и т.д.
- играет один игрок против казино (в нашем случае против ПТК)
Разумеется есть еще некоторое количество допущений, которые будут пояснены по ходу. Надо сказать, что остальные варианты реализуются предельно просто в течение пяти минут добавлением в исходную программу нескольких дополнительных проверок.
Задача поставлена, теперь мы можем приступать к реализации. И тут, в отличие от предыдущих примеров, мы начнем с графической части.
Делаем графический интерфейс
Первым делом ищем в интернете картинку для фона. Например такую:
Этот фоновый рисунок подходит нам идеально, т.к. на нем уже нарисованы фишки с номиналами для ставок и кнопка раздачи.
Далее нужны карты. Тут возникла самая большая проблема, т.к. сходу не удалось найти рисунки колоды карт, которые бы подходили по размеру и стилю. В результате пришлось вооружиться paint'ом и рисовать самому все карты.
Остается вставить несколько полей с цифрами, кнопки начала и завершения игры, кнопки «взять еще карту» и «стоп», а так же несколько всплывающих рисунков, сообщающих результат игры.
Располагаем это все на нашем фоне и получаем следующую картину:
Выглядит как мешанина, где элементы накладываются друг на друга и ничего не понятно. Но мы же еще не сделали анимацию. Когда программа будет готова, то большая часть элементов будет невидима и появляться лишь по заданному условию.
Кроме того внимательный читатель может заметить явную ошибку, которая будет приводить к некорректной раздаче примерно в 0,00001% случаев. Но для примера я решил оставить все как есть.
Пишем программу
Итак, графика у нас уже готова и самое время ее оживить. Начнем с понимания того, что же должно происходить у нас в программе.
1. Нам нужен генератор случайных карт. Тут воспользуемся старым проверенным способом:
Есть два быстроменяющихся числа, которые «случайны» в момент нажатия игроком кнопки. Делим их друг на друга, отбрасываем целую часть и первые 4 знака после запятой, а весь хвост что останется переводим в шкалу 1-52. Далее ставим проверку, указывающую для кого из игроков была сгенерирована данная карта. Таким образом, используя несколько простых алгоблоков, мы за пару минут получили хороший генератор случайных чисел.
2. Теперь сделаем макрос для набора карт игроками. Тут задача несколько посложнее. И как я показывал в предыдущих постах, проще всего ее решить, разобравшись что же мы все-таки хотим от этого макроса.
Входы:
— Для начала нам нужна команда для добавления карты в набор. Так и назовем ее: «Добавить карту».
— Далее нам нужна сама карта, которую будем добавлять. Создадим соответствующий вход и назовем его «карта».
— Кроме того, если посмотреть на макрос генерации случайного числа, то мы видим, что он просто выдает случайное целое число в диапазоне от 1 до 52. Однако мы договорились делать одноколодный Блэкджек, а следовательно карты не должны повторяться. Таким образом для анализа нужно добавить два входа: «Открытые карты игрока» и «Открытые карты дилера» чтобы мы могли «видеть», какие карты уже вышли из колоды и при выпадении уже открытой карты еще раз генерировать случайное число. И так до тех пор, пока не выпадет одна из ранее не выпавших карт.
— Перед каждой новой раздачей нужно забирать все карты у игроков и складывать их в колоду. Следовательно нужен вход «Сброс», который обнулит все карты игрока и дилера.
— Последний вход «Скрыть карту». Как уже было сказано выше, правил игры в Блэкджек великое множество. Во время начальной раздачи дилер может брать себе только одну карту, показывая ее игроку, а может сразу две, одна из которых открыта, а вторая скрыта и дилер открывает ее только когда ход переходит к нему. С точки зрения вероятности для игрока эти два варианта абсолютно одинаковые. Но чтобы все было совсем «по честному» тут используется второй вариант (дилер сразу берет себе две карты), и для этого нужен признак, позволяющий в начале раздачи скрыть вторую карту дилера.
Выходы:
— «Карты». Данный выход показывает карты игрока и дилера.
— «Еще одна попытка». Если генератор случайных чисел выкинул карту, которая уже была открыта, то формируется этот признак, позволяющий еще раз сгенерировать случайное число.
— «Карты обработка». Как уже было сказано выше, дилер сразу берет себе две карты, одну из которых скрывает (в нашем случае это код 54). Однако реально карта из колоды уже взята и игрок не может взять себе такую же карту. А следовательно первый выход «Карты» мы будем использовать для отображения в графическом интерфейсе, где вторая карта у дилера заменена кодом 54. А выход «Карты обработка» — для анализа уже выпавших карт и подсчета очков.
В результате получается вот такой макрос:
Макрос предельно простой. Берем вход «Карта» и сравниваем со всеми картами, которые уже на руках у игроков. Если она новая (на всех сравнениях выполняется проверка <>) то на алгоблоке «24. Слож1» добавляем ее порядковый номер и записываем в соответствующую ячейку памяти. Если проверка на уникальность не выполнилась, то взводим триггер «21.RS1» и начинаем опять генерировать карты, до тех пор пока проверка не выполнится. Командой «сброс» обнуляем все ячейки памяти и порядковый номер сдаваемой карты. Командой «Скрыть карту» подменяем вторую карту дилера кодом 54 (отображается в графическом интерфейсе как рубашка карты).
3. Теперь когда карты розданы, самое время подсчитать очки. Для этого создадим макрос «Подсчет очков» со следующими входами и выходами.
Входы:
— «Карты». Собственно сам набор карт, на основе которых мы должны подсчитать количество очков.
— «Скрыть карту». Закрытая карта дилера подменяется нулем очков.
Выходы:
— «Очки». Количество набранных очков игроком и дилером. Отображается в графическом интерфейсе.
— «Очки обработка». Количество набранных очков вместе со скрытой картой дилера.
— «Перебор». Логический признак, что игрок набрал больше 21 очка.
В результате получается данный макрос:
Ничего сложного нет. На вход подается карта. Далее вложенный макрос преобразует карту в очки. Очки суммируются и проверяются на превышение порога в 21. Если есть превышение, то с задержкой в 50 мсек выдается признак «перебор». Одновременно по признаку «Скрыть карту» вторая карта дилера, которая закрыта, приравнивается к нулю очков. Вопрос вызывает лишь задержка в 50 мсек. Чтобы прояснить ситуацию рассмотрим вложенный макрос «Обработка карты» который выдает соответствующее карте количество очков.
и подробнее:
У нас есть 52 карты с двойки до туза четырех разных мастей. Они закодированы следующим образом: 1 — двойка бубей, 2 — тройка бубей,… 13 — туз бубей, 14 — двойка пик и т.д. Отсюда и принцип подсчета очков. Код карты делим с остатком на 13. Частное будет равно масти, а остаток — карте. При этом несложно заметить, что код туза всегда будет давать в остатке ноль. Теперь преобразовываем карту в очки. Если остаток от деления лежит в диапазоне от 1 до 9, то очки, которые дает карта соответствуют остатку плюс единица, что реализовано посредством алгоблока «7. Слож1». Карты валет, дама и король дают 10 очков. А вот с тузом все хитрее. По правилам игры туз может давать как 11 очков так и одно очко в случае, если при 11 очках получается перебор. Если посмотреть на алгоблоки 3-5 и 9-12, то видно, что изначально туз (частное от деления кода карты не равно нулю, а остаток равен нулю) приравнивается к 11 очкам, однако если приходит сигнал «перебор», то взводится триггер «10.RS1» и туз приравнивается уже к 1 очку. Т.к. сигнал «перебор» приходит по обратной связи с задержкой в 10 мсек, то для предотвращения преждевременного поражения игрока (а вдруг перебор получился как раз из-за 11-ти очков туза) ставим задержку в 50 мсек чтобы успеть пересчитать очки.
Итоговая программа
Основа нашей программы (макросы, рассмотренные выше) готова. Осталось расставить их на поле и сделать обвязку для управления и отображения результатов.
Краткое пояснение по итоговой программе.
В качестве главного элемента управления используется алгоблок «1.Ручселектор1». По команде с операторской станции он на 10 мсек формирует на соответствующем выходе единицу. В комментариях подписано, какой бит что означает.
— Для начала игры на первом селекторе устанавливается единица на первом выходе. По этой команде взводится триггер «3.RS8», который содержит признак, что идет игра.
— Единица на втором выходе селектора сбрасывает все триггеры, обнуляет все счетчики и запомненные состояния и приводит программу в исходное состояние.
— Единица на третьем выходе селектора запускает раунд. При этом триггер «14.RS5» взводится только если взведен триггер «Идет игра» и одновременно сделана ставка. После взвода этого триггера запускается формирование 4 импульсов, по которым игроку и дилеру раздается по две карты.
— Единица на четвертом выходе формирует импульс, по которому игроку дается еще одна карта.
— Единица на пятом выходе взводит триггер «31.RS10» и блокирует взятие карт игроком. одновременно запускается раздача карт дилеру. Тут сделана одна поправочка. По правилам казино дилер должен останавливаться как только он набрал 17 и более очков независимо от того, сколько очков у игрока (это кстати даже написано на фоновой картинке). Но если игра идет 1 на 1 то данное правило не имеет смысла. Поэтому реализован алгоритм, по которому дилер берет карты до тех пор, пока его очки меньше чем у игрока. Разумеется если у игрока перебор, то дилер вообще не берет карты т.к. раунд на этом заканчивается.
— Единица на шестом выходе сбрасывает триггера раунда и запомненные карты. Таким образом программа готова к новой раздаче.
Далее проверяются условия окончания раунда и если игрок победил, то ему начисляется количество фишек, равное ставке, а у дилера соответственно отнимается столько же фишек. Если игрок проиграл, то его ставка переходит к дилеру. При равенстве каждый остается при своих.
Накопленная сумма фишек (а изначально у игрока и дилера по 1000 фишек) сравнивается с нулем (алгоблоки «98.Сравн8» и «99.Сравн9») и взводится соответствующий триггер победы либо поражения в игре.
Вот собственно и все. Программа готова. Компилируем и загружаем все это в контроллер.
Привязываем нарисованный в самом начале графический интерфейс к сигналам в программе и можно играть.
Краткие выводы
В отличие от предыдущих примеров, когда задача содержала несколько тысяч алгоблоков, в этот раз потребовалось всего 600 штук. Причем, как можно заметить, большая часть алгоблоков находится внутри макросов.
Для реализации этой задачи не потребовалось долгих и нудных изучений справочников или поиска ответов на вопросы в интернете. Все написано с помощью простейших алгоритмов таких как «и», «или», «сложение», «деление», «триггер» и т.п., принцип работы которых понятен и школьнику.
При этом задача, сделанная менее чем за день (на весь проект ушло 3 дня. Не полных конечно. Где то по 2-3 часа в день в сумме. Причем первые два дня я потратил чтобы нарисовать в paint карты, сделать надписи и прочие картинки) получилась не громоздкая и при желании ее легко можно модифицировать. В начале я писал, что блэкджек будет одноколодный. Так вот — за пять минут можно переделать программу под любое количество колод. Достаточно в макросе при проверке карты на уникальность поставить счетчик совпадений. Если колоды 3, то одна и та же карта может выпасть три раза и следовательно признак «еще попытка» нужно взводить, когда карта появилась в четвертый раз.
Так же легко вносятся и другие правки.
Закончим статью фразой о том как «весело и просто писать программы на CFC».
Спасибо всем, дочитавшим до конца. Надеюсь это было интересно.
Автор: OPCSenator