Лабы_ЭкспСист / Лабораторная №8
.docЛабораторная работа №8.
Интеллектуальные игры.
Цель работы: Изучить определение типа данных для Visual Prolog.
Задание: Написать программу для игры в Ним.
Состояние в игре Ним может быть представлено как набор кучек спичек; мы будем отображать каждую кучку как список целых чисел.
[1, 1, 1, 1, 1]
Следовательно, множество кучек будет списком списков; например,
[ [1,1,1,1,1],
[1, 1],
[1,1,1,1] ]
Два игрока, вы и компьютер, по очереди делаете ходы. Как только игрок не сможет сделать ход, игра заканчивается, и этот игрок проигрывает. Ход состоит из взятия как минимум одной спички из ровно одной кучки. В примере, если вы возьмёте три спички из третьей кучки, вы сделаете допустимый ход, и доска станет
[ [1,1,1,1,1],
[1, 1],
[1] ]
где вы убрали три спички из третьей кучки. Чтобы реализовать этот проект, мы будем использовать технику программирования, называемую incremental development of systems – наращивающая разработка систем. Сначала, вы реализуете и протестируете программу, которая присоединяет два списка. Так как вы собираетесь использовать эту программу и для разделения, и для конкатенации списков, вам нужно протестировать обе возможности. Фактически, если вы запустите первую программу, из указанных (ниже), вы получите
[[1],[1],[1],[1],[1],[1],[1],[1]]
[][[1],[1],[1,1]]
[[1]][[1],[1,1]]
[[1],[1]][[1,1]]
[[1],[1],[1,1]][]
Первая строка является результатом конкатенации двух множеств кучек; последующие строки показывают все возможности разделения списка кучек. Тогда первая программа, похоже, работает верно.
implement nimgame
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
clauses
classInfo( "nimgame", "1.0").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
clauses
run():-
console::init(),
append([[1],[1],[1],[1],[1]], [[1],[1],[1]], L), !,
stdio::write(L), stdio::nl; succeed().
end implement nimgame
goal
mainExe::run(nimgame::run).
Следующий шаг – написать предикат, который забирает как минимум одну спичку из кучки; конечно, он может забрать больше, чем одну спичку. После удаления одной или более спичек из кучки, takesome/3 вставляет измененную кучку во множество кучек. Если вы протестируете программу, она напечатает следующий результат:
[[1,1,1,1],[1,1]]
[[1,1,1],[1,1]]
[[1,1],[1,1]]
[[1],[1,1]]
[[1,1]]
NB: первое предложение takesome/3 заверяет, что предикат не вставит пустую кучку во множество кучек.
implement nimgame
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
test:() procedure.
takesome:(ms, hs, hs) nondeterm anyFlow.
clauses
classInfo( "nimgame", "1.0").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
test() :- L= [1, 1, 1, 1, 1],
V= [[1,1]],
takesome(L, V, Resp),
stdio::write(Resp), stdio::nl,
fail; succeed().
clauses
run():-
console::init(),
test().
end implement nimgame
goal
mainExe::run(nimgame::run).
Теперь вы готовы создавать все возможные ходы с заданной позиции. Если вы запустите тестовую программу, она выдаст следующий экран:
Possible moves from [[1,1,1],[1,1]]:
[[1,1],[1,1]]
[[1],[1,1]]
[[1,1]]
[[1,1,1],[1]]
[[1,1,1]]
implement nimgame
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
takesome:(ms, hs, hs) nondeterm anyFlow.
move:(hs, hs) nondeterm anyFlow.
clauses
classInfo( "nimgame", "1.0").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
move(X, Y) :- append(U, [X1|V], X),
takesome(X1, V, R), append(U, R, Y).
run():- console::init(), L= [[1, 1, 1], [1, 1]],
stdio::write("Possible moves from ", L, ": "),
stdio::nl, move(L, Resp),
stdio::write(Resp), stdio::nl, fail; succeed().
end implement nimgame
goal mainExe::run(nimgame::run).
implement nimgame
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
takesome:(ms, hs, hs) nondeterm anyFlow.
move:(hs, hs) nondeterm anyFlow.
winningMove:(hs, hs) nondeterm (i, o).
clauses
classInfo( "nimgame", "1.0").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
move(X, Y) :- append(U, [X1|V], X),
takesome(X1, V, R), append(U, R, Y).
winningMove(X, Y) :- move(X, Y),
not(winningMove(Y, _)).
run():- console::init(), L= [[1, 1, 1], [1, 1]],
winningMove(L, S),
stdio::write("Winning move: ", S),
stdio::nl, fail; succeed().
end implement nimgame
goal mainExe::run(nimgame::run).
Сердце этой программы – предикат
winningMove(X, Y) :- move(X, Y), not(winningMove(Y, _)).
Если есть выигрышная стратегия, этот потрясающий предикат в одну строчку найдёт её!
Ниже вы найдёте пример игры против компьютера. Как вы можете видеть, если есть шанс выиграть, машина возьмёт его.
[[1,1,1],[1,1]]
Your move: [[1], [1,1]]
My move: [[1],[1]]
Your move: [[1]]
My move: []
I win
Чтобы реализовать конечную программу, вы создадите класс для самой игры. Пусть названием для этого класса будет nim. Затем, основная программа дана ниже.
% File: nimgame.pro
implement nimgame
open core, stdio
clauses
classInfo( "nimgame", "1.0").
run():- console::init(), L= [[1, 1, 1], [1, 1]],
write(L), nl, nim::game(L), ! ; succeed().
end implement nimgame
goal mainExe::run(nimgame::run).
Интерфейс класса экспортирует метод game/1 и домены, которые вам нужны для описания позиции в игре. Он определён ниже.
% File: nim.cl
class nim
open core
domains
ms= integer*.
hs= ms*.
predicates
classInfo : core::classInfo.
game:(hs) determ (i).
end class nim
% File:nim.pro
implement nim
open core, stdio
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
takesome:(ms, hs, hs) nondeterm anyFlow.
move:(hs, hs) nondeterm anyFlow.
winningMove:(hs, hs) nondeterm (i, o).
iwin:(hs) determ (i).
youwin:(hs, hs) determ (i, o).
clauses
classInfo("nim", "1.0").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
move(X, Y) :- append(U, [X1|V], X),
takesome(X1, V, R), append(U, R, Y).
winningMove(X, Y) :- move(X, Y), not(winningMove(Y, _)).
iwin([]) :- !, write("I win"), nl.
youwin(L, L2) :- winningMove(L, L2), !.
youwin(_L, _L2) :- write("You win"), nl, fail.
game(L1) :- write("Your move: "),
L= console::read(), move(L1, LTest),
L= LTest, youwin(L, L2), !,
write("My move: "), write(L2), nl,
not(iwin(L2)), game(L2).
end implement nim
Стратегия, использованная в этой очень простой программе, называется алгоритмом минимакса (minmax algorithm). Алгоритм может быть применён к любой игре с двумя соперниками, где оба имеют совершенно точное знание о состоянии игры. Примеры таких игр: шахматы, Го, шашки, крестики-нолики и Ним. Он также может быть использован для контролирования роботов.