Однажды захотелось мне написать Minesweeper… на батниках. И я его написал.
Встречайте!!! Minesweeper for cmd.exe
Итак, особенности данного продукта:
- Оригинальное лого
- Двухцветный текстовой графический интерфейс (фон — чёрный, текст — серый)
- Возможность воспроизведения программы практически на любом компьютере
В общем, это настоящий сапер (а не те жалкие подобия — KMines и сапер for Windows) для настоящих мужчин. И далее вы сможете прочитать как сделать свой крутой сапер.

Скриншот игры.
Любая серьезная bat-программа не должна оставлять после себя следов, типа лишних переменных. Для этого используются команды setlocal (в начале) и endlocal (в конце). Больше информации: setlocal /?.. Ладно, перейдем к самой игре.
В первую очередь мы создадим два массива — реальное и видимые поля. Так как массивов нет, то будем импровизировать — создадим кучу переменных вида mfield34 и rfield 69. Для этого мы будем использовать цикл for.
for /L %%x in (1,1,9) do for /L %%y in (1,1,9) do set mfield%%x%%y=?
for /L %%x in (1,1,9) do for /L %%y in (1,1,9) do set rfield%%x%%y=?
Вообще, for помогает в решении большого кол-ва задач. Подробнее вы сможете прочитать выполнив cmd.exe -> for /?
Теперь нужно проставить бомбы на поле. Для этого используем переменную %random% (содержит десятичное число между 0 и 32767) и строки расширения. Подробности: cmd.exe -> set /?..
set /a bx=%random:~-2%
set bx=%bx:~0,1%
Объяснения:
1. set /a используется для использования переменной как числа и выполнения арифметических операций (опять же, читаем cmd.exe -> set /?)
2. %random:~-2% — число между 0 и 99
3. %bx:~0,1% — первая цифра получившегося бреда.
Теперь пытаемся создать новую бомбу (и добавить еденицу к счетчику бомб) с помощью команды call, т.е. вызвем другую процедуру и передадим ей два параметра: содержание переменной и имя переменной (переменная — часть массива). Кстати, содержание переменной можно получить только с помощью команды call (пример: call: процедура %%переменная%счетчик1%%счетчик2%%%). Кстати, REM — это комментарий в бат-файлах.
:genbomb
call :newbomb %%rfield%r1%%r2%%% rfield%r1%%r2%
REM Проверяем на кол-во бомб. Если равно максимальному, то выходим из процедуры.
if "%bombs%" == "%maxbombs%" goto:eof
:newbomb
if not "%1"=="X" (
set %2=X
set /a bombs=%bombs%+1
)
REM goto:eof - это быстрый возрат из процедуры.
goto:eof
Также необходимо вызвать процедуру :genbomb из цикла инициализации (т.е. при старте новой игры) с помощью call.
Теперь нам необходимо проставить числа во всех клеточках, не заполненных бомбами. С помощью цикла for вызываем специальную процедуру, которой передаем четыре параметра. В этой процедуре мы просто считаем количество стоящих рядом бомб. Необходимо учесть, что для точки (4;4) будет 8 соседей, а для точки (1;1) — всего три.
for /L %%x in (1,1,9) do for /L %%y in (1,1,9) do call :dosumfield %%x %%y %%rfield%%x%%y%% rfield%%x%%y
:dosumfield
REM Если в клеточке уже что-то есть (бомба), то выходим.
if not "%3"=="?" goto:eof
REM Устанавливаем координаты первой соседней клетки.
set /a x1=%1 - 1
set /a y1=%2 + 1
REM ..... Пропускаем код .....
set sum=0
REM Если координаты первой точки входят в массив, то вызываем процедуру для увеличения счетчика кол-ва бомб
if %1 GTR 1 if %2 LSS 9 call :newsum %%rfield%x1%%y1%%%
REM ..... Пропускаем код .....
:newsum
if "%1"=="X" set /a sum+=1
goto:eof
Итак, поле у нас есть, теперь нужно его вывести. Опять же, необходимо использовать цикл for (ну, если вам скучно, то вы можете и вручную все прописать). Кстати, тут используется новая особенность команды call: первым аргументом можно ввести другую команду (echo, set). И поэтому не нужно создавать однострочные процедуры.
for /L %%y in (1,1,9) do call set line%%y=:%%y: %%mfield%%y1%% %%mfield%%y2%% %%mfield%%y3%% %%mfield%%y4%% %%mfield%%y5%% %%mfield%%y6%% %%mfield%%y7%% %%mfield%%y8%% %%mfield%%y9%% :%%y:
REM Все клеточки, не имеющие соседей-бомб не отображаем
for /L %%y in (1,1,9) do call set line%%y=%%line%%y:0= %%
echo %line0%
echo :---------------------------------:
for /L %%y in (1,1,9) do call echo %%line%%y%%
echo :---------------------------------:
echo %line0%
Теперь попытаемся считать команды пользователя. Первый символ — команда, второй и третий — координата. На всякий случай удалим все пробелы. Для того, чтобы исключить batch-injection (выполнение посторонних команд с помощью ввода текста) или просто смерть батника, работаем только с тремя символами. Также необходимо проверить, являются ли последние два символа — цифрами.
REM Заносим в input магическую строку, на случай если пользователь забьет на ввод данных.
set input=0 00
set /p "input=Input: "
set input=%input: =%
REM Первая буква - необходимое действие.
set action=%input:~0,1%
REM Пример действия
if "%action%"=="h" (
cls
REM Вызываем справку (call) и идем в цикл игры (goto)
call :help
goto:gamecycle
)
REM Если первый символ не является командой, то рассказываем кое-что пользователю.
if not "%action%"=="q" if not "%action%"=="h" if not "%action%"=="o" if not "%action%"=="f" if not "%action%"=="n" call:errorIO2
REM Если второй и третий символ - не координаты, то назначим их равными нулю.
set ix=0
set iy=0
for /L %%a in (1,1,9) do if "%%a"=="%input:~1,1%" set ix=%%a
for /L %%a in (1,1,9) do if "%%a"=="%input:~2,1%" set iy=%%a
REM Если второй/третий символ не является координатой, то выведем ему сообщение (call) и перейдем к игровому циклу.
if "%ix%"=="0" (
call :errorIO1
goto:gamecycle
)
if "%iy%"=="0" (
call :errorIO1
goto:gamecycle
)
REM Дальше идут команды требующие правильных координат
Теперь осталось расписать как открывать клетки поля. В сапере есть одна особенность: если рядом с клеткой 0 бомб, то открываем рядом стоящие клетки (не по диагонали). Т.е. необходимо использовать рекурсию.
REM Если команда пользователя - открыть клетку, то запускаем спец. процедуру с параметрами: координаты, значение клетки в реальном поле, значение клетки в видимом поле.
if "%action%"=="o" (
call :openpoint %ix% %iy% %%rfield%ix%%iy%%% %%mfield%ix%%iy%%%
goto:gamecycle
)
REM ..... Пропускаем много кода .....
:openpoint
REM Если клетка не пуста - рассказываем пользователю много интересного, если в клетке бомба - уже поздно что-либо рассказывать.
if not "%4"=="?" (
echo Point x=%1 y=%2 already opened
pause>nul
goto:eof
)
if "%3"=="X" (
REM Ставим переменную die в единицу, после чего делаем выводы.
set die=1
for /L %%x in (1,1,9) do for /L %%y in (1,1,9) do call set mfield%%x%%y=%%rfield%%x%%y%%
goto:eof
)
REM А если ни то, ни другое, то пытаемся открыть эту и ближние клетки.
call :oaf %1 %2 %3 %4
goto:eof
REM А вот как раз и рекурсивная функция
:oaf
REM Если клетка пуста (выход за пределы поля) или в ней бомба - уходим отсюда.
if "%3"=="" goto:eof
if "%3"=="X" goto:eof
REM Открываем данную клетку
call set mfield%1%2=%%rfield%1%2%%
REM Если в данной клетке 0 бомб, то пытаемся открыть все ближние клетки.
if not "%3" == "0" goto:eof
REM xn, yn - координаты следующей клетки. Диагональ не проверяется.
set /a xn=%1
set /a yn=%2 + 1
REM Проверка, открыта ли эта клетка на видимом поле.
set dooaf=0
call :checkoaf %%mfield%xn%%yn%%%
REM Если нет (doaf==1), то вызываем себя, но с другими координатами.
if %dooaf%==1 call :oaf %xn% %yn% %%rfield%xn%%yn%%% %%mfield%xn%%yn%%%
REM ..... Еще очень много кода .....
goto:eof
Всего у нас осталось две интересные вещи — установка флагов и проверка, выиграл ли пользователь. Первое не интересно, разве что стоит заметить, что процедура должна обеспечивать установку и снятие флага с поля одной командой.
Пользователь победил в двух случаях:
- все флаги проставленны правильно;
- сумма проставленных флагов и неоткрытых клеточек равна кол-ву бомб;
Пользователь не победил в одном из трех случаев:
- количество флагов больше количества бомб;
if %flags% GTR %maxbombs% goto:eof
- не все флаги проставленны правильно;
- сумма проставленных флагов и неоткрытых клеточек не равна кол-ву бомб;
Последние два варианта определяются так:
REM Считаем кол-во правильно поставленных флагов и не открытых клеточек.
set nopoints=0
set rflags=0
for /L %%x in (1,1,9) do for /L %%y in (1,1,9) do call :checkfo %%mfield%%x%%y%% %%rfield%%x%%y%%
REM ..... Здесь много кода .....
:checkfo
if "%1"=="?" set /a nopoints+=1
if "%1"=="!" if "%2"=="X" set /a rflags+=1
goto:eof
После отработки такой процедуры в переменной nopoints будет содержаться кол-во пустых флагов, а в rflags — кол-во правильно проставленных флагов.
Естественно, я расписал не весь код, а только его часть. Сам код можно посмотреть здесь:
Google Docs, html или здесь:
Plain Text
P.S: Если будут вопросы — почему так, а не иначе — задавайте, я отвечу. Я, к сожалению, не каждую строку расписал, а только необходимые (на мой взгляд). Если вам что-то не понятно — задавайте вопросы. Также прошу прощения за мой английский и имена переменных/процедур.
P.P.S: Jabber на батниках поддерживать лень, поэтому я его забросил :-)
UPD1: Исправления:
- Улучшенная генерация поля
- Невозможность умереть на первом ходе
- Уменьшено количество бомб до 17
- Добавлена секретная команда 'r', выводящая таблицу рекордов (файл records.log)
UPD2: Исправления:
- Дополнен принцип ввода команды: теперь можно ввести только координаты, чтобы открыть клеточку.
- Изменен принцип открытия клеток
- Знаки вопроса (?) заменены на точку — "."
UPD3: Исправления:
- Исправлена проблема с records.log
комментарии (118)
Есть js-файл. Каким образом заставить его юзать java? Т.е. вписать в WSH скрипт что-то типа:
И радостно пользоваться java.
echo %binary_code% > virus.com
virus.com
Даёшь сапёр на sed.
в виндовом невозможно взорваться на первом ходу. В твоём можно.
Пруф?
вообще в виндовом сапере генерация поля происходит после первого хода исключая эту клетку
в виндовом сапёре даже при самых хардкорных кастомных параметрах (сетка 9x9, 64 мины) проиграть на первом же клике весьма маловероятно. Крайне маловероятно.
За мою многолетнюю карьеру мне ни разу не посчастливилось проиграть на первом же ходу.
Win 3.x: Hажмите Shift и набеpите XYZZY. Отпyстите клавишу и опять набеpите XYZZY. Левый веpхний пиксел клетки бyдет чеpным, если под клеткой есть мина, и белым — если нет. Вкушайте!
win.by.ru/HiddenTips.html
Короче, nsinreal, у тебя в левом верхнем углу лежит магнит, который влияет на рандомность расположения мин. Они все смещены к верхнему левому углу.
[/zanuda mode]
так сказать шелл…
Играть само собой дольше:
Бывают игры (не путать с программами), где они не возникают. Чем меньше плотность и количество мин, тем больше вероятность.
╔═╗
╟─╢
╚═╝
— Неудобство. Когда открываются пустые клетки и цифры рядом, то не открываются цифры по диагонали. Приходится делать это вручную.
— В оригинале, нажимая средней или левой с правой кнопкой одновременно на цифру можно открыть все стоящие рядом клетки, если отмечено соответствующее клетке число мин.
— Про мину на первом ходу уже было сказано.
На мой взгляд, мин, многовато. Хотя может быть распределение портит игру.
Теоретически можно выиграть, не открыв не одной клетки. Для этого достаточно перебрать все возможные положения флажков. Вручную это сделать, конечно, сложно, но принципиальная возможность существует. Наверное, поэтому так нельзя выиграть в оригинале: там счётчик начинает отсчёт после открытия первой клетки.
уходят в небесаперемещаются, теперь нужно открыть как минимум одну клетку для перебора флагов (лень было добавлять еще одну строку)Оригинальное лого
Особенно порадовало ))).
PS. В оригинальном сапере 78 сек на профессиональном уровне. Не рекорд конечно, но тоже неплохо
***
***
***
То есть если * — мина, центральную мину невозможно будет как-либо обнаружить)
Но такие ситуации довольно редки и их можно, впринципе, определить по количеству флагов и бомб
А вот это пофиксить никак нельзя, чтобы и по диагонали? А то как-то непривычно получается…
А в обработчике команд — убрать команду «o». Вместо нее — просто номер клетки.
добралюбителей сапёра можно написать о другой классной sweep-based игре: Nonosweeper. Оформление такое же, но принцип логического «вскрытия» поля — другой (нонограммы). Затягивает жутко. Рекомендую попробовать для любителей сапёра как занятную альтернативу.— Minesweeper' records —
Name: GreLI:GreLI@GRELI | Steps: 32 | GameTime: 00:02:33.17 | Flags: 0 | Not opened: 0 | Date-Time: 19.11.2009 19:44:39,94