Справочник Жаркова по проектированию и программированию искусственного интеллекта. Том 10: Программирование на Visual C# искусственного интеллекта. Издание 3. Продолжение 1
000
ОтложитьЧитал
Введение
Это первый и единственный в мире “Справочник Жаркова по проектированию и программированию искусственного интеллекта” из нескольких томов по методологии разработки искусственного интеллекта в двухмерных и трёхмерных играх и приложениях со звуковым сопровождением для настольных компьютеров, ноутбуков, планшетов и смартфонов на основе самого популярного, совершенного и перспективного языка программирования высокого уровня Visual C# самой мощной в мире в области программирования корпорации Microsoft (США).
Искусственный интеллект (ИИ, artificial intelligence, AI) – это интеллектуальная компьютерная программа, способная разумно выполнять функции, задачи и действия, обычно характерные для разумных существ и свойственные человеческому интеллекту. ИИ в игре или приложении, например, в игре между Компьютером и Человеком, умеет не только проигрывать, но и выигрывать у хорошего Игрока-человека с визуализацией результатов выигрыша. Хорошо известно, что компьютер с ИИ обыгрывает в шахматы любого гроссмейстера. Компьютер с ИИ также легко обыгрывает многих хороших игроков в карты. Если программа в виде ИИ размещена, например, в роботе или другом устройстве, то после того, как ИИ решил заданную ему задачу, ИИ выдаёт команду на выполнение устройством определённого действия. При программировании ИИ важно правильно подобрать среду разработки ИИ и язык программирования.
Среда разработки (иначе, платформа, студия) Visual Studio (или коротко VS) для визуального объектно-ориентированного проектирования приложений даёт уникальную возможность быстро разрабатывать высокотехнологичные и наукоёмкие программные продукты с использованием ИИ, а также компьютерные игры с двухмерной и трёхмерной графикой также с использованием ИИ. Важно отметить, что на основе VS мы программируем не закрытые “чёрные ящики”, как это делают другие известные компьютерные фирмы, а мы создаём открытые любому пользователю (для постоянного дополнения и совершенствования) программы на базе самых мощных в мире алгоритмических языков высокого уровня Visual C#, Visual Basic и Visual C++. В данном чрезвычайно насыщенном томе (заменяющей несколько других книг) мы последовательно и всесторонне, идя от простого к сложному, излагаем методологию программирования ИИ в играх и приложениях с использований двухмерных и трёхмерных изображений.
Наша основная цель – дать читателю ту информацию, которую он больше нигде не найдёт. Поэтому мы не будем дублировать известные книги по языку программирования Visual C# и давать подробные объяснения по теории языка VC#. Если у читателя возникнуть вопросы, он легко отыщет книгу по данному языку (некоторые полезные по данной тематике книги и журналы с сайта ZharkovPress.ru приведены в нашем списке литературы) и там найдёт ответ, так как терминология по всем тематикам у нас общая. Мы будем давать лишь краткие пояснения в виде комментариев в программах, чтобы начинающий пользователь постепенно осваивал базовые дисциплины программирования ИИ на языке VC#, по возможности не используя другие книги; опытному пользователю также будут полезны эти пояснения, поскольку книги по разработке ИИ на основе новых версий языка Visual C# в мире ещё не изданы. К достоинствам нашей книги, рассчитанной на широкий круг новичков и опытных специалистов, мы относим практическую направленность, простоту изложения (без описания сложных теорий, но давая ссылки на книги, в которых эти сложные теории можно изучить), наличие подробных методик и пошаговых инструкций, большое количество примеров и иллюстраций. Теперь читателям может не потребоваться изучать сложные теоретические книги, посещать длительные и дорогостоящие учебные курсы и покупать много отдельных книг. Автор это сделал за них. Читателю необходимо лишь открыть данную книгу в интересующем его разделе (мало кто будет изучать книгу от корки до корки, хотя это и желательно) и по аналогии с разделом (по принципу: делай, как я) самостоятельно программировать ИИ в практическом приложении или игре с использованием VS. И именно при проектировании ИИ в своём конкретном и профессионально интересном приложении или игре (а не отвлечённых примеров в других книгах) читатель будет изучать базовые дисциплины по данной тематике. Создавая ИИ в своём приложении или игре по методике данной и других наших книг и журналов из списка литературы и с сайта ZharkovPress.ru (а также используя справку Help из Visual Studio, как правило, заменяющей все учебники по всем языкам), читатель сможет в одиночку работать за конструктора, технолога, математика и программиста одновременно (при разработке практических приложений) или за сценариста, режиссёра, оператора, дизайнера, художника, музыкального редактора и программиста одновременно (при разработке игр) и сэкономить недели упорного труда. Если в начальных главах серии книг инструкции по разработке ИИ в играх и приложениях на базе VS подробны (в интересах новичков), то инструкции в каждой последующей главе мы даём всё короче и короче, чтобы не повторяться и экономить место в книге.
Приводим краткое содержание данного тома справочника. Введение. Часть I. Методология программирования искусственного интеллекта в карточных играх. Глава 1. Методика разработки искусственного интеллекта в карточных играх на примере игры в 21 очко. Часть II. Методология программирования искусственного интеллекта в спортивных играх с ракетками и мячами. Глава 2. Методика программирования искусственного интеллекта в игре в теннис для игрока с компьютером или двух игроков. Глава 3. Вариант программирования искусственного интеллекта в игре в теннис для игрока с компьютером. Глава 4. Методика программирования искусственного интеллекта в игре Minjie по зеркальному размещению шашек одного игрока по отношению к шашкам другого игрока. Часть III. Методология программирования искусственного интеллекта в играх и приложениях, когда первый объект охотится за вторым, а второй – за третьим. Глава 5. Методика программирования искусственного интеллекта на примере обнаружения одним объектом другого. Глава 6. Методика программирования искусственного интеллекта в играх и приложениях, когда первый объект охотится за вторым, а второй – за третьим. Глава 7. Методика программирования искусственного интеллекта в играх и приложениях, когда один большой объект охотится на одного большого объекта, составленного из нескольких маленьких объектов. Глава 8. Методика программирования искусственного интеллекта в игре боя космического корабля с инопланетными кораблями. Глава 9. Статья: Программирование на C# искусственного интеллекта с несколькими агентами в игре в мини-баскетбол. Часть IV. Развёртывание, публикация и распространение разработанной игры или приложения с искусственным интеллектом. Глава 10. Методика распространения игры или приложения с искусственным интеллектом. Заключение. Список литературы.
Многие приложения и игры в книге основаны на программах, или разработанных корпорацией Microsoft, или опубликованных на сайте корпорации Microsof. Поэтому эти программы являются очень мощными и могут быть использованы не только при разработке ИИ в самых разнообразных играх, но и на практике для разработки различных приложений. Структура книги продумана таким образом, чтобы читатели могли создавать на профессиональном уровне (по методологиям и программам из данной и предыдущих наших книг и журналов с сайта ZharkovPress.ru) свои приложения, игры и открытые графические и вычислительные системы с применением двухмерных и трёхмерных изображений и звуковых эффектов, могли вводить разнообразные исходные данные и на выходе приложения или игры получать с использованием ИИ те результаты, которые необходимы именно им и характерны для их профессиональных или непрофессиональных интересов.
Книга предназначена для всех желающих быстро изучить основы проектирования и программирования искусственного интеллекта в разнообразных двухмерных и трёхмерных компьютерных играх и приложениях на базе самого популярного, совершенного и перспективного (в мире программирования) языка высокого уровня Visual C# последних версий для настольных компьютеров, ноутбуков, планшетов и смартфонов, на этих основах сразу же проектировать ИИ в сложных играх и приложениях и применять ИИ на практике или на отдыхе в разнообразных сферах профессиональной и непрофессиональной деятельности. Также адресована начинающим и опытным пользователям, программистам любой квалификации, а также учащимся и слушателям курсов, студентам, аспирантам, учителям, преподавателям и научным работникам.
Отличие данного сокращённого Издания от предыдущего: в главах даны описания игр и приложений, правила игры с использованием ИИ и создание проекта в Visual Studio, но без текстов программ.
В следующем томе автор (Жарков Валерий Алексеевич) продолжит описывать программирование ИИ в следующих играх и приложениях.
Вопросы, замечания и предложения по тематике наших книг и журналов можно направлять по email с сайта: ZharkovPress.ru.
Автор: доктор технических наук Жарков Валерий Алексеевич (г. Москва).
Часть I. Методология программирования искусственного интеллекта в карточных играх
Глава 1. Методика разработки искусственного интеллекта в карточных играх на примере игры в 21 очко
1.1. Общие сведения
Разработаем методологию программирования искусственного интеллекта в карточных играх на примере типичной и широко распространённой игры в “очко” или в “21”, следуя статье с сайта microsoft.com: Rob Miles. Pocket Jack: Writing a Card-Playing Application, но с нашими усовершенствованиями для современной версии Visual Studio.
Карточные игры широко распространены во всем мире, по многим из них официально проводятся спортивные соревнования различных уровней, вплоть до первенства мира. В США, России и других странах некоторые карточные игры также официально признаны как спортивные игры, и по ним также проводятся спортивные соревнования различных уровней, включая первенство мира.
Поэтому имеет смысл разработать ряд классов, методы которых обеспечивали бы управление широким диапазоном карточных игр. Эта глава описывает механизм сдачи карт (из колоды) случайным образом (на основе генератора случайных чисел), показ их на экране, показ их в "руках" для каждого игрока и управления карточной игрой. Мы разработаем полностью функциональную игру, которая в США и других странах называется как “Black Jack”, “21” или pontoon при использовании 52-х карт, а в России и других странах обычно называется как “очко” или “21” при использовании 36-ти карт. В разработанной далее в данной главе игре нетрудно будет вместо 52-х карт добавить в проект 36 карт (в случае необходимости).
Напомним смысл игры. В игре участвуют, как минимум, два игрока: игрок и банкомёт в виде компьютера, сдающего карты. Из колоды карт банкомёт (по-английски называется dealer – дилер) сдаёт по одной карте игроку, который должен набрать количество очков, как можно ближе к 21 или равное 21 (21 – это лучший вариант – очко), но не более 21. Если игрок набрал больше 21, то это обычно называется “перебор”, и игрок считается проигравшим. Если же игрок набрал меньше 21, то он предлагает банкомёту набирать карты себе. Если банкомёт наберёт очков меньше, чем игрок, банкомёт считается проигравшим, а если больше, то победившим. При равном количестве очков в данной игре принимается, что победил тот, кто первым набрал эти очки, а именно, игрок (но можно счёт оставить прежним). Правила игры сформулируем далее.
1.2. Рисование карт на экране
Первая проблема, которую нужно решить, – показ на экране карт, которые два соревнующихся игрока, например, компьютер и игрок, держат в “руках”. Чтобы это сделать, нам нужно: 52 изображения карт в виде файлов формата, например, (.gif), одно изображение фона игры 0.gif, одно изображения банка bank.jpg и одно изображение загрузки loading.gif. Напомним, что формат (.gif) расшифровывается как Graphics Interchange Format (Формат обмена графическими данными). Все изображения в уменьшенном масштабе показаны на рис. 1.1, а в увеличенном масштабе – на рис. 1.2 – 1.5. Видно, что файлы карт имеют имена 1, 2, 3,…,52 и содержат каждую последовательность из 13 карт в четырёх мастях (последовательно сверху вниз: трефы – club, бубны – diamond, черви – heart, пики – spade). Файл с именем “0” – фон игры. Эти изображения должны быть добавлены к проекту и использованы для вывода карт на экран. Каждое изображение карты имеет приблизительно только 1 Кбайт объёма, потому что содержит только четыре цвета, чтобы не использовать много памяти.
Рис. 1.1. Карты и другие рисунки игры в уменьшенном масштабе.
Рис. 1.2. Первые семь карт в увеличенном масштабе.
Рис. 1.3. Последующие шестнадцать карт в увеличенном масштабе.
На этом рисунке закончились 13 карт 1, 2, 3, …,13 первой масти трефы – club. Первая карта любой масти – Туз (Ace – A).
Рис. 1.4. Последующие шестнадцать карт в увеличенном масштабе.
На этом рисунке закончились 13 карт 14, 15, 16, …, 26 второй масти бубны – diamond и 13 карт 27, 28, 29, …, 39 третьей масти черви – heart. И здесь первая карта масти – Туз (Ace – A).
Ниже показаны 13 карт 40, 41, 42, …, 52 последней четвертой масти пики – spade.
Рис. 1.5. Последние тринадцать карт в увеличенном масштабе.
Значения очков каждой карты следующие: Туз (Ace – A) = 1 или 11; как 1-я, 2-я или 3-я карта – Туз даёт 11 очков; с Валетом, Дамой и Королём, Туз даёт 11 очков и в сумме 10+11=21 эти две карты называются PocketJack, который бьёт карты соперника, даже набравшие 21; как 4-я и последующая карта – Туз даёт 1 очко; цифры на картах от 2 до 9 означают очки этой карты;
карта с числом 10 card with number 10, Валет (Jack – J), Дама (Queen – Q), Король (King – K) = по 10 очков.
1.3. Загрузка в проект изображений карт
Чтобы добавить имеющиеся у нас файлы карт в проект, необходимо сначала добавить в проект папку для этих файлов, затем скопировать в эту папку файлы, а затем свойства этих файлов задать как Embedded Resource, как подробно будет описано далее при создании проекта игры.
Лучший способ загрузить файлы карт в программу при её выполнении – создать массив этих карт в классе Image или Bitmap. После этого изображения могут тогда быть нарисованы на поле игры playfield, когда потребуется. Чтобы загрузить изображения карт в массив, возможны два варианта кода. По первому варианту, код имеет следующий вид:
static private Image[] cardImages = new Bitmap[53];
System.Reflection.Assembly execAssem =
System.Reflection.Assembly.GetExecutingAssembly();
for (int i=0 ; i< 53 ; i++ )
{
cardImages[i] =
new Bitmap(execAssem.GetManifestResourceStream (
@"PocketJack.cardImages."+i+@".gif"));
}
Видно, что в этом варианте проект имеет имя PocketJack, папка с файлами карт имеет имя cardImages, а в массиве cardImages все файлы карт с именами “i” должны иметь расширение (.gif).
По второму варианту, который мы применим далее в программе, код имеет следующий вид:
public Image CardImage
{
get
{
int dispNo = CardNo;
if (!FaceUp)
{
dispNo = 0;
}
if (cardImages[dispNo] == null)
{
cardImages[dispNo] = new Bitmap(
execAssem.GetManifestResourceStream(
@"PocketJack.images." + dispNo + @".gif"));
}
return cardImages[dispNo];
}
}
Видно, что в этом варианте проект имеет имя PocketJack, папка с файлами карт имеет имя images, а в массиве cardImages все файлы карт с именами “dispNo” должны иметь расширение (.gif).
1.4. Рисование изображений карт
Следующий шаг в разработке игры – процесс рисования карт. Изображение каждой карты должно иметь скруглённые углы. Когда карты прорисовываются на фоне игры, карты со скруглёнными углами выглядят более реалистично. Это – маленькая деталь, но существенная, если мы хотим спроектировать хороший пользовательский интерфейс игры. А если мы пристально посмотрим на изображения карт на экране, то можно увидеть, что углы карт нарисованы зелёным цветом, как показано на рис. 1.6.
Рис. 1.6. Углы карт нарисованы зелёным цветом.
При рисовании карт мы должны назначить этот цвет как прозрачный, чтобы был виден фон формы Form1 вокруг углов каждой карты. Мы должны использовать следующий код, чтобы создать объект класса ImageAttributes с целью задания зелёного цвета прозрачным:
static public System.Drawing.Imaging.ImageAttributes
cardAttributes;
static Card()
{
cardAttributes =
new System.Drawing.Imaging.ImageAttributes();
cardAttributes.SetColorKey(Color.Green, Color.Green);
execAssem =
System.Reflection.Assembly.GetExecutingAssembly();
}
В этом коде метод SetColorKey даёт начало и конец диапазона цветов, которые будут расценены как прозрачный. Для среды выполнения .NET Compact Framework эти два цвета должны иметь одно и то же значение, так как только один цвет может быть сделан прозрачным.
Целесообразно также после создания проекта в программе задать фон формы Form1 в свойстве BackColor темно-зелёного цвета (DarkGreen) из структуры Color.
Когда изображение карты нарисовало, метод DrawImage используется следующим образом:
private static Rectangle drawRect;
public void DrawHand(Graphics g, int startx, int starty,
int gapx, int gapy)
{
drawRect.X = startx;
drawRect.Y = starty;
foreach (Card card in this)
{
drawRect.Width = card.CardImage.Width;
drawRect.Height = card.CardImage.Height;
g.DrawImage(
card.CardImage, // Image
drawRect, // destination rectange
0, // srcX
0, // srcY
card.CardImage.Width, // srcWidth
card.CardImage.Height, // srcHeight
GraphicsUnit.Pixel, // srcUnit
Card.cardAttributes); // ImageAttributes
drawRect.X += gapx;
drawRect.Y += gapy;
}
}
Этот код рисует все карты на экране в случайно определённой позиции (при помощи генератора случайных чисел класса Random).
1.5. Класс Card для загрузки
карт в программу
В движке игры CardEngine.cs объект класса Card представляет каждую из карт в игре. Этот класс держит фактическое значение карты и рисует её на экране. Он также обеспечивает свойства, которые дают возможность пользователям класса найти координаты карты, получить название карты и другую полезную информацию. Класс Card может использоваться во многих других карточных играх, но есть некоторые особенности, которые характерны для игры в очко.
Первая версия класса Card выполняла загрузку всех изображений, когда приложение начинало выполняться. Каждая из 52 карт и фон игры загружались в самом начале игры. Это делало приложение замедленным. Способ ускорить процесс загрузки состоит в том, чтобы загружать изображения только по запросу при использовании следующего кода:
static private Image[] cardImages = new Bitmap[53];
public Image CardImage
{
get
{
int dispNo = CardNo;
if (!FaceUp)
{
dispNo = 0;
}
if (cardImages[dispNo] == null)
{
cardImages[dispNo] = new Bitmap(
execAssem.GetManifestResourceStream(
@"PocketJack.images." + dispNo + @".gif"));
}
return cardImages[dispNo];
}
}
Переменная cardImages – массив изображений карт. Первоначально все изображения карт в этом массиве пусты. В этом коде переменная dispNo является индексом массива и именем файла карты. Если данный элемент массива – пустой указатель (null), изображение загружается и затем может быть нарисовано. В следующий раз, когда потребуется изображение данной карты, оно будет найдено немедленно. В результате приложение начинает выполняться намного быстрее, чем если бы все карты были загружены в начале игры; время, потраченное, чтобы загрузить только небольшое количество карт, необходимых для игроков, будет небольшим. Если наши приложения нуждаются в большем количестве изображений карт, то это стоит выполнять постепенно по мере загрузки приложения, вместо того, чтобы выполнить это все сразу же в начале игры.
1.6. Класс CardHand для представления
карт в руках игрока
В движке игры CardEngine.cs мы нуждаемся в контейнерном классе CardHand (Рука игрока или банкомёта с картами), чтобы держать все карты. Законченная игра будет требовать двух объектов этого контейнера: один – для управляемого компьютером дилера и другой – для игрока. Класс CardHand, который мы собираемся использовать, держит множество карт. Это основано на коллекции ArrayList, которая облегчит для пользователей класса CardHand возможность добавлять и перечислять карты в руке. Эта коллекция также содержит метод, который будет рисовать карты в руке, как показано в следующем коде:
public void DrawHand(Graphics g, int startx, int starty,
int gapx, int gapy)
Этот метод, приведённый выше, рисует карты, начиная с определённой позиции на экране. Каждая последующая карта рисуется на определённом расстоянии от предыдущей.
Класс CardHand также содержит следующий метод, который вычисляет счёт набранным картам второго игрока blackjack (компьютера):
public int BlackJackScoreHand()
{
int score = 0;
int aces = 0;
foreach (Card card in this)
{
score += card.BlackJackScore;
if (card.BlackJackScore == 11)
{
aces++;
}
}
while ((score > 21) && (aces > 0))
{
score -= 10;
aces–;
}
return score;
}
Метод работает для каждой карты в руке. Он следит за числом тузов (aces), и если пришел туз, то уменьшает счёт карт с учётом туза, чтобы гарантировать, что счёт – как можно ближе к 21, насколько это возможно без перебора.
1.7. Класс CardShoe для представления
карт в колоде случайным образом и тестирования игры
Заключительный класс, который управляет картами в движке игры CardEngine.cs, – класс CardShoe. Мы используем этот класс, чтобы обеспечить вывод карт случайным образом (при помощи генератора случайных чисел – г.с.ч.). Игорное казино данного приложения имеет специальное устройство, названное shoe (колода) или deck (колода), которое содержит карты. В начале игры карты перетасованы (shuffle) много раз и помещены в колоду. В процессе многократной перетасовки приложение использует г.с.ч. для размещения карт в виде элементов массива. Класс CardShoe содержит этот массив и заполняет его в начале игры. Все карты вводятся в массив от первой до последней, а затем массив перетасовывается снова, и так несколько раз.
Когда мы проектируем любую систему, мы должны также думать, как мы собираемся её проверять (тестировать). Было бы трудным для нас проверить игру, если бы мы должны были запустить игру 50 раз только для того, чтобы удостовериться, что игра работает правильно, когда игрок получает счёт карт, равный 21. Поэтому класс CardShoe снабжён дополнительной особенностью. В дополнение к конструктору этого класса, который позволяет разработчику использовать класс, чтобы выбрать число перетасовок в колоде, имеется ещё перегрузка конструктора, который принимает массив числовых значений типа byte и представляет "расположенную в стеке" колоду. Такая колода не перетасована, и вместо этого располагает карты в специфической заранее предопределённой последовательности. Расположенная в стеке колода даёт возможность разработчику проверить поведение карт в различных ситуациях игры, предоставляя приложению специфическую последовательность значений карт.
Чтобы гарантировать, что расположенная в стеке колода карт не может использоваться в низменных целях (для подтасовок в игре), флажок сообщает пользователю объекта класса CardShoe, действительно ли в данный момент используется расположенная в стеке колода карт. Разработчик, который использует объект этого класса, может проверить этот флажок и гарантировать, что игра не использует расположенную заранее определённым образом в стеке колоду карт.
Проверяя приложение, мы можем создавать сравнительно редкие сочетания карт, типа blackjacks, просто обеспечивая расположенную в стеке колоду, как показано в следующем коде:
public CardShoe(byte[] stackedDeck)
{
decks = stackedDeck;
testShoe = true;
}
Расположенная в стеке колода даст в игре Блэк Джека обоим игрокам. Последовательность карты 1 представляет первую карту первой масти в колоде, которая является тузом. Поскольку каждая масть содержит 13 карт, 14-я карта представляет первую карту второй масти, которая является также тузом. В полной игре между игроком (player) и банкомётом (banker) наличие с начала колоды последовательностей карт 1 и 14 приводит и к игроку, и к банкомёту, первоначально начавшему игру, туза. Последовательности карт 11 и 25 представляют Джек (валета) от первых и вторых мастей соответственно, таким образом, каждый игрок получает Джек (валета) как их вторая карта. В игре и игрок (player), и банкомёт (banker) получают по тузу, комбинация Джека (валета) приходит к обоим игрокам, имеющим Блэк Джеки.
1.8. Схема запуска игры
Когда начинается игра, приложение очищает (Clear) “руку игрока” (player's hand) и добавляет две колоды карт следующим образом:
CardHand playerHand = new CardHand();
playerHand.Clear();
dealerHand.Clear();
// deal the face down hole card
dealerHoleCard = shoe.DealCard();
dealerHoleCard.FaceUp = false;
dealerHand.Add(dealerHoleCard);
// deal the first player card
playerHand.Add(shoe.DealCard());
// deal the second dealer card (face up)
dealerHand.Add(shoe.DealCard());
// deal the second player card
playerHand.Add(shoe.DealCard());
В этом коде, для простоты, не принимается во внимание чередование раздачи карт между игроком и банкомётом. Это будет учтено далее в полной программе игры.
Далее в проекте имеется команда “Hit Me” меню maneMenu1. После выбора этой команды, компьютер выдаёт игроку дополнительную карту, если набранное им количество очков меньше 21, как показано в следующем коде:
void playerHits()
{
if (playerHand.BlackJackScoreHand() < 21)
{
playerHand.Add(shoe.DealCard());
if (playerHand.BlackJackScoreHand() > 21)
{
//We write in the original:
pot.DoPlaceBet();
pot.HouseWins();
showPot();
mode = GameMode.PlayerBust;
}
this.Invalidate();
}
}
Отметим, что метод BlackJackScoreHand каждый раз возвращает счёт “руки игрока” (playerHand). После этого метод Invalidate перерисовывает форму Form1, точнее, перерисовывает карты и обновляет счёт обоих игроков. Аналогично в меню maneMenu1 имеется команда “Себе”, по которой банкомёт набирает карты себе (после игрока).
1.9. Рисование очков игроков
Методы Windows для рисования текстов хороши для простых сообщений, но для игры пользователь ожидает что-то более красивое. Например, мы можем рисовать текст на фоне какого-либо рисунка, чтобы выделить текст. Мы можем осуществить это, неоднократно рисуя текст в множестве позиций вокруг его желательного местоположения, перед размещением реального текста на самом верху фона. Чтобы сделать это, был написан ряд утилит, как показано в следующем коде:
static private SolidBrush messageBrush =
new SolidBrush(Color.Black);
public static void BigText(string message, int x, int y,
Color back, Color fore,
Font messageFont, Graphics g)
{
int i;
messageBrush.Color = back;
for (i = 1; i < 3; i++)
{
g.DrawString(message, messageFont, messageBrush,
x – i, y – i);
g.DrawString(message, messageFont, messageBrush,
x – i, y + i);
g.DrawString(message, messageFont, messageBrush,
x + i, y – i);
g.DrawString(message, messageFont, messageBrush,
x + i, y + i);
}
messageBrush.Color = fore;
g.DrawString(message, messageFont, messageBrush, x, y);
}
Этот метод BigText снабжён ссылкой на объект графики, чтобы использовать её для рисования текста (message) соответствующим шрифтом (messageFont) в соответствующей позиции. Задаётся также цвет для приоритетных и фоновых версий текста. Метод рисует множество фоновых версий текста перед помещением приоритетной версии на вершине. Метод является статическим, поэтому для вызова не нуждается в объекте класса Utilities, а вызывается напрямую после имени класса, как показано в следующем коде:
Utilities.BigText("Dealer Bust",
20, 80, Color.Black, Color.Yellow, messageFont, g);
В этом коде сообщение "Dealer Bust!" означает “Банкомёт перебрал карты”.
Объект messageFont класса Font создан в начале приложения и используется для всего рисунка сообщения.
Далее при разработке программы игры мы сначала стандартно создадим шаблон метода Paint (после двойного щелчка по имени события Paint в панели Properties для формы Form1), затем в тело этого шаблона запишем наш код и будем вызывать этот метод каждый раз, когда экран должен быть перерисован. С точки зрения проектирования, считается не очень хорошей практикой выполнять прикладные функции непосредственно в обработчике события Paint. Поэтому для рисования изображений, наше приложение в шаблоне метода Form1_Paint будет вызывать специальный метод paintForm, как показано в следующем коде:
private void Form1_Paint(object sender, PaintEventArgs e)
{
paintForm(e.Graphics);
}
1.10.
Управление
игрой
Теперь мы можем использовать вышеупомянутые классы, чтобы осуществить большинство видов игры в карты. Рассмотрим, как в целом осуществляется игра, давая возможность сначала игроку (player) сделать первые ходы (набрать карты), а затем – банкомёту (или дилеру – dealer) сделать ответные ходы (набрать свои карты).
Игра в очко может иметь одно из следующих состояний в течение всей игры:
игрок выдаёт карты себе или Компьютеру (the player is making his or her moves);
игрок перебрал карты (the player busted);
игрок выиграл (the player has won);
банкомёт (dealer) осуществляет набор карт (the dealer is making his or her moves.);
банкомёт перебрал карты (the dealer busted);
банкомёт выиграл (the dealer has won);
счёт равный (the score is tied, known as a push).
В каждом из этих состояний рисунок экрана будет различным, как ответ игры на события. Например, запрос банкомёта следующей карты допускается только тогда, когда игрок закончил набор своих карт. Мы можем представить эти состояния или режимы игры (mode) посредством перечисления enum следующим образом:
public enum GameMode
{
LoadingDisplay,
PlacingBets,
PlayerActive,
PlayerWon,
PlayerBust,
PocketJack,
DealerActive,
DealerWon,
DealerBust,
Push
}
Переменная типа GameMode сохраняет состояние игры. Эта переменная управляет перерисовкой экрана. Когда состояние игры изменяется, должен произойти ряд действий. Лучший способ получить это поведение состоит в том, чтобы осуществить управление посредством свойства следующим образом:
GameMode modeValue;
GameMode mode
{
get
{
return modeValue;
}
set
{
switch (value)
{
case GameMode.LoadingDisplay:
BetMinusToolStripMenuItem1.Enabled = false;