От переводчика: этот пост — перевод оригинальной статьи Маки Чиза, опытного кодера, который не только пишет классные программы, но и демонстрирует возможности различных языков своим коллегам, как новичкам, так и профессионалам.
«Сапер» — веселая игра, многие из нас в нее играют. Может быть, вы хотите сделать «Сапера» сами?
Skillbox рекомендует: Образовательный онлайн-курс «Профессия веб-разработчик».Уникальность игры в том, что она очень простая и при этом весьма увлекательная. В «Сапере» нет никакой хитрой геймплейной механики, вы просто нажимаете на квадратики, надеясь, что под ними нет мины.
Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».
Предлагаю попробовать написать «Сапера» на Processing. Это отличный инструмент, который позволяет создавать графические приложения на Java. Ссылка на него вот здесь.
Прежде чем начать, скажу, что этот туториал рассчитан на тех, кто уже знает Java. Опыт работы с Processing необязателен.
Итак, начинаем. Первый шаг — определение состояния игры. Я решил реализовать это вот так.
int gridW; // grid width
int gridH; // grid height
int numMines; // number of mines on the board
int[][] mines; // entry is 1 for having a mine and 0 for not
boolean[][] flags; // entry is true if you have flagged that spot
boolean[][] revealed; // entry is true if that spot is revealed
Здесь, кажется, имеет смысл все, кроме вот этого участка: int[][] mines. Почему сетка мин является целым числом, а не булевым? Дело в том, что это позволяет легко подсчитать, сколько мин находится рядом с определенной позицией.
int calcNear(int x, int y) {
int i=0;
for (int offsetX=-1; offsetX<=1; offsetX++) {
for (int offsetY=-1; offsetY<=1; offsetY++) {
i+=mines[offsetX+x][offsetY+y];
}
}
return i;
}
Этот код определяет, сколько мин находится рядом с определенным участком. После того, как мы уберем исключения, мы получим что-то похожее на это:
boolean outBounds(int x,int y){
return x<0||y<0||x>=gridW||y>=gridH;
}
int calcNear(int x, int y) {
if(outBounds(x,y))return 0;
int i=0;
for (int offsetX=-1; offsetX<=1; offsetX++) {
for (int offsetY=-1; offsetY<=1; offsetY++) {
if (outBounds(offsetX+x, offsetY+y))continue;
i+=mines[offsetX+x][offsetY+y];
}
}
return i;
}
Главная задача самой игры — раскрывать квадраты, начиная с точки х, у.
void reveal(int x, int y){
if(outBounds(x,y))return;
if(revealed[x][y])return;
revealed[x][y]=true;
if(calcNear(x,y)!=0)return;
reveal(x-1,y-1);
reveal(x-1,y+1);
reveal(x+1,y-1);
reveal(x+1,y+1);
reveal(x-1,y);
reveal(x+1,y);
reveal(x,y-1);
reveal(x,y+1);
}
В итоге мы имеем следующий алгоритм:
- Если позиция за пределами поля, end;
- Если позиция раскрыта, end;
- Раскрываем текущую позицию;
- Если у нас бомба рядом с текущей позицией, end;
- Если мы дошли до этого пункта во время выполнения функции, текущая позиция в пределах игрового поля была обнаружена и рядом с ней нет бомб, открываем смежные квадраты.
В принципе, вся остальная игра представляет собой то же самое. Полный код можно найти, перейдя по этой ссылке.
Если вы хотите узнать о визуализации, читайте дальше.
Визуализация
Здесь все должно быть понятно, но я объясню сложные части.
Итак, у нас есть переменная cellSize, определяющая количество пикселей в каждом квадрате.
void settings(){
size(gridW*cellSize, gridH*cellSize);
}
Таким образом мы создаем поле со сторонами gridW x gridH, где размеры каждого квадрата будут равняться значению cellSize.
Затем мы возвращаем переменные в изначальное состояние.
void setup(){
//initialize and clear the arrays
mines=new int[gridW][gridH];
flags=new boolean[gridW][gridH];
revealed=new boolean[gridW][gridH];
for(int x=0;x<gridW;x++){
for(int y=0;y<gridH;y++){
mines[x][y]=0;
flags[x][y]=false;
revealed[x][y]=false;
}
}
}
Для инициализации поля:
//Place numMines mines on the grid
void placeMines(){
int i=0;
while(i<numMines){//We don't want mines to overlap, so while loop
int x=int(random(gridW));
int y=int(random(gridH));
if(mines[x][y]==1)continue;
mines[x][y]=1;
i++;
}
}
//Clear the mines
void clearMines() {
for (int x=0; x<gridW; x++) {
for (int y=0; y<gridH; y++) {
mines[x][y]=0;
}
}
}
И далее включаем реакцию на клики мышью.
//We don't want the first click to be a mine
boolean firstClick=true;
void mousePressed() {
int x=int(mouseX/cellSize);
int y=int(mouseY/cellSize);
//Right-click is flagging or de-flagging a square
if (mouseButton==RIGHT) {
flags[x][y]=!flags[x][y];
return;
} else {//left-click
//Avoid making the first click a mine
if (firstClick) {
firstClick=false;
do {
clearMines();
placeMines();
} while (calcNear(x,y)!=0);
}
//Check for game loss
if (mines[x][y]!=0) {
println("Dang!");
exit();
} else {//If game not lost, reveal starting from that square
reveal(x, y);
}
}
}
И функция отображения, вызываем ее один раз для каждого кадра.
void draw() {
background(0);
//For each cell
for (int x=0; x<gridW; x++) {
for (int y=0; y<gridH; y++) {
//The colors alternate for texture
color col1=color(2*255/5);
color col2=color(3*255/5);
color txtColor=color(0);
int near=calcNear(x, y);
if (flags[x][y]) {
col1=color(255, 0, 0);
col2=color(4*255/5, 255/5, 255/5);
} else if (revealed[x][y]) {
col1=color(255/2);
col2=color(255/2);
}
if (near==1)txtColor=color(255*1/4, 255*1/4, 255*3/4);
if (near==2)txtColor=color(255*1/4, 255*3/4, 155*1/4);
if (near==3)txtColor=color(255, 0, 0);
if (near==4)txtColor=color(0, 0, 255);
if (near==5)txtColor=color(255, 0, 0);
boolean alternate=(x+y)%2==0;
if (alternate) {
fill(col2);
stroke(col2);
} else {
fill(col1);
stroke(col1);
}
//if(mines[x][y]>0){
//fill(0,255,0);
//stroke(0,255,0);
//}
rect(x*cellSize, y*cellSize, cellSize, cellSize);
//If there is a mine near this spot and it is revealed
if (near>0&&revealed[x][y]) {
fill(txtColor);
noStroke();
textAlign(LEFT, TOP);
textSize(cellSize);
text(""+near, x*cellSize, y*cellSize);
}
}
}
}
И все, вот и наш «Сапер».
Игра выглядит просто, но в целом она полностью функциональна. И помните: «Сапер» вызывает привыкание!
Skillbox рекомендует:
- Онлайн-курс «Профессия frontend-разработчик».
- Практический годовой курс PHP-разработчик.
- Практический курс «Мобильный разработчик PRO».
Комментарии (31)
Aquahawk
04.10.2018 13:54Особенно интересно что классический виндовый сапёр устроен так что при клике первый раз в закрытое поле нельзя подорваться. Он или генерит или модифицирует карту при клике.
Perlovich
04.10.2018 14:31+2Я думаю, гораздо проще генерировать поле при клике, чем модифицировать уже существующее поле.
ninJo
04.10.2018 15:21-1Я на винде играл, часто бывало что несколько раз подряд подрывался на первом клике. Еще на 95ой винде так было
AlexPancho
04.10.2018 15:39нет, на первом нельзя, только на втором
ninJo
04.10.2018 16:32Я точно помню как обламывался проигрывать с первого клика, может даже на верси 3.11 так было, не помню точно какая версия винды, но это было когда я еще был ребенком
www.reddit.com/r/gaming/comments/8sytu3/think_you_suck_at_a_video_game_this_is_my_firstAlexPancho
04.10.2018 17:29+1однако, даже там на скриншоте счетчик ходов с цифой 4.
Вики пишет, что были версии Спаера на геймБой где можно было подрываться на первом ходу, или просто у нас бывают ложные воспоминания.photino
04.10.2018 21:47Если я правильно помню, это не счетчик ходов, а индикатор времени. Но вроде бы он запускался только после первого нажатия на поле. В общем, какая-то странная картинка.
postgres
04.10.2018 19:344 минуты — это скорее всего время требуемое на прочтение этой статьи. После которых можно сказать: «Круто, но делать этого я, конечно, не буду»
fogone
05.10.2018 12:52Хотя за 4 минуты такое конечно не напишешь, но качество кода очень похоже на то, что пытались написать именно за 4.
vadimus
05.10.2018 15:37Как-то учился я на гикбейнсе на курсе JS. Нам задали домашку написать рабочую змейку. Мы написали. Преподаватель сказал, что мы написали плохо и сказал:
— я напишу вам сейчас нормальную змейку за 40 минут.
В итоге он потратил 2 часа, и написал меньше половины. Появилось только поле и змейка побежала.
Правило умножения на 3 работает. Правда, ця думаю, что в случае с 4 минутами надо было умножать на 10 в реале.
dlinyj
Хотелось бы видео, как программист такое пишет за четыре минуты. С секундомером.
maslyaev
Ctrl+C ? Ctrl+V с исходников в статье
dlinyj
Это не спортивно
maslyaev
А демотивировать молодёжь — разве спортивно?
Вот сидит начинающий прогер, втыкается в исходник и понимает, что только на понимание логики у него ушло полчаса. Задаётся резонным вопросом: а смог бы я сделать такое за 4 минуты? Нет, не смог бы. Значит, думает, я жалкий неудачник. И это, видимо, навсегда, и максимум, чего удастся достигнуть в IT — торговать мобильниками в салоне.
dlinyj
Я исключительно поэтому и спросил. Потому, что, говоря про программирование за 4 минуты — это прям демотивирует. Поэтому я и прошу видео.
nikitasius
Мне 32 года и я до сих пор не умею играть в сапера. Мне такое и за месяц не написать.
maslyaev
Мне больше, и я уже научился ;)
Не отчаивайтесь, всё впереди.
svistkovr
Даже копипастом сделать такое нереально за 4 минуты.
Код дан кусками и вам придётся несколько раз копировать.
Код в блокноте не запустится сам. Вам надо развернуть окружение чтоб скомпилить код. Возможно потребуется починить зависимости/пути или установить библиотеку/фреймворк.
Вероятно с первого раза не всё заработает как надо и автор упустил какие-то детали.
Perlovich
Что интересно, ссылка на оригинальную статью содержит слова «10-minutes»: medium.com/@mackycheese21/making-minesweeper-in-10-minutes-e4c4e810fa06
Видимо, автор позже решил, что 10 минут — это не достаточно тру и хардкор. Хотя, разумеется, и за 10 минут это никто не напишет.
dlinyj
Ну вот пускай преподаватели этого горе университета, запишут видео, как они за десять минут пишут сапёр. Без шпаргалок и ctrl-c|ctrl-v
igorp1024
Блин, несколько секунд пытался понять что это за ROPE-университет. %)
MacIn
А заголовок статьи:
Making Minesweeper in 4 minutes
nafgne
Вот тут можно на такое полюбоваться и немного поплакать от качества кода: www.youtube.com/user/shiffman
Tortortor
давайте ссылку на конкретный пример. иначе вас заминусуют, ой…
nafgne
Во-первых, там все видео на такую тематику, во-вторых, мало волнует.