Игра “фие”

Пять лет назад мы с друзьями придумали одну простенькую, но довольно забавную игру, которую тогда назвали – “фие” (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]

Добавить комментарий