Реализуется следующий вариант игры "Крестики-нолики". Игра ведется на прямоугольном поле размером m х n, где $$$m\geq3$$$ и $$$n\geq3$$$, но место для игры (игровой участок) не должно выходить за пределы квадрата, содержащего девять клеток (3 х 3). В отличие от обычной игры, этот участок определяется только в процессе игры. Выигрывает игрок, поставивший в ряд по вертикали, по горизонтали или по диагонали три своих камня. Если участок заполнен полностью, а ни одному игроку не удалось поставить три камня в ряд, то признается ничья.
Первый игрок первым ходом может поставить камень в любое поле. Игроки ходят по очереди. Они могут ходить только в доступные поля, так чтобы в результате все камни оказались на участке размером 3 х 3. Например, если первый игрок поставит камень так, как показано на рис. 9.1 ( , то второй игрок может ходить в любое поле, находящееся внутри выделенного квадрата, доступными для него являются 24 поля. После хода второго игрока, показанного на рис. 9.1 ( , место игры сужается, доступными для первого игрока остаются 14 полей. Если первый игрок пойдет так, как показано на рис. 9.1 ( , то место игры будет полностью определено.
(рис 9.1) Определение игрового участка
Нетрудно заметить, что у первого игрока больше шансов выиграть. Тем не менее правильная игра, т. е. игра, в которой оба игрока ходят наилучшим для себя образом, всегда завершится вничью.
Если игрок ставит камень в ряд, в котором уже имеются два камня противника, то такой ход называется блоком, он предотвращает немедленный выигрыш противника. Вилкой называется ход, после которого возникает два различных ряда, содержащих по два камня игрока и не содержащих камней противника; на пересечении этих рядов должен стоять камень. Если участок игры будет полностью определен, то противник сможет заблокировать только один из таких рядов, поэтому у игрока останется возможность поставить камень третьим в другой ряд и выиграть. Если же участок игры не будет полностью определен, то и выигрыш, вообще говоря, не будет предопределенным. Поэтому вилкой в данной игре будет называться только такой ход, после которого участок игры будет полностью определен.
Ниже перечисляются правила, в соответствии с которыми ходит игрок-компьютер. Они приводятся в том же порядке, в котором реализуются в программе. Формулировка правил — "декларативная", по возможности самая общая, без уточнения всех случаев (см. программу). Правила реализуются просто и в общем виде за счет использования списков. Списки в языке Пролог — это сохраняющий (persistent) тип данных, поэтому их удобно использовать для просчитывания позиции на шаг или на несколько шагов вперед.
1. Если противник сделал первый ход, то игрок ставит камень в соседнее по диагонали поле.
После первого хода первого игрока, второй игрок имеет, самое большее, пять вариантов хода, с точностью до симметрии (см. рис. 9.1 ( , где они отмечены точками). В четырех из них он проиграет в игре с правильным игроком-противником (см. ниже правило 6). Остается ход по диагонали в соседнее поле. В программе такое поле выбирается случайным образом.
2. Игрок ставит свой камень третьим в ряд и выигрывает.
3. Игрок ставит блок так, чтобы противник не мог в ответ поставить вилку или поставить камень третьим в ряд.
На рис. 9.2 ( приведен пример позиции, в которой применяется правило 3. Ходит игрок темными. Он должен поставить камень в поле, находящееся непосредственно слева или справа от двух светлых камней, в противном случае он немедленно проиграет. Если игрок поставит камень слева от камней противника, то тот сможет сразу поставить вилку и выиграть. Поэтому игрок должен ходить так, как показано на рис. 9.2 (.
(рис 9.2) Блок с предотвращением вилки, ходит игрок темными
4. Если участок игры полностью определен, то игрок ставит блок.
5. Игрок ставит вилку.
6. Игрок делает такой ход, чтобы противник был вынужден поставить блок, так чтобы следующим ходом игрок мог поставить вилку.
На рис. 9.3 ( рис 9.6 приведены примеры позиций, в которых для выбора хода игрока применяется правило 6. Ходит игрок темными. Правильные ходы показаны на рис. 9.3 ( рис 9.6. В каждом случае противник вынужден ставить блок, после чего игрок ставит вилку (рис. 9.3 ( рис 9.6 ).
(рис 9.3) Принуждение к блоку с последующей вилкой. Позиция 1
(рис 9.4) Принуждение к блоку с последующей вилкой. Позиция 2
(рис 9.5) Принуждение к блоку с последующей вилкой. Позиция 3
(рис 9.6) Принуждение к блоку с последующей вилкой. Позиция 4
7. Если участок игры полностью определен, то делается ход в центральное поле.
На рис. 9.7 ( приведен пример позиции, в которой игрок темными ходит в центральное поле игрового участка (рис. 9.7 ( . Нетрудно заметить, что если он сделает любой другой ход, то противник сможет поставить ему вилку.
(рис 9.7) Ход в центральное поле
8. Если центральное поле занято, то игрок ходит так, чтобы участок игры был определен, и при этом противник ответным ходом не мог поставить вилку.
На рис. 9.8 ( приведен пример позиции, в которой игрок темными выбирает ход по правилу 8 (рис. 9.8 (. Если он пойдет в угловое поле игрового участка, то противник сможет поставить ему вилку.
(рис 9.8) Предотвращение вилки противника
9. Игрок занимает поле, ходом в которое противник поставил бы ему вилку.
10. Игрок делает случайный ход в доступное поле (последние два правила программой фактически не используются).
Создадим проект tictactoe (MDI). После запуска приложения должно открыться окно установки параметров, в котором пользователь сможет указать размеры поля, игрока, который будет ходить первым, и цвет камней.
С помощью диалогового окна Create Project Item создадим диалоговое окно settingsDialog.
(рис 9.9) Диалоговое окно установки параметров
Разместим в нем следующие элементы управления (рис. 9.9 рис 9.9):
Static Text) "Ваше имя", "Количество строк", "Количество столбцов", "Ходит первым";Edit Control):Custom Control; Class: integerControl):Check Box):Group Box):Radio Button):Теперь следует включить пункт меню ) и определить его следующим образом:
clauses
onFileNew(_Source, _MenuTag):-
_ = settingsDialog::display(This).
Для того чтобы окно установки параметров открывалось сразу при запуске приложения, изменим также определение предиката onShow в имплементации класса taskWindow так, как показано ниже:
clauses
onShow(_, _CreationData):-
_MessageForm = messageForm::display(This),
_ = settingsDialog::display(This).
Создадим класс 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. % польз. играет темными
В декларации класса game объявим конструктор и свойство.
constructors
new: (integer M, integer N, boolean CompFirst, boolean DarkSt).
properties
game : 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.
С помощью диалогового окна Create Project Item создадим поле для игры (Control) и назовем его gameControl. Закроем редактор окна gameControl.
Далее создадим форму ().
(рис 9.10) Форма для игры
Затем с помощью диалогового окна Create Project Item создадим клетку (Draw Control) под названием cellControl. Добавим в интерфейс cellControl объявление свойств и предиката setCellState.
properties
gameCtl : gameControl.
i : integer.
j : integer.
state : game::state.
predicates
setCellState : (game::state).
В имплементацию класса 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().
Далее добавим в интерфейс gameControl объявление свойств, хранящих указатели на объекты классов game и gameForm.
properties
game : game.
gameFrm : gameForm.
Изменим раздел 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.
В редакторе окна gameControl добавим обработчики событий ShowListener и SizeListener. Определение предикатов приведено ниже.
clauses
onShow(_Source, _Data):-
createControls(),
setControls().
clauses
onSize(_Source):-
setControls().
При изменении размеров окна пропорциональным образом изменяется размер клеток и камней.
В восточных играх камни часто ставятся на пересечение линий сетки.
В редакторе окна 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.
В интерфейсе gameForm объявим свойство для хранения имени игрока-пользователя.
properties
name : string.
Изменим раздел 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 := "игрок".
Теперь, наконец, мы сможем открыть форму. Форма 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().
После нажатия на клавишу 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}.
В декларации класса game объявим предикаты insert/3 и isCorrect/3. Первый предикат добавляет поле, в которое сделан ход, в текущую позицию и возвращает новую позицию. Второй предикат проверяет, является ли ход в указанное поле допустимым.
predicates
insert: (tuple{integer, integer}, state Stone, sq* Position) -> sq*.
predicates
isCorrect: (integer Row, integer Column, sq* Position) determ.
Ниже приведено определение объявленных предикатов в имплементации класса 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).
Предикат 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, _)]).
Первым аргументом предиката 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)}).
Ниже приводится реализация правил хода игрока-компьютера.
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).
Ниже определяются вспомогательные предикаты
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).
Сообщения о ходе игрока и о результате игры выводятся в строке заголовка окна gameForm. Объявим соответствующие предикаты в интерфейсе gameForm.
predicates
moveMessage: (positive Player).
gameOverMessage: (positive Result, positive Player).
Ниже приведено определение этих предикатов в имплементации класса 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(_) = "компьютер".
Ходом игры управляет класс gameContol. Объявим в интерфейсе gameControl необходимые свойства и предикаты. Предикат startGame обрабатывает начало игры. Предикат isCorrect/2 проверяет, является ли допустимым ход пользователя. Предикат humanMove/2 обрабатывает ход пользователя.
properties
isGameOver : boolean.
predicates
startGame: ().
isCorrect: (cellControl) determ.
humanMove: (cellControl).
Определение свойств и предикатов в имплементации класса 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().
Пользователь ходит с помощью мыши. В редакторе окна cellControl добавим обработчик событий MouseDownListener. Ниже приведено его определение.
clauses
onMouseDown(_Source, _Point, _ShiftControlAlt, _Button):-
empty = state,
false = gameCtl:isGameOver,
gameCtl:isCorrect(This),
!,
gameCtl:humanMove(This).
onMouseDown(_Source, _Point, _ShiftControlAlt, _Button).
Остается открыть редактор формы gameForm и добавить обработчик событий ShowListener. Ниже приведено его определение.
clauses
onShow(_Source, _Data):-
gameControl_ctl:startGame().
Упражнения
9.1. Напишите реализацию игры "Четыре в ряд", используя для хода игрока-компьютера правила и оценочную функцию.
9.2. Напишите реализацию игры "Пять в ряд", используя для хода игрока-компьютера правила и оценочную функцию.
9.3. Реализуйте обычную игру "Крестики-нолики" 3 х 3, в которой игрок-компьютер играет на разных уровнях сложности.
9.4. Напишите самообучающуюся программу игры в "Крестики-нолики".
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.