Пять лет назад мы с друзьями придумали одну простенькую, но довольно забавную игру, которую тогда назвали – “фие” (fier в переводе с французского, если не ошибаюсь, означает “сетка”).
Правила игры довольно просты: есть клетчатое поле, в каждой клетке которого стоит случайная цифра от 1 до 9, игроку нужно выделять линии из цифр, дающих в сумме число 10. Естественно, линии из выделенных цифр не могут пересекаться и не могут иметь разломов.
Но тогда (более 5 лет назад) я не мог реализовать идею создания подобной игры для компьютера…
Однако, время не стоит на месте – и я все-таки смог сделать реализацию фие на Icon.
Итак первая задача, возникающая в ходе такой реализации – это генерация сетки со случайными цифрами, но она легко решается с помощью графических процедур Icon:
procedure grid_start()
local i
Fg("black")
i:=0
while i<21 do {
DrawLine(i*20,0,i*20,400)
DrawLine(0,i*20,400,i*20)
i+:=1
}
end
procedure grid_fill()
local i,j,n
i:=0
num:=list()
Fg("blue")
while i<20 do {
j:=0
while j<21 do {
randomize()
n:=?9
DrawString((i*20)+6,(j*20)-4,n)
put(num,i,j,n)
j+:=1
}
i+:=1
}
end
Процедура start_grid() рисует саму сетку, а процедура grid_fill() заполняет сетку случайными цифрами, занося их в глобальную переменную num (которая является списком и которая пригодиться нам далее).
Следующая задача – это определить координаты каждой клетки, но не в пикселях, а в “клетках” (т.е. в квадратиках на которые сейчас разбита плоскость), но сделать это проще в отдельных процедурах.
В решении этой задачи нам поможет геометрия, а именно те неравенства, которые определяют принадлежность точки, на которую указывает курсор мыши (после нажатия ее левой клавиши) к некоторому прямоугольнику, который в нашем случае будет размерами 20*20 и он образован линиями сетки:
procedure find_x(n)
local i,x,xx
i:=0
while i<20 do {
x:=i*20
xx:=x+20
if x<=n<=xx then return i
i+:=1
}
end
procedure find_y(n)
local i,y,yy
i:=0
while i<20 do {
y:=i*20
yy:=y+20
if y<=n<=yy then return i
i+:=1
}
end
Процедура find_x() определяет координату Х в “квадратиках”, а процедура find_y() определяет координату Y. Как видно, каждая процедура принимает один аргумент – которым в нашем случае будут координаты курсора после щелчка левой клавишей мыши (т.е. переменные &x и &y).
Далее необходимо получить цифру, расположенную в клетке с уже известными нам координатами, для чего необходимо сделать извлечение этой цифры из списка num:
procedure find_el(x,y)
local i,r
i:=1
while i<(*num)+1 do {
r:=num[i+:3]
if x=r[1] & r[2]=y+1 then return r[3]
i+:=3
}
end
После этого можно уже обозначить действия пользователя. Проще всего, реализовать отмену неверного хода. Отмена хода будет производиться следующим способом: сначала получаем координаты квадратика, потом через эти координаты узнаем цифру, которая была в квадратике, затем обнуляем глобальную переменную s, накапливающую сумму цифр, используемую для подсчета найденных линий, затем закрашиваем белым квадратик (почему так, объясню ниже) и вновь записываем в квадратик цифру:
procedure user_cancel()
local n
x:=find_x(&x)
y:=find_y(&y)
n:=find_el(x,y)
s:=0
Fg("white")
FillRectangle(x*20,y*20,20,20)
Fg("blue")
DrawString((x*20)+6,((y+1)*20)-4,n)
return n
end
А теперь собственно ход пользователя.
Ход пользователя будет состоять в следующем – пользователь просто щелкает левой клавишей мыши по квадратику с нужной цифрой и квадратик выделяется желтым цветом, при этом цифра, бывшая в квадратике попадает в переменную s.
Далее, если s оказывается равной 10, то происходит прибавление 1 очка в глобальную переменную comb, которая в самом начале программы, как и переменная s, установлена в 0, также эта процедура выводит на экран количество найденных пользователем линий.
Итак процедура для пользовательского хода:
procedure user_move()
local x,y,n
x:=find_x(&x)
y:=find_y(&y)
n:=find_el(x,y)
Fg("yellow")
FillRectangle(x*20,y*20,20,20)
Fg("blue")
DrawString((x*20)+6,((y+1)*20)-4,n)
s+:=n
if s=10 then {
comb+:=1
Fg("white")
FillRectangle(440,0,100,50)
Fg("red")
DrawString(440,20,"User: "||comb)
put(mv,x,y)
s:=0
}
end
Вот и вся “игровая часть”, а теперь оргвопросы, т.е. процедура main, в которой надо установить некоторые переменные в ноль (предварительно определив их как глобальные), а также сделать окно размером 500*500 пикселей и вывод количества найденных линий.
Процедура main() с помощью оператора case … of будет обрабатывать ход пользователя (нажатие левой кнопки мыши на квадратик) и отмену хода (нажатие правой кнопки мыши), используя процедуру Event() из библиотеки graphics:
link graphics,random
global num,s,comb,mv
procedure main()
local W,x,y,n
W:=WOpen("label=fier","size=500,500")
grid_start()
grid_fill()
s:=0
comb:=0
mv:=list()
Fg("red")
DrawString(440,20,"User: "||comb)
repeat {
case Event() of {
&lpress : user_move()
&rpress : user_cancel()
"q" : exit()
}
}
WDone()
end
Как видно, нам еще потребовалась библиотека random для генерации случайных значений.
А вот как все выглядит:
P.S: В программе есть несколько неиспользуемых переменных, которые планировались для реализации “неповторения хода” пользователем, а также для реализации ходов компьютера… Но, увы, идей по первому пункту не возникло (все перепробовал), так что надеюсь на Вас (ну а по поводу ходов компьютера я еще подумаю).
P.P.S: Не слишком увлекайтесь игрушкой – очень давит на глаза!
[accordion][panel intro=”Update 09.04.2016. Новая версия, в которой поправлены некоторые баги”]
link graphics
procedure main()
local t
W:= WOpen("label=test","size=550,500")
gameFier()
WDone()
end
# генерация таблицы для сетки
procedure drawGrid()
local i
every i := 0 to 20 do {
DrawLine(i*20,0,i*20,400)
DrawLine(0,i*20,400,i*20)
}
end
# генерация цифр для сетки
# возврат: игровое поле
procedure placeDigits()
local i,j,t
t := table()
Fg("blue")
every i := 0 to 19 do {
every j := 0 to 19 do {
t[i || ":" || j] := ?9
DrawString(10+i*20,15+j*20,t[i || ":" || j])
}
}
return t
end
# получить координаты в квадратиках
# возврат: список с двумя координатами
procedure getXY()
local i,j,l
l := list()
repeat {
if Event() = &lpress | &rpress then {
every i := 0 to 19 do {
if (i * 20) <= &x <= ((i * 20) + 20) then put(l,i)
}
every j := 0 to 19 do {
if (j * 20) <= &y <= ((j * 20) + 20) then put(l,j)
}
return l
}
}
end
# пометить ячейку
# аргументы: список с двумя координатами, игровое поле, цвет пометки
# возврат: помеченная цифра
procedure markCell(XY,gridtable,color)
local digit
Fg(color)
digit := gridtable[XY[1] || ":" || XY[2]]
FillRectangle(1+20*XY[1],1+20*XY[2],19,19)
Fg("blue")
DrawString(10+XY[1]*20,15+XY[2]*20,digit)
return digit
end
# обновление счета
# аргументы: текущий счет
procedure updateScore(score)
Fg("white")
FillRectangle(450,0,100,20)
Fg("red")
DrawString(450,20,"Score: " || score)
end
procedure gameFier()
local acc,digit,sum,gridtable
acc := 0
sum := 0
drawGrid()
t := placeDigits()
updateScore(sum)
repeat {
case Event() of {
&rpress : {
markCell(getXY(),t,"white")
acc := 0
}
"q" | "Q" : exit()
&lpress : {
digit := markCell(getXY(),t,"yellow")
acc +:= digit
if acc = 10 then {
sum +:= 1
updateScore(sum)
acc := 0
}
}
}
}
end
[/panel][/accordion]