Система программирования Turbo Pascal

           

Правила нима просты. Игроки по



Фишки, расположенные для игры в ним по схеме 3-4-5



Правила нима просты. Игроки по очереди забирают одну или несколько фишек из любого ряда. Не разрешается за один ход брать фишки из нескольких рядов. Выигрывает тот, кто возьмет последнюю фишку (фишки).

Если Вы сыграете несколько партий в ним, то скоро заметите, что существует некоторая оптимальная последовательность ходов, которая гарантирует победу, если только Вы начинаете игру и первым ходом берете две фишки из первого ряда. Любой другой ход даст шанс Вашему сопернику, который в этом случае наверняка победит, если, в свою очередь, воспользуется оптимальной стратегией.

Полный анализ игры с обобщением на любое число рядов с любым числом фишек в каждом ряду впервые опубликовал в 1901 г. профессор математики из Гарвардского университета Чарльз Л.Бутон, который и назвал игру «ним» от устаревшей формы английских глаголов «стянуть», «украсть». Открытая им оптимальная стратегия основана на двоичной системе счисления и довольно проста. Каждую комбинацию фишек Бутон назвал либо опасной, либо безопасной: если позиция, создавшаяся после очередного хода игрока, гарантирует ему победу, она называется безопасной, если такой гарантии нет - опасной. Бутон строго доказал, что любую опасную позицию всегда можно превратить в безопасную нужным ходом. Наоборот, если перед очередным ходом игрока уже сложилась безопасная позиция, то любой его ход превращает позицию в опасную. Таким образом, оптимальная стратегия состоит в том, чтобы каждым ходом опасную позицию превращать в безопасную и заставлять противника «портить» ее. Использование оптимальной стратегии гарантирует победу игроку только тогда, когда он открывает партию и начальная позиция фишек опасна или он делает второй ход, а начальная позиция безопасна.

Чтобы определить, опасна позиция или безопасна, нужно количество фишек в каждом ряду записать в двоичной системе счисления. Если сумма чисел в каждом столбце (разряде) равна нулю или четна, позиция безопасна. Если же сумма нечетна хотя бы в одном разряде, то позиция опасна. Например, для начальной позиции по схеме 3-4-5 получим:



Десятичная запись количества фишек Двоичная запись количества фишек
3 011
4 100
5 101
Сумма по разрядам 212

Сумма цифр в среднем столбце равна 1 - нечетному числу, что свидетельствует об опасности этой позиции. Поэтому первый игрок может сделать ее безопасной для себя, если он возьмет две фишки из первого ряда. В результате в первом ряду остается только 1 фишка (двоичное число также 1), и сумма чисел в среднем столбце изменится на ноль.

В привычной нам десятичной системе счисления емкость каждого разряда равна 10, а для записи значений разряда используются цифры от 0 до 9. В двоичной системе счисления емкость каждого разряда равна 2, а из всех цифр используются только 0 и 1. В этой системе число записывается в виде суммы степеней двойки и при переходе от одного разряда к соседнему левому вес разряда увеличивается в 2 раза. Если нужно записать число 2 в двоичной системе, следует действовать точно так же, как при записи числа 10 в десятичной системе: записать ноль в первом (младшем) разряде и единицу - слева от него, т.е. 10 в двоичной системе означает 2 в десятичной системе. Точно так же 100 в двоичной системе означает 4 в десятичной, 1000 - 8 и т.д.

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

Делимое Результат Остаток
11 5 1
1 2 1
 2  1  0
Если, начиная с последнего результата, остатки от деления записать в обратном порядке, получим 1011 - это и есть двоичное представление десятичного числа 11. В этом легко убедиться, записав двоичное число 1011 как сумму степеней 2:

1х23+1х22+1х21+1 = 11

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

Представим себе на минутку, что Вы уже создали программу и начинаете работу с ней. Как организовать удобное взаимодействие с программой? Конечно, возможно простейшее решение: Вы предварительно раскладываете на столе монетки, по запросу программы вводите в нее Ваш ход, затем читаете на экране ход программы, делаете нужные изменения в раскладке монет и т.д. Вряд ли Вас удовлетворит такая программа. Гораздо эффектнее имитировать на экране игровое поле с фишками и своеобразное табло игры, в котором сообщается об очередных ходах противников. Однако использованные ранее средства вывода данных (процедуры WRITE и WRITELN) недостаточны для этих целей, ведь с их помощью нельзя указать конкретное место на экране, куда нужно поместить выводимую информацию. Вывод этими процедурами всегда начинается с той позиции на экране, которую в данный момент занимает курсор. Следовательно, для вывода текста в нужное место экрана требуется перед обращением к этим процедурам изменить положение курсора. Для этих целей служит процедура GOTOXY, которая хотя и является стандартной, но располагается в отдельной библиотеке (модуле) с именем CRT. Подробнее о модулях мы поговорим в гл.9, а сейчас просто учтем, что процедуры и функции из дополнительных библиотек становятся доступны в программе, если в самом ее начале объявить об их использовании. Так, указание об использовании библиотеки CRT делается таким образом:

Uses CRT;

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

Теперь попробуем составить алгоритм главной программы. В простейшем виде он таков:

Uses CRT; {Подключение библиотеки дополнительных процедур и функций для управления экраном} 

var

exit: Boolean; {Признак окончания работы} 

begin

{Подготовить экран к работе}

repeat

{Ввести, проконтролировать и отобразить ход игрока}

{Найти и отобразить ход программы}

until exit 

end.

В этом алгоритме выделяются три главных действия и организуется цикл, который будет выполняться до тех пор, пока где-то в программе переменной EXIT (выход) не будет присвоено значение TRUE.

Вначале экран подготавливается к работе: формируется игровое поле с фишками и выводится информация о правилах игры. Как уже говорилось, ним позволяет играть с произвольным количеством фишек. Разумно ввести в программу возможность, которая бы позволила пользователю самому указывать число рядов и количество фишек в рядах, т.е. настраивать программу на нужную раскладку фишек. Можно несколько модифицировать главную программу, чтобы предусмотреть эту возможность:

Uses CRT; {Подключение библиотеки дополнительных процедур и функций для управления экраном}

var

exit : Boolean; {Признак окончания работы} 

change : Boolean; {Признак изменения условий игры}

{----------------------}

Procedure Prepare; {Готовит экран к игре} 

begin {Prepare} 

end; {Prepare}

{----------------------}

Procedure GetPlayerMove;

{Получает, контролирует и отображает ход игрока}  

begin {GetPlayerMove} 

end; {GetPlayerMove}

{----------------------}

Procedure SetOwnerMove;

{Находит и отображает очередной ход программы}  

begin {SetOwnerMove} 

end; {SetOwnerMove}

{----------------------}

begin {Главная программа} 

{Подготовить начальную расстановку фишек}  

repeat {Цикл изменения условий игры} 

Prepare; {Подготовить экран} 

repeat {Игровой цикл}

GetPlayerMove; {Получить ход пользователя} 

if not (exit or change) then

SetOwnerMove {Определить собственный ход} until exit or change 

until exit 

end.

В этом варианте главная программа содержит два вложенных друг в друга цикла Repeat. . .Until: внутренний цикл управляет игрой, внешний отвечает за изменение условий игры. Оба цикла управляются двумя логическими переменными, которые являются глобальными для трех основных процедур PREPARE, GETPLAYERMOVE, SETOWNERMOVE и, следовательно, могут изменяться внутри этих процедур.

Теперь настал момент подумать о том, каким способом в программе будет храниться и использоваться информация о текущем состоянии игры. Судя по всему, нам понадобятся хотя бы две переменные: в одной, назовем ее NROW, будет содержаться число рядов фишек, в другой (NCOL) - количество фишек в каждом ряду. Переменная NROW содержит одно целое положительное число, поэтому ее тип должен быть INTEGER. В переменной NCOL должно быть не менее NROW целых чисел, т.е. ее тип - это массив целых чисел. Поскольку в программе предусмотрена возможность изменения условий игры самим игроком, переменная NROW может меняться от партии к партии. В соответствии с этим должна была бы меняться и длина массива NCOL. Однако в Турбо Паскале нельзя использовать массивы, длина которых меняется динамически, т.е. в процессе работы программы. Эта длина должна определяться статически (на этапе компиляции) и не может меняться в работающей программе. Значит, понадобится массив достаточно большой длины, чтобы его хватило на все случаи. На экране одновременно можно отобразить максимум 25 строк по 80 символов в каждой строке. Однако использовать все строчки экрана как возможные ряды фишек вряд ли целесообразно: во-первых, сама игра при большом количестве рядов становится неинтересной, так как игрок не сможет проанализировать в уме все варианты ходов; во-вторых, на экране не останется места для вывода другой информации. Будем считать, что максимальное количество рядов фишек не должно превышать 14. Укажем это константой MAXROW - теперь, если Вы захотите назначить другое максимальное количество рядов, понадобится изменить значение этой константы и перекомпилировать программу. Именно таким способом программам придается дополнительная гибкость: Вы сосредоточиваете в нескольких константах параметры, которые выбраны Вами произвольно и которые Вы или кто-то другой, возможно, захочет изменить. Все размерности массивов или другие особенности программной реализации следует определять через эти константы, тогда процедура переделки программы предельно упростится.

С учетом сказанного назначим следующие глобальные константы и переменные:

const

MAXROW = 14; {Максимальное количество рядов} 

MAXCOL = 20; {Максимальное количество фишек в ряду}

type

ColType= array [I..MAXROW] of Integer;

var

exit :Boolean; {Признак окончания работы}

change:Boolean; {Признак изменения условий игры}

nrow :Integer; {Количество рядов}

ncol :ColType; {Максимальное колич-во фишек по рядам}

col :ColType; {Текущее количество фишек по рядам}

Константа MAXCOL не участвует в формировании массивов, она будет использоваться для контроля горизонтальных размеров игрового поля. Поэтому она, а также пять переменных сделаны глобальными. Если считать, что начальная раскладка фишек соответствует схеме 3-4-5, то можно написать такой окончательный вариант главной программы:

Uses CRT; {Подключение библиотеки дополнительных процедур и функций для управления экраном}

const  
MAXROW = 14; {Максимальное количество рядов}
MAXCOL =20; {Максимальное количество фишек в ряду}
type  
ColType = array [1.. MAXROW] of Integer;
var  
exit : Boolean; {Признак окончания работы}
change : Boolean; {Признак изменения условий игры}
nrow : Integer; {Количество рядов.}
ncol : ColType; {Максимальное колич-во фишек по рядам}
col : ColType; {Текущее количество фишек по рядам}
{------------------------}

Procedure Prepare; {Готовит экран к игре} 

begin {Prepare} 

end; {Prepare}

{------------------------}

Procedure GetPlayerMove;

{Получает, контролирует и отображает ход игрока}  

begin {GetPlayerMove} 

end ; {Get PlayerMove}

{------------------------}

Procedure SetOwnerMove;

{Находит и отображает очередной ход программы}  

begin {SetOwnerMove} 

end; {SetOwnerMove}

{------------------------}

begin {Главная программа}

nrow := 3; {Готовим игру... } 

ncol [1]:= 3; { на поле из трех } 

ncol [2]:= 4; { рядов фишек } 

ncol [3]:= 5; { по схеме 3-4-5.} 

repeat {Цикл изменения условий игры} 

Prepare; {Подготовить экран}

repeat {Игровой цикл}

GetPlayerMove; {Получить ход пользователя}

if not (exit or change) then

SetOwnerMove {Определить собственный ход} 

until exit or change 

until exit 

end.

Приступим к конструированию процедуры PREPARE. В ходе ее работы формируется значение переменной COL, соответствующее начальной раскладке фишек, и выводится информация о правилах игры. Чтобы было понятнее дальнейшее описание программной реализации, на рис. 2.4 показан вид экрана в начальном состоянии игры.

Процедура начинает свою работу с очистки экрана от имеющейся на нем информации. Это достигается обращением к стандартной процедуре без параметров CLRSCR. Затем выводятся три строчки с названием игры и кратким описанием ее правил. Кроме того, слева и справа на экране формируются заголовки для двух колонок цифр, в которых затем будут отображаться номер ряда (слева) и текущее количество фишек в ряду (справа). Эта информация поможет игроку сообщить программе свой ход. Для размещения информации на нужных участках экрана используется процедура GOTOXY(X,Y) , с помощью которой курсор перемещается нужным образом. Параметры X и Y этой процедуры задают новые координаты курсора. Начало координат соответствует точке (1,1) и размещается в левом верхнем углу экрана, поэтому горизонтальная координата увеличивается слева направо, а вертикальная - сверху вниз.



Содержание раздела