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

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

Соседние файлы в папке Курсовой проект ПРОЛОГ