Скачиваний:
51
Добавлен:
01.05.2014
Размер:
257.54 Кб
Скачать

20.2, Игра Ним

От «Выдающегося ума» перейдем к игре Ним, в которую также играют двое. В игре используются спички, разложенные в Nкучек. Игроки поочередно берут из любой кучки произвольное число спичек (можно все). Побеждает игрок, который берет последнюю спичку. На рис. 20.1показана обычная исходная позиция, в которой четыре кучки содержат 1, 3, 5и 7спичек соответственно.

Программу для игры Ним будем строить, основываясь на структуре игровой программы 18.8.

Сначала надо выбрать способ представления игровой позиции и ходов. Позицию естественно представлять списком целых чисел, элементы которого соответствуют кучкам спичек. Ход представим парой (N,M).где М-число спичек, взятых из кучкиN.Нетрудно написать процедуруход (Ход, Позиция, Позиция1),согласно которой после ходаХодпозицияПозицияпереходит в позициюПозиция1.Рекурсивное правило служит для перебора кучек с целью достижения требуемой кучки. Остающиеся кучки спичек, представляющие новую позицию игры, вычисляются обычным образом:

ход((К,М),[N| NS],[N| Nsl]) 

К > 1,К1: =К - 1, xoд ((K1,M),Ns,Nsl).

Существуют две возможности модификации определенной кучки спичек. Если все спички из кучки взяты, то она удаляется из списка. В противном случае вычисляется новое число спичек в кучке и проверяется ею допустимость:

ход((1 ,N),[N| Ns],Ns).

ход((l, M),[N | Ns],[N1 | Ns])  N > M, Nl: = N - M.

Техника ходов в играх для двоих специфицируется двумя фактами.

Исходныесостояния кучек спичек и право на первый ход определяются игроками. В предположении, что компьютер ходит вторым, играс исходной позицией, представленной на рис. 20.1,описывается предложением

инициализировать(ним,[1,3,5,7],противник).

I

I I I

I I I I I

I I I I I I I

Рис.20.1Исходная позиция в игре Ним.

1

1 1

1 0 1

1 1 1

0 0 0

Рис.20.2Вычисление ним-суммы.

Игра завершается, когда взята последняя спичка. Этому соответствует состояние игры, представляемое пустым списком. Игрок, который не может сделать следующий ход, проигрывает, о чем сообщается с помощью предложения извещение. Программа 20.2содержит необходимые детали.

Осталось описать, как производится выбор ходов. Ходы противника поступают с клавиатуры. Гибкость организации ввода зависит от программиста. Поскольку нас интересует логика игры, будем предполагать, что игрок вводит допустимые ходы:

игра(Партия) См. программу 18.8.

Выбор ходов

выбор_хода (Позиция,противник,Ход) 

writeln(['пожалуйста, сделайте ход G']),геаd(Ход).

выбор_хода(Ns, компьютер, Ход)

опасная(Ns, cyммa),6eзопасный_xoд(Ns,Сумма, Ход).

выбор .хода (Ns,компьютер, (1,1))  %«Произвольный ход» компьютера

безопасная (Ns).

Ход( Ход, Позиция, Позиция1)

Позиция1-результат выполнения ходаХод в текущей позицииПозиция.

ход ((K,M),[N | Ns],[N | Nsl]) 

К > l, Kl:=K-l, xoд((Kl,M),Ns,Nsl).

ход((1,N),[N | Ns],Ns).

ход((l,M),[N | Ns],[Nl | Ns]) 

N > M,N1:= N - M.

отображение_партии (Позиция, X) write(Позиция),n1.

следующий_игрок (компьютер, противник).

следующий_игрок (противник, компьютер).

партия_закончена([ ],Игрок, Игрок).

сообщение (компьютер)  write('Выпобедили! Поздравляю.'),n1.

сообщение (противник)  write(‘ Я победил.’),nl.

инициализировать(ним, [1,3,5,7],противник).

опасная (Позиция, Сумма)

Позиция сним - суммойСуммаявляется опасной.

опасная (Ns,Сумма)  ним_сумма(Ns, [ ],Сумма), notнуль(Сумма).

безопасная (Ns) notопасная (Ns,Сумма).

ним_сумма (Позиция, Накопленная Сумма, Сумма)

Сумма-ним - сумма текущей позицииПозиция,

Накопленная Сумма-накопленное значение суммы.

ним_сумма([N| Ns],Bs,Сумма) 

двoичнoe(N,Ds),ним_cлoжeниe(Ds,Bs,Bsl),

ним_сумма(Ns,Bsl,Сумма).

ним_сумма([ ],Сумма, Сумма).

ним_сложение(Вs,[ ],Bs).

ним_сложение([ ],Bs,Bs).

ним_сложение([В | Bs],[C | Cs],[D | Ds]) 

D: =(В +С) mod 2,ним_сложение (Bs, Cs, Ds).

двоичное(1,[1]).

двоичное(N, [D| Ds]) 

N > 1, D: = N mod 2, Nl: = N/2,двоичное(N1,Ds).

десятичное (Ds,N) десятичное (Ds, 0,1,N).

десятичное([ ],N,T,N).

десятичное([D | Ds],А,Т,N)  А1 : =А + D*T,T1: = T*2,дecятичнoe(Ds,Al,Tl,N).

нуль([ ]).

нуль ([0 | Zs]) нуль(Zs).

безопасный_ход (Позиция,Ним,Сумма,Ход)

Ход-ход в текущей позицииПозиция,которой соответствует значениеНимСумма, сохраняющий позицию безопасной.

безопасный_ход(Кучки, НимСумма, Ход)

безопасный_ход (Кучки, НимСумма,1, Ход).

безопасный_ход ([Кучка | Кучки], НимСумма, К, (К, М)) 

двоичное (Кучка, Bs),может_быть_нуль(Вs, НимСумма, Ds,0),

десятичнoe(Ds,М).

безопасный_ход([Кучка | Кучки],НимСумма, К,Ход) 

К1: =К +1,безопасный_ход(Кучки,НимСумма,К1,Ход).

может_быть_нуль([ ],НимСумма, [],0) 

нуль(НимСумма).

может_быть_нуль([В| Bs],[0 |НимСумма], [С | Ds],С) 

может_быть_нуль(Bs,НимСумма, Ds,C).

может_быть_нуль([В|Вs],[1 | НимСумма], [D| Ds],С) 

D:= 1 -B*C,C1 := 1–В, может_быть_нуль (Bs, НимСумма,Ds,Cl).

выбор_хода(Позиция, противник, Ход)

writeln(['пожалуйста,ходите']), геаd(Ход).

Программа 20.2Программа для игры Ним, реализующая выигрышную стратегиюигры.

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

Bы6op_xoдa([N | Ns],компьютер,(l.N)).

Для игры Ним известна выигрышная стратегия. Она предполагает деление состояний, или позиций, игры на два класса -безопасные и опасные состояния. Для определения принадлежности некоторой позиции к одному из классов вычисляются двоичные представления числа спичек в каждой кучке. Затем подсчитываются ним - суммы этих двоичных чисел следующим образом: независимо в каждом столбце все элементы суммируются по модулю 2.Если суммы по всем столбцам равны нулю, то соответствующая позиция является безопасной. В противном случае позиция опасна.

На рис. 20.2показан процесс вычисления ним_сумм для позиции из четырех кучек спичек, представленной на рис. 20.1.Двоичными представлениями десятичных чисел1, 3, 5и 7будут 1, 11, 101и 111соответственно. Вычислим ним - сумму: заметим, что в первом столбце справа 4единицы, во втором-2,в третьем-2единицы. В каждом столбце - четное число единиц, поэтому ним - сумма равна нулю, а соответствующая позиция [1,3,5,7]безопасна. С другой стороны, например, позиция [2,6]является опасной. Здесь двоичными представлениями будут 10и 110.В первом слева столбце сумма по модулю 2равна 1,что делает позицию опасной.

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

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

выбор_хода(Ns,компьютер,Ход) 

oпacнaя(Ns,Cyммa),бeзoпacный_xoд(Ns,Cyммa,Xoд). вы6op_xодa(Ns,Компьютер,(l,l))  %«Произвольный ход» компьютера

бeзoпacнaя(Ns).

Предикат опасная (Ns, Сумма)истинен, если представляемая Nsпозиция является опасной. Он определяется с помощью вычисления ним - суммыСумма(сумма вычисляется процедуройним_сумма/3)и ее проверкой на равенство нулю:

oпacнaя(Ns,Cyммa)  ним_суммa(Ns,[],Сумма), notнуль(Сумма).

В предыдущей версии программы определение истинности предиката опаснаянесопровождалось выдачей суммыСумма,При написании правила для определение предикатабезопасный_ходобнаружилось, что было бы полезно использовать здесь уже вычисленное значение ним-суммы, а не перевычислять его заново. Предикатбезопаснаялегко определяется с помощью предикатаопасная.

Ним-сумма вычисляется предложением ним_cyммa(Ns,HaкonлeннaяCyмма,Сумма).Вычисление этого отношения связано с получением ним-суммыСумма чисел Nsпосредством добавления их к накопленной суммеНакопленнаяСумма.Для выполнения сложений числа предварительно должны быть преобразованы в двоичный вид с помощью процедурыдвоичное/2:

ним-сумма([N | Ns],Bs,Сyммa) 

двоичное(N,Ds),ним_сложение(Ds,Bs,Вs1), ним_сумма(Ns,Вs1, Сумма).

Число в двоичном виде представляется здесь списком цифр. Чтобы преодолеть затруднение со сложением чисел, представленных списками неравной длины, цифры с меньшими весами записаны в списке слева. Так, 2(в двоичной системе 10) представляется как [0,1],а6-как [0,1,1].При этом два числа поразрядно складываются, начиная с младших, левых разрядов. Это сложение выполняется при вычислении предикатаним_сложение/3.Оно несколько проще, чем обычное сложение, поскольку не требуется учитывать переносы. Тексты процедурдвоичноеиним_сложениесодержатся в программе 20.2.

Ним-сумма Суммаиспользуется в предикатебезопасный_ход (Ns,Сумма,Ход) для поиска выигрышного ходаХодв позиции, описываемой Ns.Предикатбезопасный_ход/4служит для поиска кучки, удаление из которой некоторого количества спичек создает безопасную позицию. Такой поиск выражается следующим интересным правилом:

безопасный_ход([Кучка | Кучки], НимСумма,К,(К,М)) 

двоичнoe(Kyчкa,Bs),может_быть_нуль(Вs,НимСумма, Ds,0),

десятичное(Ds,М).

Центральной частью этой программы является предикат может_быть_нуль(Bs, НимСумма,Ds,Перенос).Это отношение истинно,если замена двоичного числа Bs на двоичное число Dsприводит к нулевому значениюНимСумма.Число Ds вычисляется последовательно цифра за цифрой. Каждая цифра определяется соответствующей цифрой Bs, НимСуммаи цифрой переносаПеренос,которая первоначально устанавливается равной нулю. Чтобы сделать корректный ход, это число преобразуется с помощью предикатадесятичное/2в десятичное представление.

Программа 20,2представляет собой полную программу для игры Ним, которая в диалоге реализует выигрышную стратегию. Так же как и существующая программа для этой игры, она является некоторой аксиоматизацией того, что составляет выигрышную стратегию.

20.3. Игра в калах

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

В калах играют на доске с двумя рядами из шести лунок, расположенных друг против друга. Каждый игрок владеет рядом из шести лунок и еще одной, лункой справа, называемой калахом. В исходной позиции в каждой из шести лунок находится по шесть камешков, а лунка калах - пуста. Исходная позиция изображена в верхней половине рис. 20.3.

Рис. 20.3.Положение на доске для игры в калах.

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

Изображенная в нижней половине рис. 20.3доска калаха представляет позицию, сложившуюся после двух ходов подряд игрока, владеющего верхним рядом лунок. Он взял шесть камешков из самой правой лунки. При их раскладывании последний камешек попал в калах, и поэтому игрок получил право еще на один ход. На этот раз он взял все камешки из четвертой справа лунки и разложил их.

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

Трудностью программирования этой игры на Прологе является поиск эффективной структуры данных для представления игральной доски, удобной для вычисления ходов. Мы используем четырехаргументную структуру доска(Лунки, Калах, ЛункиПротивника, КалахПротивника),гдеЛунки- список количеств камешков в ваших шести лунках,Калах -число камешков в вашем калахе, аЛункиПротивникаиКалахПротивника - это список количеств камешков и число камешков в калахе противника. Выбор списков, а не шестиместных структур был сделан с целью облегчения написания рекурсивных программ для распределения камешков по лункам.

Ход состоит в выборе лунки и распределении находящихся в ней камешков. Ход описывается списком целых чисел со значениями от 1до 6включительно, которыми указываются лунки. Лунка 6 ближайшая к калаху игрока, в то время как лунка1наиболее удаленная от него. Использование списка, и не отдельного числа объясняется тем, что ход может продолжаться. Так, ход, изображенный на рис 20.3, представляется списком [1,6].

В программе 20.3представлены все ходы, используемые при организации возвратов. Если Nбольше 0,то предикаткамешки (М,доска, N)обеспечивает получение числа камешков Nв лунке М. Вычисление предиката завершается отказом, если в этой лунке нет камешков. Предикатпродолжить_ход(M,Доска,N,Ms)обеспечивает продолжение хода Ms.Второе предложение в процедуре используется для обработки случая, когда во время выполнения хода все лунки игрока становятся пустыми.

Основная часть программы игры в калах

игра (Партия) См. программу 18.8.

Выбор хода с использованием минимаксной стратегии и альфа-бета отсечение

выбрать_ход (Позиция, компьютер, Ход) 

просмотр_вперед(Глубина),

альфа_бета (Глубина, Позиция, -40,40,Ход, Значение),

nl, write(Ход),п1.

выбрать_ход(Позиция, противник, Ход) 

nl, writeln(['Делайте, пожалуйста, ход']), read(Ход), допустимый (Ход).

альфа_бета(0, Позиция, Альфа, Бета, Ход, Значение) 

значение (Позиция, Значение).

альфа_бета (D,Позиция, Альфа, Бета, Ход, Значение) 

D>0,

множество_состояний(М, Ход (Позиция, М), Ходы),

Альфа1: = -Бета,

Бета1: =1Альфа,

D1:=D-1,

оценить_и_выбрать (Ходы, Позиция, D1,Альфа1,Бета1, nil,(Ход,Значение)).

оценить_и_выбрать([Ход | Ходы], Позиция, D,Альфа, Бета, Запись, Лучший Ход) 

ход(Ход, Позиция, Позиция1),

альфа_бета (D,Позиция1,Альфа, Бета, ХодХ, Значение)

Значение1:=- Значение,

отсечение (Ход, Значение 1,D,Альфа, Бета, Ходы, Позиция, Запись, ЛучшийХод),!.

оценить_и_выбрать ([ ],Позиция, D,Альфа, Бета, Ход, (Ход, Альфа)).

отсечение (Ход, Значение, D,Альфа, Бета, Ходы, Позиция,Запись, (Ход, Значение)) 

Значение Бета,!.

отсечение (Ход, Значение, D,Альфа, Бета,Ходы, Позиция, Запись, ЛучшийХод) 

Альфа <Значение, Значение <Бета,!.

оценить_и_выбрать (Ходы, Позиция, D,Значение, Бета, Ход, ЛучшийХод).

отсечение (Ход, Значение, D,Альфа, Бета, Ходы, Позиция, Запись, ЛучшийХод) 

Значение Альфа,!,

оценить_и_выбрать (Ходы, Позиции, D, Альфа, Бета, Запись, ЛучшийХод).

ход (Доска, [М | Ms]) 

member(M,[l,2,3,4,5,6]),

камешки в_лунке(М, Доска, N),

продолжить_ход(М,М,Доска, Ms).

ход(доска([0,0,0,0,0,0],К,Ys,L),[ ]).

камешки_в_лунке (М,доска (Hs,К, Ys, L),Камешки) 

n_й_член(М,Нs,Камешки),Камешки > 0.

продолжить_ход(Камешки,М, Доска, [ ]) 

Камешки =\ = (7 -М) mod 13,!.

продолжить ход (Камешки, М, Доска, Ms) 

Камешки =:= (7 —М) mod ! 3,!,

распределить_камешки (Камешки, М, Доска, Доска1).

ход (Доска1,Ms).

Выполнение хода

ход(N| Ns],Доска, ФинальнаяДоска) 

камешки_в_лунке(N, Доска. Камешки),

распределить_камешки (Камешки, N,Доска, Доска1),

ход (Ns,Доска1,Финальная Доска).

ход([ ],Доска 1,Доска2) 

обмен(Доска 1,Доска2).

распределить_камешки (Камешки, Лунка, Доска, Доска!)

Состояние Доска1-результат распределения камешков(Камешки)из лунки(Лунка) при текущем состоянииДоска.Распределение производится в два этапа: распределение по своим лункам(распределитъ_по_моим_лункам)и распределение по лункам противника(распределить_по_вашим_лунка.и).

распределить_камешки (Камешки, Лунка, Доска, Финальная Доска) 

распределить_ло_моим_лункам (Камешки, Лунка, Доска, Доска 1,Камешки1), распределить_по_.вашим_лункам (Камешки1,Доска1,ФинальнаяДоска).

распределить_по_моим_лункам(Камешки,N,доска(Нs,К,Ys,L),доска(Нs1, Кl,Ys,L),

Камешки) 

Камешки > 7 - N,!,

взять_и_распределить(N,Камешки, Hs,Hsl),

К1: =К+ 1,Камешки! : =Камешки + N-7.

распределить_по_моим_лункам (Камешки, N,доска (Hs,К, Ys,L),Доска, 0)

взять._и_распределить(N,Камешки, Hs, Hsl),

проверить_захват (N,Камешки, Hs1, Hs2, Ys, Ys1,Штуки),

модифицировать_калах (Штуки, N,Камешки, К., К1),

проверить_на_окончание (доска(Нs2, K1 ,Ys1, L)Доска).

проверить_захват (N,Камешки, Hs, Hs 1, Ys, Ys 1,Штуки) 

ФинишнаяЛунка: = N +Камешки,

ПротивоположнаяЛунка: = 7-ФинишнаяЛунка,

n_й_.член (Противоположная Лунка, Ys, Y).

Y>0,!,

n_подставка (Противоположная Лунка, Hs, 0, Hs 1),

n_подставка (ФинишнаяЛунка, Ys, 0, Ys1),

Штуки := Y+ 1.

проверить_захват(Камешки, Hs,Hs,Ys,Ys,0)  !.

проверить_на_окончание (доска (Hs,К, Ys, L),доска (Hs,К, Hs, L1) 

нуль(Нs),!, списоксумм(Ys,YsSum), L1: = L + YsSum. проверить_нa_окончание(доскa(Hs,K,Ys,L),доскa(Ys,Kl,Ys,Ll)) 

Нуль(Ys),!,списоксумм(Hs,HsSum),Kl :=К + HsSum.

проверить на окончание(Доска. Доска) !,

модифицировать_калах(0,Камни,N,К,К) Камни < 7-N,!.

модифицировать_калах(0,Камни,N,К,К1) Камни =:=7-N,!,K1:=K+1.

модифицировать_калах (Штуки, Камни, N,К, К1)Штуки >0,!,К1: =К+Штуки.

распределить._по_вашим_лункам (0,Доска, Доска)  !.

распределить_по_вашим_лункам (Камешки, доска(Нs,К,Ys,L),доскa(Hs,К,Ysl,L))

1Камешки,Камешки 6,

не_нуль(Нs),!,

распределить(Камешки,Ys,Уs1).

распределить_по_вашим_лункам (Камешки, доска(Нs,К,Ys,L),доска(Нs, К, Ysl, L))

Камешки > 6,!,

pacпределить(6,Ys,Ysl),

Камешки1 : =Камешки-6,

распределить_камешки (Камешки 1,1,доска (Hs,К, Ysl, L),Доска).

распределить_по_вашим_лункам (Камешки, доска (Hs,К, Ys, L),доска (Hs,К, Hs, Ll))

нуль(Нs),!,сисоксумм(Ys,YsSum),Ll: =Камешки+YsSum+L.

Распределение камешков на нижнем уровне

взять_и_распределить (1,N,[H | Hs],[0 | Hsl]) 

!,распределить (N, Hs, Hs 1).

взять_и_распределить(К,N,[Н | Hs],[H | Hsl]) 

К>1,!,K1:=К-1,взять_и_распределить(К1,N,Нs,Нs1).

распределить (0,Hs,Hs)  !.

распределить (N,[H | Hs].[Hl | Hsl]) 

N >0,!,N1: = N-1,Н1: =Н+1,распределить (Nl,Hs, Hsl).

распределить(N,[ ],[ ])'.

Оценочная функция

значение(доска(Н,К,Y,1.),Значение) Значение: =К-L.

Проверка окончания игры

игра_закончена (доска (0, N,0, N),Игрок,ничья) 

штуки(K),N=:=6*K!.

игра_закончена(доска(Н,К,Y,L), Игрок, Игрок) 

штуки(N),К>6*N,!.

игра_закончена (доска(Н,К,Y,L,), Игрок, Противник) 

штуки(N),L>6*N,следующий_партнер (Игрок, Противник).

сообщение (противник) wirteln([‘Поздравляю! Вы победили.’]).

сообщение (компьютер) writeln([‘Я победил.']).

сообщение(ничья) writeln(['Игpaзакончилась вничью.']).

Разные вспомогательные средства

n_й_член(N,[Н | Н8],К)

N>1,!N1:= N-l,n_й_член(Nl,Hs,K).

n_й_член(1,[Н | Нs],Н).

n_подстановка(1,[Х | Xs],Y,[Y | Xs])  !.

n_подстановка (l,[X | Xs], Y,[Y | Xsl ])

N> 1,!,N1:= N-l,n_пoдcтaнoвкa(Nl,Xs,Y,Xsl).

следующий .игрок (компьютер, противник).

следующий игрок (противник, компьютер).

допустимый ([N| Ns])  0 < N,N < 7,допустимый (Ns).

допустимый ([ ]).

обмен (доска (Hs,К, Ys, L),доска (Ys, L, Hs,К)).

отображение_партии (Позиция,компьютер) 

демонстрация (Позиция).

отображение_партии (Позиция, противник) 

обмен (Позиция, Позиция1),демонстрация (Позиция 1).

демонстрация (доска (Н. К,Y,L)) 

реверсировать(Н,НR), вывод_камешков (HR),

вывод калахов(К,L),вывод камешков(Y).

вывод_камешков(Н) 

n1,tab(5),отображение_лунок(Н).

отображение лунок ([Н | Hs])

вывод_кучки(Н), отображение_.лунок (Hs).

отображение_лунок([ ]) n1.

вывoд_кyчки(N)  N < 10,write(N),tab(4).

вывод_кучки(N)  N  10,write(N),tab(3).

вывод_калахов(К,L) 

write(K), tab(34), write(4), nl.

нуль([0,0,0,0,0,0]).

не_нуль(Нs)  Hs [0,0,0,0,0,0].

Инициализация

просмотр_вперед(2).

инициaлизиpoвaть(кaлax,дocкa([N,N,N,N,N,N],0,[N,N,N,N,N,N,0),пpoивник) 

штуки(N).

штуки(6).

Программа 20.3.Программа для игры в калах.

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

Основным предикатом, используемым для выполнения хода, является предикатраспределить_камешки(Камешки, N, Доска, Доски 1),который определяет, что позицияДоска1получена из позицииДоскапри распределении камешковКамешки, начиная с лунки с номером N.Распределение выполняется в два этапа: распределение камешков по своим лункам (предложениераспределить_по_моим_лункам} и распределение камешков по лункам противника (предложение,распределить_по_вашим_лункам).

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

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

Оценочная функция определяется как разность между количествами камешков в двух калахах; для вычисления ее значения используется следующее правило:

значенис(доска(Н,К,Y,L),Значeниe)Значение: = К - L.

Таким образом, описаны основные предикаты программы. Теперь пригодную для выполнения программу можно получить, добавив в нее предложения ввода-вывода, средства инициализации и завершения игры и др. Простые примеры такого дополнения можно найти в программе 20.3,представляющей собой полную программу этой игры.

Для оптимизации программы в нее могут быть добавлены отсечения. Можно еще посоветовать переписать основной цикл программы, сделав его не рекурсивным, а управляемым отказами. Это иногда необходимо, когда в системе отсутст­вует оптимизация остатка рекурсии и нет хорошего сборщика мусора.

Соседние файлы в папке prolog14_end