Добрый день, при написании программы в Prologe появляется строчка getSpiral(_, _, _, _) = []. Но данный предикат нигде не описан и естественно программа выдае ошибку. Можно уточнить, что это за предикат и где и как его необходимо описать |
Игра "Крестики-нолики"
Правила игры. Стратегия выбора хода
Реализуется следующий вариант игры "Крестики-нолики". Игра ведется на прямоугольном поле размером m х n, где и , но место для игры (игровой участок) не должно выходить за пределы квадрата, содержащего девять клеток (3 х 3). В отличие от обычной игры, этот участок определяется только в процессе игры. Выигрывает игрок, поставивший в ряд по вертикали, по горизонтали или по диагонали три своих камня. Если участок заполнен полностью, а ни одному игроку не удалось поставить три камня в ряд, то признается ничья.
Первый игрок первым ходом может поставить камень в любое поле. Игроки ходят по очереди. Они могут ходить только в доступные поля, так чтобы в результате все камни оказались на участке размером 3 х 3. Например, если первый игрок поставит камень так, как показано на рис. 9.1 (a) рис. 9.1 , то второй игрок может ходить в любое поле, находящееся внутри выделенного квадрата, доступными для него являются 24 поля. После хода второго игрока, показанного на рис. 9.1 (b) рис. 9.1 , место игры сужается, доступными для первого игрока остаются 14 полей. Если первый игрок пойдет так, как показано на рис. 9.1 (c) рис. 9.1 , то место игры будет полностью определено.
Нетрудно заметить, что у первого игрока больше шансов выиграть. Тем не менее правильная игра, т. е. игра, в которой оба игрока ходят наилучшим для себя образом, всегда завершится вничью.
Если игрок ставит камень в ряд, в котором уже имеются два камня противника, то такой ход называется блоком, он предотвращает немедленный выигрыш противника. Вилкой называется ход, после которого возникает два различных ряда, содержащих по два камня игрока и не содержащих камней противника; на пересечении этих рядов должен стоять камень. Если участок игры будет полностью определен, то противник сможет заблокировать только один из таких рядов, поэтому у игрока останется возможность поставить камень третьим в другой ряд и выиграть. Если же участок игры не будет полностью определен, то и выигрыш, вообще говоря, не будет предопределенным. Поэтому вилкой в данной игре будет называться только такой ход, после которого участок игры будет полностью определен.
Ниже перечисляются правила, в соответствии с которыми ходит игрок-компьютер. Они приводятся в том же порядке, в котором реализуются в программе. Формулировка правил — "декларативная", по возможности самая общая, без уточнения всех случаев (см. программу). Правила реализуются просто и в общем виде за счет использования списков. Списки в языке Пролог — это сохраняющий (persistent) тип данных, поэтому их удобно использовать для просчитывания позиции на шаг или на несколько шагов вперед.
1. Если противник сделал первый ход, то игрок ставит камень в соседнее по диагонали поле.
После первого хода первого игрока, второй игрок имеет, самое большее, пять вариантов хода, с точностью до симметрии (см. рис. 9.1 (a) рис. 9.1 , где они отмечены точками). В четырех из них он проиграет в игре с правильным игроком-противником (см. ниже правило 6). Остается ход по диагонали в соседнее поле. В программе такое поле выбирается случайным образом.
2. Игрок ставит свой камень третьим в ряд и выигрывает.
3. Игрок ставит блок так, чтобы противник не мог в ответ поставить вилку или поставить камень третьим в ряд.
На рис. 9.2 (a) рис. 9.2 приведен пример позиции, в которой применяется правило 3. Ходит игрок темными. Он должен поставить камень в поле, находящееся непосредственно слева или справа от двух светлых камней, в противном случае он немедленно проиграет. Если игрок поставит камень слева от камней противника, то тот сможет сразу поставить вилку и выиграть. Поэтому игрок должен ходить так, как показано на рис. 9.2 (b) рис. 9.2.
4. Если участок игры полностью определен, то игрок ставит блок.
5. Игрок ставит вилку.
6. Игрок делает такой ход, чтобы противник был вынужден поставить блок, так чтобы следующим ходом игрок мог поставить вилку.
На рис. 9.3 (a) – 9.6 (a) рис. 9.3 рис. 9.6 приведены примеры позиций, в которых для выбора хода игрока применяется правило 6. Ходит игрок темными. Правильные ходы показаны на рис. 9.3 (b) – 9.6 (b) рис. 9.3 рис. 9.6. В каждом случае противник вынужден ставить блок, после чего игрок ставит вилку (рис. 9.3 (c) – 9.6 (с) рис. 9.3 рис. 9.6 ).
7. Если участок игры полностью определен, то делается ход в центральное поле.
На рис. 9.7 (a) рис. 9.7 приведен пример позиции, в которой игрок темными ходит в центральное поле игрового участка (рис. 9.7 (b)) рис. 9.7 . Нетрудно заметить, что если он сделает любой другой ход, то противник сможет поставить ему вилку.
8. Если центральное поле занято, то игрок ходит так, чтобы участок игры был определен, и при этом противник ответным ходом не мог поставить вилку.
На рис. 9.8 (a) рис. 9.8 приведен пример позиции, в которой игрок темными выбирает ход по правилу 8 (рис. 9.8 (b)) рис. 9.8. Если он пойдет в угловое поле игрового участка, то противник сможет поставить ему вилку.
9. Игрок занимает поле, ходом в которое противник поставил бы ему вилку.
10. Игрок делает случайный ход в доступное поле (последние два правила программой фактически не используются).
Создание игрового поля
Создадим проект tictactoe (MDI). После запуска приложения должно открыться окно установки параметров, в котором пользователь сможет указать размеры поля, игрока, который будет ходить первым, и цвет камней.
Создание окна установки параметров
С помощью диалогового окна Create Project Item создадим диалоговое окно settingsDialog.
Разместим в нем следующие элементы управления (рис. 9.9 рис. 9.9):
- надписи (Static Text) "Ваше имя", "Количество строк", "Количество столбцов", "Ходит первым";
- поле редактирования (Edit Control):
- Name: name_ctl; Text: Петя (введите свое имя);
- пользовательские элементы управления (Custom Control; Class: integerControl):
- Name: row_ctl; Text: 15; Maximum: 30; Minimum: 3;
- Name: column_ctl; Text: 15; Maximum: 30; Minimum: 3;
- флажок (Check Box):
- Text: компьютер;
- групповой блок (Group Box):
- Text: Цвет ваших камней;
- в групповом блоке нужно разместить два переключателя (Radio Button):
- Name: dark_ctl; Text: темный; RadioState: checked;
- Name: light_ctl; Text: светлый.
Теперь следует включить пункт меню File -> New, добавить обработчик событий выбора данной команды меню (см. п. 1.1 "Основные элементы графического интерфейса пользователя" ) и определить его следующим образом:
clauses onFileNew(_Source, _MenuTag):- _ = settingsDialog::display(This).
Для того чтобы окно установки параметров открывалось сразу при запуске приложения, изменим также определение предиката onShow в имплементации класса taskWindow так, как показано ниже:
clauses onShow(_, _CreationData):- _MessageForm = messageForm::display(This), _ = settingsDialog::display(This).
Создание класса game
Создадим класс game. В интерфейсе game объявим основные параметры игры. Позднее будет добавлено объявление предикатов.
constants compPlayer : positive = 0. humanPlayer : positive = 1. winner : positive = 1. draw : positive = 0. domains sq = sq(integer Row, integer Column, state). % поле state = darkStone; lightStone; empty. % состояние поля properties m : integer. % число строк n : integer. % число столбцов isComputerFirst : boolean. % комп. ходит первым isDarkStone : boolean. % польз. играет темнымиЛистинг 9.1. Объявление параметров в интерфейсе game
В декларации класса game объявим конструктор и свойство.
constructors new: (integer M, integer N, boolean CompFirst, boolean DarkSt). properties game : game.Листинг 9.2. Объявление свойства в декларации класса game
Ниже приведено определение объявленных свойств и конструкторов в имплементации класса game.
facts m : integer := 15. n: integer := 15. isComputerFirst : boolean. isDarkStone : boolean. clauses new(M, N, IsComputerFirst, IsDarkStone):- m := M, n := N, isComputerFirst := IsComputerFirst, isDarkStone := IsDarkStone. class facts game : game := erroneous.Листинг 9.3. Определение свойств в имплементации класса game
Создание игрового окна
С помощью диалогового окна Create Project Item создадим поле для игры (Control) и назовем его gameControl. Закроем редактор окна gameControl.
Далее создадим форму (Form) gameForm. Разместим в поле формы пользовательский элемент управления (CustomControl; Class: gameControl), для всех якорей привязки укажем значение True (рис. 9.10 рис. 9.10).
Затем с помощью диалогового окна Create Project Item создадим клетку (Draw Control) под названием cellControl. Добавим в интерфейс cellControl объявление свойств и предиката setCellState.
properties gameCtl : gameControl. i : integer. j : integer. state : game::state. predicates setCellState : (game::state).Листинг 9.4. Объявление свойств в интерфейсе cellControl
В имплементацию класса cellControl добавим код, приведенный ниже.
constants bgColor : color = color_BlanchedAlmond. % цвет фона lineColor : color = color_BurlyWood. % цвет линий darkBrushColor : color = color_Sienna. % цвет камня darkBorderColor : color = color_SaddleBrown. % граница камня lightBrushColor : color = color_LightGray. lightBorderColor : color = color_DarkGray. facts gameCtl : gameControl := erroneous. i : integer := 0. j : integer := 0. state : state := empty. clauses setCellState(State):- state := State, invalidate().Листинг 9.5. Определение свойств в имплементации класса cellControl
Далее добавим в интерфейс gameControl объявление свойств, хранящих указатели на объекты классов game и gameForm.
properties game : game. gameFrm : gameForm.Листинг 9.6. Объявление свойств в интерфейсе gameControl
Изменим раздел open имплементации класса gameControl так, как показано ниже:
open core, vpiDomains, cellControl, game
В имплементации класса gameControl следует определить объявленные выше свойства, а также предикаты, которые создают игровое поле.
facts gameFrm : gameForm := erroneous. game : game := erroneous. facts cell: (integer Row, integer Column, cellControl). predicates createControls: (). clauses createControls():- foreach I = std::cIterate(game:m), J = std::cIterate(game:n) do Cell = cellControl::new(This), Cell:i := I, Cell:j := J, Cell:gameCtl := This, Cell:show(), assert(cell(I, J, Cell)) end foreach. predicates setControls: (). clauses setControls():- getSize(Width, Height), Wc = Width div game:n, Hc = Height div game:m, foreach cell(I, J, Cell) do Cell:setSize(Wc, Hc), Cell:setPosition(Wc * J, Hc * I), Cell:invalidate() end foreach.Листинг 9.7. Создание клетчатого поля
В редакторе окна gameControl добавим обработчики событий ShowListener и SizeListener. Определение предикатов приведено ниже.
clauses onShow(_Source, _Data):- createControls(), setControls().Листинг 9.8. Определение предиката onShow
clauses onSize(_Source):- setControls().Листинг 9.9. Определение предиката onSize
При изменении размеров окна пропорциональным образом изменяется размер клеток и камней.
В восточных играх камни часто ставятся на пересечение линий сетки.
В редакторе окна cellControl добавим обработчик событий PaintResponder. Определение предиката onPaint приведено ниже. Сначала закрашивается фон клетки, затем проводятся фрагменты линий сетки — горизонтальный отрезок и вертикальный отрезок. Отрезки проходят через центр клетки. Правила вычисления координат концов отрезков определяются тем, лежит ли клетка на границе игрового поля.
clauses onPaint(_Source, Rectangle, GDI):- rct(L, T, R, B) = Rectangle, % фон GDI:setPen(pen(1, ps_Solid, bgColor)), GDI:setBrush(brush(pat_Solid, bgColor)), GDI:drawRect(Rectangle), % координаты центра клетки X = (L + R) div 2, Y = (T + B) div 2, % концы горизонтального отрезка HorPnt1 = pnt(endPntCoord(j, 0, X, L), Y), HorPnt2 = pnt(endPntCoord(j, gameCtl:game:n - 1, X, R), Y), % концы вертикального отрезка VertPnt1 = pnt(X, endPntCoord(i, 0, Y, T)), VertPnt2 = pnt(X, endPntCoord(i, gameCtl:game:m - 1, Y, B)), % линии сетки GDI:setPen(pen(1, ps_Solid, lineColor)), GDI:drawLine(HorPnt1, HorPnt2), GDI:drawLine(VertPnt1, VertPnt2), % камень if state <> empty then Dx = (R - L) div 8, Dy = (B - T) div 8, % размеры отступа stoneColor(state, BorderColor, BrushColor), GDI:setPen(pen(3, ps_Solid, BorderColor)), GDI:setBrush(brush(pat_Solid, BrushColor)), GDI:drawEllipse(rct(L + Dx, T + Dy, R - Dx, B - Dy)) end if. predicates stoneColor: (state, color Border [out], color Brush [out]). endPntCoord: (integer J, integer J, integer X, integer L) -> integer. clauses stoneColor(darkStone, darkBorderColor, darkBrushColor):- !. stoneColor(_, lightBorderColor, lightBrushColor). endPntCoord(J, J, X, _) = X:- !. endPntCoord(_, _, _, L) = L.Листинг 9.10. Определение предиката onPaint
В интерфейсе gameForm объявим свойство для хранения имени игрока-пользователя.
properties name : string.Листинг 9.11. Объявление свойства в интерфейсе gameForm
Изменим раздел open имплементации класса gameForm следующим образом:
open core, vpiDomains, game
Далее следует изменить определение конструктора new/1 и определить объявленное свойство.
clauses new(Parent):- formWindow::new(Parent), generatedInitialize(), % gameControl_ctl:gameFrm := This, gameControl_ctl:game := game::game. facts name : string := "игрок".Листинг 9.12. Изменение определения конструктора
Теперь, наконец, мы сможем открыть форму. Форма gameForm открывается при нажатии на кнопку Ok окна settingsDialog. Откроем редактор окна settingsDialog, добавим обработчик события нажатия на эту кнопку и определим его так, как показано ниже.
clauses onOkClick(_Source) = button::defaultAction:- Name = string::trim(name_ctl:getText()), IsDarkStone = toBoolean( radioButton::checked = dark_ctl:getRadioState()), Game = game::new( row_ctl:getInteger(), column_ctl:getInteger(), checkBox_ctl:getChecked(), IsDarkStone), game::game := Game, Form = gameForm::new(getParent()), if Name <> "" then Form:name := Name end if, Form:show().Листинг 9.13. Установка параметров игры
После нажатия на клавишу Ok закрывается окно settingsDialog и открывается окно gameForm.
Реализация игры
Правила хода игрока-компьютера реализуются в классе game. Позиция представляется упорядоченным списком полей, в которых стоят камни. Поле описывается парой индексов и цветом камня.
Реализация выбора хода
В интерфейсе game объявим предикаты gameOver/2 и move/3. Предикат gameOver проверяет, выполнено ли условие окончания игры. Предикат move возвращает индексы поля, в которое игрок-компьютер ставит свой камень.
predicates gameOver: (sq* Position, state Stone) -> positive Result determ. predicates move: (sq* Position, state CompStone, state HumanStone) -> tuple{integer Row, integer Column}.Листинг 9.14. Объявление предикатов в интерфейфсе game
В декларации класса game объявим предикаты insert/3 и isCorrect/3. Первый предикат добавляет поле, в которое сделан ход, в текущую позицию и возвращает новую позицию. Второй предикат проверяет, является ли ход в указанное поле допустимым.
predicates insert: (tuple{integer, integer}, state Stone, sq* Position) -> sq*. predicates isCorrect: (integer Row, integer Column, sq* Position) determ.Листинг 9.15. Объявление предикатов в декларации класса game
Ниже приведено определение объявленных предикатов в имплементации класса game.
Список полей, на которых стоят камни, упорядочивается (см. определение предиката insert). Поле допустимо в данной позиции, если разница между индексами, как по строкам, так и по столбцам, не превышает двух (см. определение предиката isCorrect).
% вставка поля в позицию clauses insert(tuple(I, J), Stone, Pos) = list::sort([sq(I, J, Stone) | Pos]). % проверка допустимости поля clauses isCorrect(I, J, Position):- maxdist(I, J, Position, 2). class predicates maxdist: (integer I, integer J, sq*, integer R) determ. maxdist: (tuple{integer, integer}, tuple{integer, integer}*, integer R) determ. dist: (tuple{integer, integer}, tuple{integer, integer}, integer) determ. dist: (integer, integer) -> integer. clauses maxdist(I, J, L, R):- list::all(L, {(sq(I1, J1, _)):- dist(tuple(I, J), tuple(I1, J1), R)}). maxdist(Sq, ML, R):- list::all(ML, {(Sq1):- dist(Sq, Sq1, R)}). dist(tuple(I1, J1), tuple(I2, J2), R):- math::max(dist(I1, I2), dist(J1, J2)) <= R. dist(I1, I2) = math::abs(I1 - I2).Листинг 9.16. Обновление позиции. Проверка допустимости хода
Предикат all/2 истинен, если указанное условие выполняется для каждого элемента списка. Предикат maxdist истинен, если расстояние от заданного поля до остальных полей не превосходит R. Расстояние определяется как максимальная абсолютная величина разности между номерами строк и столбцов.
Игра закончена, если в текущей позиции имеется три камня одного цвета в ряд, либо на доске стоят девять камней (см. определение предиката gameOver).
clauses gameOver(Position, S) = winner:- % три камня S в ряд existsFullRow(Position, S), !. gameOver(Position, _) = draw:- % ничья 9 = list::length(Position). class predicates existsFullRow: (sq* Position, state Stone) determ. subset: (positive, sq*, state Stone) -> sq* nondeterm. isFullRow: (sq*) determ. clauses existsFullRow(Position, S):- Row = subset(3, Position, S), isFullRow(Row), !. subset(0, _, _) = []:- !. subset(N, [sq(I, J, S) | L], S) = [sq(I, J, S) | subset(N - 1, L, S)]. subset(N, [_ | L], S) = subset(N, L, S). isFullRow([sq(I, J, _), sq(I, J + 1, _), sq(I, J + 2, _)]):- !. isFullRow([sq(I, J, _), sq(I + 1, J, _), sq(I + 2, J, _)]):- !. isFullRow([sq(I, J, _), sq(I + 1, J + 1, _), sq(I + 2, J + 2, _)]):- !. isFullRow([sq(I, J, _), sq(I + 1, J - 1, _), sq(I + 2, J - 2, _)]).Листинг 9.17. Условие окончания игры
Первым аргументом предиката move/3 является текущая позиция — список полей, в которых стоят камни, вторым аргументом — цвет камня компьютера и третьим — цвет камня его противника. Последним аргументом предиката move/4 является список допустимых полей, т. е. полей, в которые игрок-компьютер может сделать ход.
clauses move(Pos, S, S1) = move(Pos, S, S1, getMoves(Pos)). predicates getMoves: (sq*) -> tuple{integer, integer}*. clauses getMoves(Position) = getMoves(Position, ML):- [sq(I, J, _) | _] = Position, !, ML = [tuple(I1, J1) || I1 = std::fromTo(math::max(0, I-2), math::min(I+2, m-1)), J1 = std::fromTo(math::max(0, J-2), math::min(J+2, n-1)), not((I1 = I, J1 = J))]. getMoves(_) = []. predicates getMoves: (sq*, tuple{integer, integer}*) -> tuple{integer, integer}* MoveList. clauses getMoves(Position, ML) = list::filter(ML, {(tuple(I, J)):- not(sq(I, J, _) in Position), isCorrect(I, J, Position)}).Листинг 9.18. Реализация хода игрока. Вычисление допустимых ходов
Ниже приводится реализация правил хода игрока-компьютера.
predicates move: (sq*, state, state, tuple{integer, integer}* MoveList) -> tuple{integer, integer} Move. clauses % последний ход move(_, _, _, [Move]) = Move:- !. % ход по диагонали от первого камня первого игрока move([Sq], _, _, ML) = Move:- MoveList = [M || M = diagMove(Sq, ML)], Move = randomMove(MoveList), !. % три в ряд - выигрыш move(Position, S, _, ML) = Move:- Move = block(ML, Position, S), !. % блок, не приводящий к вилке или прямому проигрышу move(Position, S, S1, ML) = Move:- Move = block(ML, Position, S1), nextPosition(ML, Position, S, Move, Position2, ML2), not((nextPosition(ML2, Position2, S1, _, Position3, ML3), isFork(ML3, Position3, S1))), not(_ = block(ML2, Position2, S1)), !. % блок, если поле определено move(Position, _S, S1, ML) = Move:- isGamePlaceDefined(Position, ML), Move = block(ML, Position, S1), !. % вилка move(Position, S, _S1, ML) = Move:- nextPosition(ML, Position, S, Move, Position1, ML1), isFork(ML1, Position1, S), !. % вынуждаем ставить блок, чтобы поставить вилку move(Position, S, S1, ML) = Move:- nextPosition(ML, Position, S, Move, Position1, ML1), %ход комп Move1 = block(ML1, Position1, S), % ход человека not((nextPosition(ML1, Position1, S1, Move2, Pos0, ML0), Move2 <> Move1, not(_ = block(ML0, Pos0, S)))), nextPosition(ML1, Position1, S1, Move1, Position2, ML2), nextPosition(ML2, Position2, S, _, Position3, ML3), % ход комп isFork(ML3, Position3, S), not(_ = block(ML3, Position3, S1)), !. % ход в центр, если место определено move(Position, _, _, ML) = Move:- Move = centerMove(Position, ML), !. % предотвращение возможной вилки move(Position, S, S1, ML) = Move:- nextPosition(ML, Position, S, Move, Position1, ML1), isGamePlaceDefined(Position1, ML1), not((nextPosition(ML1, Position1, S1, _, Position2, ML2), not(_ = block(ML2, Position2, S)), isFork(ML2, Position2, S1))), not(_ = centerMove(Position1, ML1)), !. % не даем ставить вилку – не исп. move(Position, _S, S1, ML) = Move:- nextPosition(ML, Position, S1, Move, Position1, ML1), isFork(ML1, Position1, S1), !. % случайный ход – не исп. move(_Position, _S, _S1, ML) = randomMove(ML).Листинг 9.19. Правила хода игрока-компьютера
Ниже определяются вспомогательные предикаты
predicates % выбор хода и определение позиции nextPosition: (tuple{integer, integer}*, sq*, state, tuple{integer, integer}, sq*) nondeterm (i,i,i,o,o) determ (i,i,i,i,o). clauses nextPosition(ML, Pos, S, Move, insert(Move, S, Pos)):- Move in ML. predicates % выбор хода, позиции и допустимых ходов nextPosition: (tuple{integer, integer}*, sq*, state, tuple{integer, integer}, sq*, tuple{integer, integer}*) nondeterm (i,i,i,o,o,o) determ (i,i,i,i,o,o). clauses nextPosition(ML, Pos, S, Move, Pos1, getMoves(Pos1, ML)):- nextPosition(ML, Pos, S, Move, Pos1). predicates % блок block: (tuple{integer, integer}*, sq*, state) -> tuple{integer, integer} nondeterm. clauses block(ML, Position, S) = Move:- nextPosition(ML, Position, S, Move, Position1), existsFullRow(Position1, S). predicates % проверка, поставлена ли вилка isFork: (tuple{integer, integer}*, sq*, state) determ. clauses isFork(ML, Position, S):- isGamePlaceDefined(Position, ML), Move1 = block(ML, Position, S), Move2 = block(ML, Position, S), Move1 <> Move2, !. predicates % определен ли участок игры isGamePlaceDefined: (sq*, tuple{integer, integer}*) determ. clauses isGamePlaceDefined(Position, ML):- 9 = list::length(Position) + list::length(ML). predicates % ход в центр centerMove: (sq*, tuple{integer, integer}*) -> tuple{integer, integer} determ. clauses centerMove(Position, ML) = tuple(I, J):- isGamePlaceDefined(Position, ML), tuple(I, J) in ML, maxdist(I, J, Position, 1), maxdist(tuple(I, J), ML, 1), !. predicates % ход по диагонали diagMove: (sq, tuple{integer, integer}*) -> tuple{integer, integer} nondeterm. clauses diagMove(sq(I, J, _), ML) = tuple(I1, J1):- tuple(I1, J1) in ML, 1 = dist(I, I1), 1 = dist(J, J1). class predicates % случайный ход randomMove: (tuple{integer, integer}*) -> tuple{integer, integer}. clauses randomMove(ML) = list::nth(math::random(list::length(ML)), ML).Листинг 9.20. Блок, вилка, центральный и диагональный ходы
Сообщения о ходах игроков
Сообщения о ходе игрока и о результате игры выводятся в строке заголовка окна gameForm. Объявим соответствующие предикаты в интерфейсе gameForm.
predicates moveMessage: (positive Player). gameOverMessage: (positive Result, positive Player).Листинг 9.21. Объявление предикатов в интерфейфсе gameForm
Ниже приведено определение этих предикатов в имплементации класса gameForm.
clauses moveMessage(Player):- setText(string::format("Ходит %", playerName(Player))). clauses gameOverMessage(winner, Player):- !, setText( string::format("Победитель: %!", playerName(Player))). gameOverMessage(_Result, _Player):- setText("Ничья!"). predicates playerName : (positive Player) -> string Name. clauses playerName(humanPlayer) = name:- !. playerName(_) = "компьютер".Листинг 9.21. Определение в имплементации класса gameForm
Реализация хода игры
Ходом игры управляет класс gameContol. Объявим в интерфейсе gameControl необходимые свойства и предикаты. Предикат startGame обрабатывает начало игры. Предикат isCorrect/2 проверяет, является ли допустимым ход пользователя. Предикат humanMove/2 обрабатывает ход пользователя.
properties isGameOver : boolean. predicates startGame: (). isCorrect: (cellControl) determ. humanMove: (cellControl).Листинг 9.22. Объявление предикатов в интерфейфсе gameControl
Определение свойств и предикатов в имплементации класса gameControl приведено ниже.
facts isGameOver : boolean := false. humanStone : state := darkStone. compStone : state := lightStone. currentPosition : sq* := []. clauses startGame():- false = game:isDarkStone, humanStone := lightStone, compStone := darkStone, fail. startGame():- true = game:isComputerFirst, !, gameFrm:moveMessage(compPlayer), Move = firstMove(), showMove(Move). startGame():- gameFrm:moveMessage(humanPlayer). clauses isCorrect(Cell):- isCorrect(Cell:i, Cell:j, currentPosition). clauses humanMove(Cell):- % обработка хода пользователя Cell:setCellState(humanStone), updatePosition(tuple(Cell:i, Cell:j), humanStone), move(compPlayer). % переход хода к компьютеру predicates % первый ход в случайное поле firstMove: () -> tuple{integer, integer}. clauses firstMove() = tuple(F(game:m), F(game:n)):- F = {(N) = math::random(N - 2 * D) + D:- D = if N > 4 then 2 else 1 end if}. predicates updatePosition: (tuple{integer, integer}, state Stone). clauses updatePosition(Move, Stone):- currentPosition := insert(Move, Stone, currentPosition). predicates move: (positive Player). makeMove: (positive Player). clauses move(Player):- gameOver(currentPosition, nextPlayer(Player)), !. move(Player):- gameFrm:moveMessage(Player), makeMove(Player). makeMove(compPlayer):- !, Move = game:move(currentPosition, compStone, humanStone), showMove(Move). makeMove(_). predicates gameOver: (sq*, positive) determ. clauses gameOver(Position, Player):- Result = game:gameOver(Position, stone(Player)), isGameOver := true, gameFrm:gameOverMessage(Result, Player). predicates stone: (positive Player) -> state. nextPlayer: (positive Player) -> positive NextPlayer. clauses stone(compPlayer) = compStone:- !. stone(_) = humanStone. nextPlayer(compPlayer) = humanPlayer:- !. nextPlayer(_) = compPlayer. predicates showMove: (tuple{integer, integer}). clauses showMove(Move):- getCell(Move):setCellState(compStone), updatePosition(Move, compStone), move(humanPlayer). % переход хода к пользователю predicates getCell: (tuple{integer, integer}) -> cellControl. clauses getCell(tuple(I, J)) = Cell:- cell(I, J, Cell), !. getCell(_) = _:- exception::raise_error().Листинг 9.23. Определение в имплементации класса gameControl
Пользователь ходит с помощью мыши. В редакторе окна cellControl добавим обработчик событий MouseDownListener. Ниже приведено его определение.
clauses onMouseDown(_Source, _Point, _ShiftControlAlt, _Button):- empty = state, false = gameCtl:isGameOver, gameCtl:isCorrect(This), !, gameCtl:humanMove(This). onMouseDown(_Source, _Point, _ShiftControlAlt, _Button).Листинг 9.24. Реализация хода пользователя
Остается открыть редактор формы gameForm и добавить обработчик событий ShowListener. Ниже приведено его определение.
clauses onShow(_Source, _Data):- gameControl_ctl:startGame().Листинг 9.25. Начало игры
Упражнения
9.1. Напишите реализацию игры "Четыре в ряд", используя для хода игрока-компьютера правила и оценочную функцию.
9.2. Напишите реализацию игры "Пять в ряд", используя для хода игрока-компьютера правила и оценочную функцию.
9.3. Реализуйте обычную игру "Крестики-нолики" 3 х 3, в которой игрок-компьютер играет на разных уровнях сложности.
9.4. Напишите самообучающуюся программу игры в "Крестики-нолики".