Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель игры на Паскале. АВС и немного Турбо

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
{ещё шесть раз}
…………………. then result:=1;
Невесело как-то получается. А как веселее, я не знаю. Если узнаете, расскажите мне. Можно слегка улучшить, если слегка подумать. Нам надо, чтобы каждое из трёх чисел равнялось единице. А чему, в таком случае, равняется их произведение? Единице, конечно! Исходя из этого пишем, сразу и для нолико восьмёрку. Нет, лучш ничья в крестиках-ноликах? Когда некуда больше ходить. То сеть, все клетки заполнены. То есть, все элементы массива не нули. И финальное то есть – их произведение не равно нулю.
procedure Check( a : TArray3x3; var result : integer; var gamover : boolean);
begin
result:=0;
if (a[1,1]*a[1,2]*a[1,3] = 1) or (a[2,1]*a[2,2]*a[2,3] = 1) or (a[3,1]*a[3,2]*a[3,3] = 1) or (a[1,1]*a[2,1]*a[3,1] = 1) or (a[1,2]*a[2,2]*a[3,2] = 1) or (a[1,3]*a[2,3]*a[3,3] = 1) or (a[1,1]*a[2,2]*a[3,3] = 1) or (a[3,1]*a[2,2]*a[1,3] = 1) then begin
end;
(a[2,1]*a[2,2]*a[2,3] = 8) or
е сразу подумать и про третий варианткогда ничья. Что такое
gamover:
result:=1;
gamover:=true;
if (a[1,1]*a[1,2]*a[1,3] = 8) or
=false;
вдля них только единицу меняем на
181
(a[3,1]*a[3,2]*a[3,3] = 8) or (a[1,1]*a[2,1]*a[3,1] = 8) or (a[1,2]*a[2,2]*a[3,2] = 8) or (a[1,3]*a[2,3]*a[3,3] = 8) or (a[1,1]*a[2,2]*a[3,3] = 8) or (a[3,1]*a[2,2]*a[1,3] = 8) then begin result:=2; gamover:=true; end;
if a[1,1]*a[1,2]*a[1,3]* a[2,1]*a[2,2]*a[2,3]* a[3,1]*a[3,2]*a[3,3] <> 0 then begin result:=3; gamover:=true; end; end;
Помните, написав процедуры для рисования крестика и нолика, мы их аккуратно и тщательно (почти) протестировали? Как Вы думаете, какая процедура сложнее, рисования нолика или процедура, только что
писанная? Правильно. Так что настоятельно рекомендую бросить всё и
на написать таки программку для тестирования во
Вражеский интеллект
т этой самой процедуры.
Вот она, наша заготовка:
procedure AI( a : TArray3x3; var xkuda, ykuda : integer);
begin end;
На входе – игровое поле с расставленными уже крестиками и ноликами. На выходе – столбец и строка, куда конкурирующий интеллект считает нужным поставить но
182
лик. Начинаем думать. Какие возникают вопросы?
А откуда эту процедуру вызвать? Мне так кажется, что вызывать её надо в обработчике нажатия на пробел. То есть, ход моей мысли (и нашей программы) такой. Если клетка свободна, ставим крестик. Проверяем на завершение игры. Если завершилась, то завершилась. Если не завершилась, обращаемся к искусственным мозгам, мозги дают ответ, куда ходить. Ходим за ноликов. Опять п И, собственн
if a[y,x]=0 then begin Cr( x,y); a[y,x]:=1; Check( a, result); if result = 0 then begin {игра продолжается} AI( a, xkuda, ykuda); Zero(xkuda,ykuda); a[ykuda,xkuda]:=2; Check( a, result); if (result=1) or (result=2) or (result=3) then begin gamover:=true; end; end; if (result=1) or (result=2) or (result=3) then begin gamover:=true; end; end; end;
Посмотрели? Понравилось? Лично мо оскорблено этими повторяющимися строками со словом оставаться не может. Варианты? Убрать gamover вообще, а главный цикл завершать по соответствующему значению result. Можно, но gamover жалко. Слово уж больно красивое. Да и вообще – использовать для завершения главного
роверяем, чем дело кончилось. Если что, устанавливаем gamover.
о, всё. Программа готова. Почти.
if Ord(ch) = SpaceBar then begin
ё тонкое эстетическое чувство
result. Так
183
цикла программы переменную, которая применяется ещё и в других целях – пошлятина.
Кстати, практически в любой программе есть свой Главный Цикл.
Другое предложение – добавить в процедуру Check третий параметр. Не очень изящно. Можно преобразовать Check в функцию. Мне эта идея не нравится – функция, которая кроме своего значения возвращает ещё и модифицированные параметры – неправильно это как-то. Придётся всё-таки добавлять третий параметр. Заголовок процедуры Check с этого момента выглядит следующим образом:
procedure Check( a : TArray3x3; var result : i
var gamover : boolean);
nteger;
Вот эти строчки переезжают в конец процедуры Check (не забыть проинициализировать gamover в начале процедуры):
if (result=1) or (result=2) or (result=3) then begin gamover:=true;
end;
А в том тексте, что наверху, они просто исчезают:
if sc = SpaceBar then begin if a[y,x]=0 then begin Cr( x,y); a[y,x]:=1; Check( a, result, gamover); if result = 0 then begin {игра продолжается} AI( a, xkuda, ykuda); Zero(xkuda,ykuda); a[ykuda,xkuda]:=2; Check( a, result, gamover); end;
184
end; end;
Эстетическое чувство всё равно неспокойно, но так гораздо лучше. Отмучались!
Теперь другой вопрос. Как думает искусственный интеллект? Давайте думать сами – а как бы мы походили на его, интеллекта, месте. Мысль номер один - если можно выиграть, то нужно выиграть! Если у нас где-то уже есть два нолика в ряд, пристраиваем к ним третий – и вс А если выиграть нельзя? Мысль номер два. Если не можем выиграть сами, не дадим выиграть тов
арищу. Не съем, так понадкусываю! Если где-то стоят два крестика в ряд, ставим туда в качестве третьего наш вредный нолик. И противник уже не выиграл. Хорошо. А если ни того, ни другого – ни двух нолико ни двух крестиков? Мысль третья. Если некуда ходить, походим в какое-нибудь хорошее место. Какое? Есть такое мнение, что лучший ход – в центр. Если центр занят – ходим в среднюю боковую клетку, их много. Если и они все заняты, ходим в угол. Уго определилась бы ничья и мы бы в это место программы вообще не попали. Суммируем словами, но вкратце:
Ищем ход, которым могут выиграть нолики. Если нашли – ходим. Если не нашли:
Ищем ход, которым могут выиграть крестики. Если нашлиходим. Если не нашли: Ходим в центр. Ес Ходим в боковую клетку. Если нельзя: Ходим в угловую клетку.
Всё
хорошо. Осталось уточнить мелкие детали. Что значит – “Ищем ход, которым могут выиграть нолики”? Как и обычно, я предлагаю самый тупой вариант. Попробовать поставить в каждую свободную клетку нолик, и, если мы выигрываем – ну, значит мы выигрываем… Кстати, процедура, проверяю
л наверняка свободен – если бы всё был
ли нельзя:
щая, не выиграли ли мы, у
о занято,
ё!
в,
185
нас уже есть. Называется Check, если кто забыл. Если ответ положительный – запоминаем, куда ходить и возвращаем в качестве результата. Вот только не надо использовать в процессе переменную gamover в качестве одноимённого параметра. Gamover – это святое, его портить нельзя. Хотя, конечно, тождественность имён так и тянет вписать его в вызов процедуры. Тем не менее, об специа Нюанс – если мы что-то нашли, дальше искать уже не надо. Напрашивается логическая переменная, отвечающая за это дело – непродолжение, в смысле. Ещё уточняем - цикл три на три, пытаемся в каждую свободную клетку поставить нолик, вызываем Check, смотрим на результат. Если всё, то всё, в смысле – ходи
procedure AI( a : TArray3x3; var xkuda, ykuda : integer); var
nashli : boolean; tmpGamover : boolean; i,j : integer;
begin
nashli:=false;
for i:=1 to 3 do begin for j:=1 to 3 do begin if a[i,j] = 0 then begin b:=a; b[i,j]:=2; Check( b, result, tmpGamover); if result = 2 then begin xkuda:=j; ykuda:=i; nashli:=true; end; end; end;
льный, временный gamover.
м туда, иначе – продолжае
b : TArray3x3;
м разговор.
ъявим для этих целей
186
end;
if not nashli then begin {продолжаем разговор} end;
Обратите внимание, я взял на себя инициативу и ввёл вспомогательный массив B. А что было бы, если не? Попробуйте, хотя бы на абсолютно пустом массиве A. В процессе экспериментов он очень быстро замусорился бы двойками (ноликами) и мы получали бы совершенно неуместные радостные сообщен нужен массив B, который не жалко испо эксперимента – всё равно на следующем шаге мы вернём его а исходное состояние.
Ещё обратите внимание на оператор присваивания B:=A. Да, такое возможно, но только при условии, что массивы объявлены абсолютно идентично. И когда я говорю “абсолютно иден имею в виду, и даже хуже. Нельзя объяви B как array[1..3,1..3] of integer. С точки зрения Паскаля (стандартного Паскаля), это разные объявления. Идентичные – это когда объявляются одним словом – string, integer, real, TArray3x3.
Теперь быстро изготавливаем продолжение – тот случай, когда мы можем помешать крестикам выиграть. Почти то же самое.
if not nashli then begin for i:=1 t for j:=1 to 3 if a[i,j] = 0 then begin
b:=a; b[i,j]:=1; Check( b, result, tmpGamover); if result = 1 then begin xkuda:=j; ykuda:=i; nashli:=true;
o 3 do begin
do begin
ия о победе. Для избежания этого и
ртить для очередного
тично”, именно это я и
ть в двух строчках массивы A и
187
end; end; end; end; end;
Вроде бы, всё понятно и дополнительных комментариев не требует. Осталось собраться с силами и дописать вариант про – куда ходить, когда некуда ходить. Предлагаю просто и без затей. Вопросы, конечно, возникнут. Ознакомьтесь с текстом, затем ответы на вопросы.
if not nashli then begin if a[2,2] = 0 then begin xkuda: ykuda:=2; nashli:=true; end; if not nashli then begin if xkuda:=3; ykuda:=2; nashli:=true; end; if a[2,1] = 0 then begin xkuda:=1; ykuda:=2; nashli:=true; end; if a[1,2] = 0 then begin xkuda:=2; ykuda:=1; nashli:=true; end; if a[3,2] = 0 then begin xkuda:=2; ykuda:=3; nashli:=true;
=2;
a[2,3] = 0 then begin
188
end; end; if not nashli then begin if a[1,1] = 0 then begin xkuda:=1; ykuda:=1; nashli:=true; end; if a[3,3] = 0 then begin xkuda:=3; ykuda:=3; nashli:=true; end; if a[3,1] = 0 then begin xkuda:=1; ykuda:=3; nashli:=true; end; if a[1,3] = 0 then begin xkuda:=3; ykuda:=1; nashli:=true; end; end; end;
А теперь – возникшие, буквально из ниоткуда, вопросы и сомнения. Попытаюсь телепатически угадать и не телепатически ответить.
А нельзя это сделать короч скажите мне.
Я не гордый, всегда готов научиться новому фокусу
е? Можно, только я не знаю как. Если знаете,
У себя на работе, сотрудников, которые учатся фокусам, я стараюсь сгноить и изничтожить.
А зачем в последних четырёх условных операторах мы усердно пишем nashli:=true, ведь nashli нам уже больше не понадобится? Во-первых, чтоб не думать. Во-вторых, для симметрии.
189
В-третьих, сегодня не понадобится, а завтра кто его знает. Случаи, как известно, разные бывают. Если кто-то думает, что я шучу, то он глубоко заблуждается. Никакого юмора, только суровые будни профессионального программиста.
А зачем мы, найдя свободной, например, первую же угловую клетку, ищем дальше, проверяя остальные три? Вопрос серьёзный. В некоторы случаях (не в нашем) это означа свободны несколько угловых клеток, мы всегда вернём последнюю, а не первую. В некоторых случаях (опять-таки, не в нашем) это важно. Так зачем? Во-первых – работает и ладно. Во-вторых – а как? Бесконечно вложенный условный оператор? Нафиг­нафиг. Есть несколько бо педагогических соображений, и ладно. Добро пожаловать в реальный мир.
А хорошо ли, что если все четыре угловые клетки свободны, искусственный интеллект всегда ходит в левую верхнюю? Плохо. Но это я оставил для Вас.
ет дополнительную трату времени. Если
лее изящный метод, но, сугубо из
я оставил его на потом. И, ещё раз, работает
Имеем в результате
х
Ну и что остал всё написанное в единый текст программы и дорабо Начнём с того, чем закончили предыдущий раздел. Случайный выбор одной из нескольких свободных клеток. Вся печаль в том, что клеток мало, максимум четыре. И получаем известную картину стрельбы из пушек по воробьям. Было бы клеток штук сто… Стандартный ход мысли – составить список пустых клеток, попутно узнав их количество. Для наших скромных масштабов – – возможно, посетит Идея. Ну и когда всё это закончилось (в смысле, основной цикл) как-то надо отреагировать на происшедшее. Для этого у Вас есть переменная result. Как миниму фейерверк. Или нарисуйте линию, перечёркивающую три крестика или три нолика. А лучше, всё сразу.
190
ось сделать? Мне – ничего. А Вам – аккуратненько собрать
тать напильником.
ужас какой-то получается. Поразмышляйте
м, напишите кто проиграл
, а кто выиграл. Или изобразите
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]