Курсовой проект ПРОЛОГ / Costas-Tyros_rus
.pdf
class predicates
append : (E*, E*, E*) nondeterm anyFlow.
clauses
classInfo("main", "nimgame").
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 main
goal
mainExe::run(main::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 main 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("main", "nimgame").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
101
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 main
goal
mainExe::run(main::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 main 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("main", "nimgame").
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).
102
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 main
goal mainExe::run(main::run).
Сердцем программы является предикат
winningMove(X, Y) :- move(X, Y), not(winningMove(Y, _)).
Если выигрышная стратегия существует, то этот потрясающий предикат в одну строчку найдёт её!
implement main 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("main", "nimgame").
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 main
goal mainExe::run(main::run).
Ниже приведен пример игры против компьютера. Как видите, если существует возможность выиграть, то машина это сделает.
[[1,1,1],[1,1]]
103
Your |
move: [[1], [1,1]] |
% Ваш |
ход |
|
My move: [[1],[1]] |
% |
Мой |
ход |
|
Your |
move: [[1]] |
|
|
|
My move: [] |
|
|
|
|
I win |
% |
Я победил |
||
Для того чтобы реализовать программу, вам осталось создать класс для самой игры. Пусть этот класс называется nim. Основная программа приведена ниже.
% Файл main.pro implement main
open core, stdio
clauses
classInfo("main", "nimgame-1.0").
run():- console::init(), L= [[1, 1, 1], [1, 1]], write(L), nl, nim::game(L), !; succeed().
end implement main
goal mainExe::run(main::run).
Декларация следующего класса содержит метод game/1 и домены, которые вам необходимы для описания состояния игры. Этот класс определяется ниже.
%Файл nim.cl class nim
open core domains
ms= integer*. hs= ms*.
predicates
classInfo : core::classInfo. game : (hs) determ (i).
end class nim
%Файл nim.pro
implement nim
open core, stdio
class predicates
append : (hs, hs, hs) 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).
104
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
Стратегия, которая используется в этой очень простой программе, называется алгоритмом минимакса. Этот алгоритм может применяться в любой игре с двумя соперниками, где оба имеют полное знание о состоянии игры. Примеры таких игр: шахматы, Го, шашки, крестики-нолики и Ним. Он также может быть использован для управления роботами. На самом деле, существует разновидность фильтров, основанных на алгоритме минимакса, которая аналогична фильтру Калмана.
В стратегии минимакса состояния игры образуют дерево, которое называется деревом поиска. Когда наступает ход компьютера, он генерирует состояние с минимальным значением для оппонента. С другой стороны, он пытается следовать ветвям дерева, которые ведут к выигрышной позиции. В простой игре, такой как Ним, компьютер способен найти путь, обеспечивающий победу. В более сложных играх, таких как шахматы, это не всегда возможно — необходимо прерывать поиск до того, как будет достигнута уверенность в победе. В этом случае игровые стратеги придумывают функции, которые присваивают значение позиции, даже если неизвестно, выигрышная это позиция или проигрышная.
105
Рисунок 10.1 Дерево минимакса
106
Глава 11: Факты
Факт — это предложение Хорна, в котором нет тела. Факты могут быть добавлены, изменены или удалены динамически, в ходе исполнения программы. Следующий пример поможет вам понять, почему факты необходимы в Прологе. Предположим, что вы хотите создать маленькую базу данных публикаций. Когда у вас есть новая книга, вы хотите добавить её в базу данных.
addItem(journal("AI in focus", "MIT Press"))
Предикат addItem/1 можно определить следующим образом:
class predicates addItem : (volume).
clauses addItem(V) :-
num := num+1, assert(item(V, num)).
В результате выполнения addItem(journal("AI", "AldinePress")) в базу данных добавляется предложение
item(journal("AI", "AldinePress")).
и увеличивается на единицу значение переменной num. Следующее объявление
class facts - bib
num : integer := 0.
item : (volume, integer) nondeterm.
создаёт базу фактов bib с помощью переменной num и предиката item/2. Предикат имеет тип nondeterm, а переменная — тип single.
Домен volume, который обеспечивает типизацию записей в этой маленькой базе данных, определяется в виде
domains
name= string.
author= n1(name); n2(name, name); etal(name). publisher= string.
title= string.
volume= journal(title, publisher); book(title, author).
Программа, приведенная ниже, показывает, как определять факты и как сохранять факты базы данных в файле.
% Файл main.pro implement main
open core
107
domains
name= string.
author= n1(name); n2(name, name); etal(name). publisher= string.
title= string.
volume= journal(title, publisher); book(title, author).
class facts - bib num:integer := 0.
item:(volume, integer) nondeterm.
class predicates addItem:(volume). prtDataBase:().
clauses
classInfo("main", "facttest").
addItem(V) :- num := num+1, assert(item(V, num)).
prtDataBase() :- item(V, I), stdio::write(I, "=", V), stdio::nl, fail.
prtDataBase().
clauses run():-
console::init(),
addItem(journal("AI in focus", "MIT Press")), addItem(book( "Databases in Prolog",
n1("Wellesley Barros"))), file::save("bibliography.fac", bib), prtDataBase().
end implement main goal
mainExe::run(main::run).
После создания базы данных и сохранения её в файле вы можете использовать её в другой программе. Для того чтобы увидеть это, создайте консольный проект factread и добавьте в него следующий код:
% Файл main.pro implement main
open core domains
name= string.
author= n1(name); n2(name, name); etal(name). publisher= string.
title= string.
volume= journal(title, publisher); book(title, author).
108
class facts - bib num:integer := 0.
item:(volume, integer) nondeterm.
class predicates prtDataBase:().
clauses
classInfo("main", "factread").
prtDataBase() :- item(V, I), stdio::write(I, "=", V), stdio::nl, fail.
prtDataBase().
clauses
run():- console::init(), file::consult("bibliography.fac", bib), prtDataBase().
end implement main goal
mainExe::run(main::run).
Вы должны переместить файл
bibliography.fac,
созданный приложением facttest, в папку factread/exe. Затем запустите программу
factread.exe.
Вы увидите, что программа прочитает базу данных и напечатает её.
11.1. Класс file
Вы использовали класс file для сохранения фактов в файле. В классе file имеется много других полезных предикатов, которые вы изучите в данном разделе.
11.1.1. Чтение и запись строки
Часто вам требуется прочитать текст из файла, сделать с ним что-нибудь, а затем записать его обратно. В прошлом, когда у компьютеров было не так много памяти, существовала необходимость читать потоки символов. Сегодня память даже персональных компьютеров исчисляется гигабайтами. Поэтому наилучший подход к
решению проблемы — прочитать весь файл в строку, а затем использовать мощный класс string для её обработки. Пролог имеет два предиката для работы такого типа:
readString : (string FileName, boolean IsUnicodeFile) -> string String procedure (i,o).
109
writeString : (string Filename, string Source, boolean IsUnicodeFile) procedure (i,i,i).
Оба предиката имеют версии, не требующие указания, имеет ли файл формат Unicode.
readString : (string FileName)
-> string String procedure (i).
writeString : (string Filename, string Source) procedure (i,i).
На рисунке 11.1 показан пример использования этих предикатов. В данном примере из файла считывается строка, затем ее символы преобразуются в символы верхнего регистра и новая строка записывается в другой файл.
Я полагаю, что вы способны понять функциональность класса file с помощью справочного руководства. Поэтому я не буду задерживаться на этой теме.
% Файл main.pro implement main
open core
constants
className = "main". classVersion = "filetest".
clauses
classInfo(className, classVersion).
clauses run():-
console::init(),
Str= file::readString("test.txt", Unicode), stdio::write(Str),
S_Upper= string::toUpperCase(Str), file::writeString("upper.txt", S_Upper, Unicode), succeed(). % place your own code here
end implement main
goal mainExe::run(main::run).
Рисунок 11.1 Чтение строки из файла
11.2. Константы
Для того чтобы научиться использовать константы построим форму, к которой подключим главное меню. Visual Prolog, как и большинство других языков, представляет пункты меню с помощью целых чисел. Это представление хорошо для компьютера, но не так хорошо для человека, который будет анализировать программу. Поэтому Visual
110
