Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Самоучитель игры на Паскале. АВС и немного Турбо
.pdf
{ещё шесть раз}
………………….
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
ось сделать? Мне – ничего. А Вам – аккуратненько собрать
тать напильником.
ужас какой-то получается. Поразмышляйте
м, напишите кто проиграл
, а кто выиграл. Или изобразите
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
