Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Лабораторная работа 1




Else

Repeat

Repeat

Const

End

Else

End

Repeat

Const

End

End

Else

Begin

Var

Const

Vаr

Const

Var

Type

Const

Var

Type

Const

Var

End.

Repeat

Begin

Var

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

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

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

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

until exit

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

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

Uses CRT; {Подключение библиотеки дополнительных

процедур и функций для управления экраном}

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 – теперь, если Вы захотите назначить другое максимальное количество рядов, понадобится изменить значение этой константы и перекомпилировать программу. Именно таким способом программам придается дополнительная гибкость: Вы сосредоточиваете в нескольких константах параметры, которые выбраны Вами произвольно и которые Вы или кто-то другой, возможно, захочет изменить. Все размерности массивов или другие особенности программной реализации следует определять через эти константы, тогда процедура переделки программы предельно упростится.

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

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

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

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

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

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

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

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

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

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

Uses CRT; {Подключение библиотеки дополнительных

процедур и функций для управления экраном}

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

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

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

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

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

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

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

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

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

Procedure Prepare;

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

begin {Prepare}

end; {Prepare}

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

Procedure GetPlayerMove;

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

begin {GetPlayerMove}

end; {GetPlayerMove}

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

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) и размещается в левом верхнем углу экрана, поэтому горизонтальная координата увеличивается слева направо, а вертикальная – сверху вниз.

 

ИГРА НИМ Вы можете взять любое число фишек из любого ряда. Выигрывает тот. кто возьмет последнюю фишку. Номер ряда Кол–вофишек 1 ■ ■ ■ 3 2 ■ ■ ■ ■ 4 3 ■ ■ ■ ■ ■ 5   Введите Ваш ход в формате РЯД КОЛИЧ (например, 2 3 – взять из 2 ряда 3 фишки) или введите 0 0 для выхода из игры; –1 0 для настройки игры Ваш ход:

Рис.2.4. Вид экрана в начале игры ним

 

Procedure Prepare;

{Подготовка данных и экрана к игре}

Header0 = 'И Г Р А НИМ';

Header1 = 'Вы можете взять любое число фишек из любого ряда.';

Header2 = 'Выигрывает тот, кто возьмет последнюю фишку.';

Header3 = 'Номер ряда';

Header4 = 'Количество фишек';

i: Integer;

begin {Prepare}

ClrScr; {Очищаем экран}

{Выводим строки заголовка:}

GotoXY((8O-Length(Header0)) div 2,1);

Write(Header0);

GotoXY((80-Length(Header1)) div 2,2);

Write(Header1);

GotoXY((80-Length(Header2)) div 2,3);

Writeln(Header2);

Write(Header3);

GotoXY(80-Length(Header4),4);

Write(Header4);

{Готовим начальную раскладку:}

for i:= 1 to nrow do

col[i]:= ncol[i]

end; {Prepare}

Для вывода верхних строк строго посередине экрана используется задание горизонтальной координаты курсора для процедуры GotoXY как половины от разницы между полной длиной экрана (80 позиций) и длиной выводимой строки (определяется с помощью функции LENGTH).

В процедуре GetPlayerMove осуществляются ввод, контроль и отображение на экране очередного хода игрока. Предварительно нужно показать игроку текущее состояние игрового поля. Поскольку поле будет обновляться как минимум дважды (после хода игрока и после хода программы), действия, связанные с изображением поля на экране, следует вынести в отдельную процедуру. Назовем ее ShowField и займемся ее реализацией.

Судя по всему, нам понадобится организовать цикл; в ходе цикла для каждого ряда игрового поля будет выведена строка, в левой части которой указывается номер ряда, в правой – текущее количество фишек в нем, а посередине выводятся символы, имитирующие фишки. В принципе, можно выбрать любой символ ПК для обозначения фишки, например, X или О. Я предпочел воспользоваться символом псевдографики с кодом 220: этот символ представляет собой небольшой квадратик и легко ассоциируется с фишкой.

Procedure ShowField;

{ Отображает на экране текущее состояние игрового поля }

FISH = #220; {Символ-указатель фишки}

Х0 = 4; {Левая колонка номеров рядов}

X1 = 72; {Правая колонка количества фишек}

X = 20; {Левый край игрового поля}

i,j: Integer;

begin {ShowField}

for i:= 1 to nrow do

begin

GotoXY(X0,i+4);

Write(i); {Номер ряда}

GotoXY(X1,i+4);

Write(col [i]:2); {Количество фишек в ряду}

for j:= 1 to ncol[i] do {Вывод ряда фишек:}

GotoXY(X+2*j,i+4);

if j[i] then

Write(FISH)

Write ('.')

end; {ShowField}

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

Теперь вернемся к процедуре GETPLAYERMOVE. При вводе любого очередного хода игрок должен задать два целых числа XI и Х2. Первое из них указывает номер ряда, а второе — количество фишек, которые игрок хочет забрать из этого ряда. Программа должна проконтролировать правильность задания этих чисел: XI должно указывать непустой ряд, Х2 не может превышать количество фишек в этом ряду. Кроме того, мы должны условиться о двух особых случаях:

• пользователь больше не хочет играть и дает команду завершить работу программы;

• пользователь хочет изменить условия игры.

Пусть ввод числа Х1=0 означает команду выхода из программы, а Х1= –1 – команду изменения условий игры. Тогда можно написать такой начальный вариант процедуры:

Procedure GetPlayerMove;

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

var

correctly: Boolean; {Признак правильности сделанного хода}

x1,x2: Integer; {Вводимый ход}

begin {GetPlayerMove}

{Показываем начальное состояние игрового поля}

ShowField;

{Сообщаем игроку правила ввода хода}

repeat

{Приглашаем игрока ввести ход}

ReadLn(x1,x2); {Вводим очередной ход}

exit:= x1=0; {Контроль команды выхода}

change:= xl=-l; {Контроль команды изменения}

if not (exit or change) then

{Проверить правильность хода и установить нужное значения переменной CORRECTLY. Если ход правильный, сделать нужные изменения в раскладке фишек и показать поле.}

else

correctly:= true {Случай EXIT или CHANGE}

until correctly;

if change then

{ Изменить условия игры }

end; {GetPlayerMove}

В этом варианте в процедуре GetPlayerMove нет описания процедуры SHOWFIELD. Сделано это не случайно: процедура ShowField может понадобиться также и при реализации процедуры SetOwnerMove, поэтому она должна быть глобальной по отношению и к GetPlayerMove, и к SetOwnerMove, т.е. ее описание должно в тексте программы предшествовать описаниям двух использующих ее процедур.

Действия

{Сообщаем игроку правила ввода хода}

{Пригласить игрока ввести ход}

и

{Проверить правильность хода и установить нужное значения переменной CORRECTLY. Если ход правильный, сделать нужные изменения в раскладке фишек и показать поле.}

не очень сложны в реализации, поэтому их можно осуществить непосредственно в теле процедуры GetPlayerMove. Иное дело – изменение условий игры. Это действие полезно реализовать в отдельной процедуре GETCHANGE. С учетом этого второй вариант процедуры GetPlayerMove примет такой вид:

Procedure GetPlayerMove;

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

TEXT1 = ' Введите Ваш ход в формате РЯД КОЛИЧ ';

TEXT01 = ' (например, 2 3 – взять из 2 ряда 3 фишки) ';

TEXT2 = ' или введите 0 0 для выхода из игры; ';

TEXT02 = ' –1 0 для настройки игры ';

TEXT3 = ' Ваш ход: ';

Y = 20; {Номер строки для вывода сообщений}

var

correctly: Boolean; {Признак правильности сделанного хода}

x1,x2: Integer; {Вводимый ход}

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

Procedure GetChange;

{Устанавливает новую настройку игры (количество рядов и количество фишек в каждом ряду}

begin {GetChange}

end; {GetChange}

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

begin {GetPlayerMove}

ShowField; {Показываем начальное состояние поля}

{Сообщить игроку правила ввода хода:}

GotoXY((80–Length(TEXTl+TEXT01)) div 2,Y);

Write (TEXT1+TEXT01);

GotoXY((80–Length(TEXT2+TEXT02)) div 2,Y+1);

Write(TEXT2+TEXT02);

{Пригласить игрока ввести ход:}

GotoXY(l,Y+2);

Write(ТЕХТЗ); {Выводим приглашение и стираем предыдущий ход}

GotoXY(WhereX-16,Y+2); {Курсор влево на 16 позиций}

readln(x1,х2); {Вводим очередной ход}

exit:= x1= 0; {Контроль команды выхода}

change:= x1= –1; {Контроль команды изменения}

if not (exit or change) then

begin

correctly:= (x1 > 0) and (x1 <= nrow) and

(x2 <= col[x1]) and (x2 > 0);

if correctly then

begin {Ход правильный:}

col[x1]:= col[x1]-x2; {Изменяем раскладку фишек}

ShowField {Показываем поле}

write (#7) {Ход неправильный: дать звуковой сигнал}

else

correctly:= true {Случай EXIT или CHANGE}

until correctly;

if change then

GetChange

end; {GetPlayerMove}

Обратите внимание: константа

ТЕХТЗ = 'Ваш ход: ';

имеет длинный «хвост» из пробелов (их 17), поэтому после вывода этого приглашения курсор возвращается влево на 16 позиций оператором

GotoXY(WhereX–16,Y+2); {курсор влево на 16 позиций}

(функция WHEREX возвращает текущую горизонтальную координату курсора, а функция WHEREY – его вертикальную координату). Сделано это для того, чтобы в случае, если игрок ввел неверный ход и программа повторяет вывод приглашения, пробелы в константе ТЕХТ3 затерли бы строку предыдущего ввода.

Чтобы завершить создание процедуры GETPLAYERMOVE, нужно спроектировать процедуру GETCHANGE, в которой осуществляется изменение условий игры. Я привожу текст этой процедуры без пояснений и приглашаю Вас самостоятельно разобраться в том, как она работает:

Procedure GetChange;

{Устанавливает новую настройку игры (количество рядов и

количество фишек в каждом ряду}

t1 ='Н А С Т Р О Й К А ИГРЫ';

t2 ='(ввод количества рядов и количества '+

'фишек в каждом ряду)';

var

correctly: Boolean;

i: Integer;

begin {GetChange}

ClrScr;

GotoXY((80–Length(t1)) div 2,1);

Write(t1);

GotoXY((80–Length(t2)) div 2,2);

Write (t2);

GotoXY(1,3);

Write('Введите количество рядов (максимум ',

MAXROW,'): ');

GotoXY(WhereX-6,WhereY);

ReadLn(nrow);

correctly:= (nrow <= MAXROW) and (nrow > 1);

if not correctly then

write (#7)

until correctly;

for i:= 1 to nrow do

GotoXY(1,i+3);

Write(' ряд ',i,', количество фишек (максимум ',

MAXCOL,'): ');

GotoXY(WhereX-6,WhereY);

ReadLn(ncol[i]);

correctly:= (ncol[i] <= MAXCOL) and (ncol[i] > 0);

if not correctly then

write (#7)

until correctly

end; {GetChange}

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

• если все ряды пусты, значит предыдущим ходом игрок забрал последнюю фишку и он победил; нужно поздравить его с победой, усложнить игру и предложить сыграть еще раз;

• если есть только один непустой ряд, то очередной ход программы очевиден – забрать все фишки, что означает победу машины: сообщить об этом и предложить сыграть еще раз;

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

Начальный вариант процедуры:

Procedure SetOwnerMove;

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

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

Function CheckField: Integer;

{Проверяет состояния игры. Возвращает 0, если нет ни одной

фишки (победа игрока), 1 – есть один ряд (победа машины)

и – количество непустых рядов в остальных случаях}

begin {CheckField}

end; {CheckField}

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

Procedure PlayerVictory;

{Поздравить игрока с победой и усложнить игру}

begin {PlayerVictory}

end; {PlayerVictory}

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

Procedure OwnVictory;

{Победа машины}

begin {OwnVictory}

end; {OwnVictory}

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

Procedure ChooseMove;

{Выбор очередного хода}

begin {ChooseMove}

end; {ChooseMove}

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

begin {SetOwnerMove}

case CheckField of {Проверяем количество непустых рядов}

0: PlayerVictory; {Все ряды пусты – победа игрока}

1: OwnVictory; {Один непустой ряд – победа машины}

ChooseMove; {Выбираем очередной ход}

end; {case}

end; {SetOwnerMove}

Функция CHECKFIELD и процедуры PLAYERVICTORY и OWNVICTORY достаточно просты и их текст помещается без каких-либо пояснений в окончательный вариант программы (см. прил.5.3). Отмечу лишь, что в случае победы игрока нет смысла повторять партию заново с той же самой раскладкой фишек. Поэтому игра усложняется: в исходную раскладку добавляется еще по одной фишке в каждый ряд.

В процедуре CHOOSEMOVE анализируется позиция и выбирается очередной ход программы. Описание оптимальной стратегии уже приводилось выше. Действия программы заключаются в поиске первого слева (старшего) двоичного разряда, для которого сумма чисел нечетная.. Если такой разряд не обнаружен, то текущая позиция безопасна для игрока, а значит любой ход программы сделает ее опасной. В этом случае для программы не существует оптимального выбора и она лишь убирает одну фишку из любого непустого ряда. Такая тактика означает пассивное ожидание ошибки игрока.

Если обнаружен разряд i с нечетной суммой, программа приступает к реализации оптимальной стратегии и тогда игрок обречен на поражение. Для выбора ряда, из которого следует взять фишки, программа просматривает последовательно все ряды и отыскивает тот ряд j, количество фишек в котором (в двоичном представлении) дает единицу в разряде i.

Значение этого разряда для количества фишек в ряду j заменяется нулем. Затем программа продолжает подсчет суммы для оставшихся младших разрядов. Если в каком-либо из них вновь обнаружена нечетность, значение этого разряда для количества фишек в ряду j инвертируется, т.е. 0 заменяется на 1, а 1 на 0. Например, если двоичные представления числа фишек и четности сумм таковы:

число фишек в ряду j: 01001

четность сумм: 01011

(единицей указаны разряды с нечетными суммами), то в результате этой операции получим:

число фишек в ряду j: 00010

четность сумм: 00000

Таким образом, в исходном состоянии в ряду j было 1001 = 9 фишек, безопасная позиция требует, чтобы в ряду осталось 0010 = 2 фишки, следовательно, из него нужно забрать 9 – 2 = 7 фишек.

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

В программной реализации алгоритма широко используется то обстоятельство, что Ваш компьютер, как и все остальные вычислительные машины, работает с числами, представленными в двоичной системе счисления. Поэтому для получения двоичного представления числа в процедуре BITFORM оно проверяется на четность с помощью стандартной функции ODD, затем сдвигается вправо на один двоичный разряд (операция SHR), вновь осуществляется проверка на четность и т.д. до тех пор, пока не будут проверены все разряды. Максимальное число двоичных разрядов, достаточное для двоичного представления количества фишек в ряду MAXCOL = 63, задается константой BIT = 6.

Для получения суммы двоичных разрядов в процедуре CHOOSEMOVE используется суммирование разрядов по модулю 2 с помощью операции XOR. Такое суммирование дает 0, если количество единиц четное или равно нулю, и 1 – если нечетное. В этой же процедуре для инверсии двоичного разряда применяется оператор

if nbit[i] = 1 then

ncbit[j,i]:= ord(ncbit[j,i]=0); {Инверсия разрядов},

в котором используется соглашение о внутреннем представлении логических величин в Турбо Паскале: 0 соответствует FALSE, а 1 – TRUE.




Поделиться с друзьями:


Дата добавления: 2014-11-06; Просмотров: 336; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.227 сек.