Пять лет назад мы с друзьями придумали одну простенькую, но довольно забавную игру, которую тогда назвали — «фие» (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: Не слишком увлекайтесь игрушкой — очень давит на глаза!
- 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