Пользователь
0,0
рейтинг
18 ноября 2009 в 17:51

Разработка → Свой сапер на своих батниках

image

Однажды захотелось мне написать Minesweeper… на батниках. И я его написал.

Встречайте!!! Minesweeper for cmd.exe

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

image
Скриншот игры.

Любая серьезная 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
Виктор Лова @nsinreal
карма
9,5
рейтинг 0,0
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (118)

  • НЛО прилетело и опубликовало эту надпись здесь
    • НЛО прилетело и опубликовало эту надпись здесь
    • +3
      Пошел учить как писать батники :)
      • +1
        Да зачем? Есть Windows script Host и PowerShell.
        • +2
          Так не интересно
          • +2
            Ну, я сам люблю творить всякие безумные штуки :) Но учить язык батников, чтобы писать просто батники ни к чему.
            • 0
              Ну вот, изучаю WSH. Возник вопрос.

              Есть js-файл. Каким образом заставить его юзать java? Т.е. вписать в WSH скрипт что-то типа:
              MyIP = new String(java.net.InetAddress.getLocalHost().getHostAddress())

              И радостно пользоваться java.
              • +1
                Если Java экспортирует COM-объекты — всё можно сделать.
              • 0
                Почему вы решили что это так должно работать?
        • 0
          Я его уже знаю :)
          • 0
            Учите ассемблер и структуру exe файлов. Сможете написать заметку как с помощью debug.exe написать сапер. Это будет гораздо круче!!!
            • НЛО прилетело и опубликовало эту надпись здесь
  • +28
    ты безумен!
  • +6
    Отлично! Ждем «Морской бой» и его сетевую версию.
    • +4
      Хорошо, почитаю правила морского боя и постараюсь сделать. По поводу сети — необходимо работать с файлами через сетевой каталог, никаких сокетов и телнетов.
      • +2
        Хотя… Еще ftp есть. Но это уже будет не default-only — для создания ftp-сервера нужно самому выполнить установку ftp-сервера
        • 0
          можно поднять один ftp в интернете «на всех», зашить его настройки, а «сетевая игра» переименовать в «игра по интернету»
          • 0
            Ага, и туда все кому не лень сразу файлов понакидают :)
            • +2
              Если добавить ауторизацию по паролю…
              • +1
                … который опять же любой желающий сможет выковырять из батника. (Спасибо, что оставили многоточие в конце для возможности продолжения :)).
                • 0
                  А вот если имя/пароль вводить ручками и в батнике просто отправлять данные ftp-серверу…
          • +1
            Ну да, единственное — будут некоторые траблы с правами доступа. Ну никак не хочется чтобы мой сервер был дыряв. Решить м.б. можно паролями.
      • 0
        А сервер с доступом по телнету не подойдет?
        • +1
          Сколько я не рыскал по форумам — управлять телнетом невозможно. Может быть такая штука есть в wsh, но вряд-ли
      • +1
        А netcat нету? Просто спросил, правда правда.
        • +1
          Честно, нету в стандартной поставке Windows. Да и не во всяком дистрибутиве сразу после установки встретишь.
      • +2
        На «Хабре» были крестики-нолики по сети на батниках, посмотрите там, там через сетевую шару было сделано.
    • +2
      О! Ждём сразу Quake ]|[…
  • +1
    Аааааа!!!
  • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Ну теперь то я смогу её пройти на последнем уровне?
  • 0
    Следующей программой будет Сапёр на Brainfuck? :)
    • +10
      Нет, следующей программой должен был BrainFuck. Но из-за особенностей восприятия треугольных скобок cmd.exe, это не является осуществимой задачей.
      • 0
        Тогда впору подумать о Whitespace (http://en.wikipedia.org/wiki/Whitespace_%28programming_language%29 ) :)
  • +1
    Хорошо!
    Даёшь сапёр на sed.
    • 0
      Да чего уж там! Даешь Galaxy Invaders!!! :)
    • 0
      Вам не хватает тетриса? :)
      • 0
        и целого мира мало ;)
  • 0
    Полчаса из жизни за игрой улетели :)
  • 0
    а виндовая cmd.exe не позволяет расскаживать текст?
    • 0
      Насколько я знаю, можно сменить только общий цвет фона и общий цвет символов, и все. Так что, в любом случае два цвета.
  • +2
    неправильный у тебя сапёр получился…
    в виндовом невозможно взорваться на первом ходу. В твоём можно.
    • +3
      Я совершал невозможное.

      Пруф?
      • 0
        возможно это было на старых версиях?
        вообще в виндовом сапере генерация поля происходит после первого хода исключая эту клетку
        • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        респект тебе, повторить сможешь? :)
        • –1
          Не смогу, в виду отсутствия у меня виндового сапёра, а также винды и wine'а :)
      • 0
        пруф с вас, собственно.
      • –2
        Ага, сапер из Вин 98. Не одну пару так коротал и подтверждаю, там случался взрыв на первом ходе.
        • –2
          Хм, а у меня и в Хрюше такие несчастья были.
        • –1
          ребят, «случался» и «10 из 10 раз при первом ходе на o 22» — это разные вещи, не правда ли?
    • 0
      И хлеб раньше был по пять копеек. Это возможно. Правда там довольно редко подрываются при открытии точкии 1:1, а у меня часто.
      • +2
        ок, давай я перефразирую :)
        в виндовом сапёре даже при самых хардкорных кастомных параметрах (сетка 9x9, 64 мины) проиграть на первом же клике весьма маловероятно. Крайне маловероятно.
        За мою многолетнюю карьеру мне ни разу не посчастливилось проиграть на первом же ходу.
        • 0
          Потому, что мины расставляются уже после клика.
          • 0
            На одном форуме проверяли это таким способом:

            Win 3.x: Hажмите Shift и набеpите XYZZY. Отпyстите клавишу и опять набеpите XYZZY. Левый веpхний пиксел клетки бyдет чеpным, если под клеткой есть мина, и белым — если нет. Вкушайте!
            win.by.ru/HiddenTips.html
          • 0
            Эх, какая разница, все равно теперь у меня все работает замечательно
    • +1
      Подтверждаю. Препод доказывал с помощью Spy++
    • 0
      [zanuda mode]
      Короче, nsinreal, у тебя в левом верхнем углу лежит магнит, который влияет на рандомность расположения мин. Они все смещены к верхнему левому углу.
      [/zanuda mode]
      • +1
        Угу, понял. Разбираюсь с магнитом. Он тянет просто влево.
        • 0
          Может, дело в %random:~0,2%? Ведь вероятность получить числа от 0 до 32 заметно больше, чем 33-99.
        • +1
          Упс, извиняюсь, там еще хитрее. Три диапазона: 0-9, 10-32 и 33-99. Их вероятности соотносятся примерно как 1:1000:100. ИМХО, правильнее было бы брать последние две цифры от random.
          • 0
            Черт, вы правы. Сейчас еще кое-что подкоректирую и перезалью
          • +1
            Исправлено.
    • +1
      Исправлено.
  • +1
    Ты маньяк! И ты крут! ))
  • 0
    Это нечто воистину прекрасное!
  • +1
    Автор! Вы мой кумир! :)))
    • 0
      Отсыпал ТС плюсов куда смог :)
  • 0
    Строчки format c: не нашёл, но всё равно играть ссыкотно.
    • +3
      Плохо искали!
  • +14
    Боже… найдите себе девушку! :)
  • 0
    Не писали ли вы серверное удаленное управление на батнегах?
    так сказать шелл…
  • +1
    Нужно сделать сетевой сапер. Суть в следующем: при подрыве на мине, игра завершается только для того, кто подорвался. Батник пишет эту информацию в расшаренный файл на сервере, другие батники игроков читают, и также раскрывают мину :)
  • 0
    nsinreal, замечательая игрушка, гораздо круче линуксовых и виндовых :)
    Играть само собой дольше:

    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        сделал допущение по третьей строке — две двойки в 63 и 53 должны были дать с большой вероятностью в 52 окошко ;)
      • 0
        Такие ситуации возникают почти во всех саперах (точнее — во всех которые я знаю)
        • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          а еще бывает вот такая подлянка:
          • 0
            в 11 было безопасно :((
  • +7
    Рамочки надо такие:
    ╔═╗
    ╟─╢
    ╚═╝
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Учтем-с
    • 0
      Исправлено распределение мин, мины на первом ходу уходят в небеса перемещаются, теперь нужно открыть как минимум одну клетку для перебора флагов (лень было добавлять еще одну строку)
  • 0
    До кучи можно добавить сохранение :)
    • 0
      Всмысле? Запись в файл как в таблицу рекордов?
      • 0
        Да, что то вроде пункта «Чемпионы» виндового Сапера. Хотя конечно возможен и вариант с сохранением текущего состояния открытых полей, дабы при следующем запуске продолжить с того же места )
        • 0
          Последнее делать не буду — поле маленькое. А вот таблица рекодов будет выводиться по команде r
  • 0
    Итак, особенности данного продукта:
    Оригинальное лого

    Особенно порадовало ))).
  • 0
    Реально заборная вещь. Неплохо реализовать возможность загрузки минного поля и его сохранение. Как задачки для ума. В шахматах есть, почему бы и в сапере не заиметь.
  • 0
    Они везде! Фанаты сапера! Сам писал лет 6 назад на турбо паскале эту игру. Вполне играбельно было, но для открытия клетки следовало вводить координаты что затягивало процесс.
    PS. В оригинальном сапере 78 сек на профессиональном уровне. Не рекорд конечно, но тоже неплохо
    • 0
      Ну, здесь та же трабла.
  • 0
    А тут учтена такая ситуация?
    ***
    ***
    ***
    То есть если * — мина, центральную мину невозможно будет как-либо обнаружить)
    • 0
      Нет, не учтена. Покажите мне программу, которая учитывает такую ситуацию.
      Но такие ситуации довольно редки и их можно, впринципе, определить по количеству флагов и бомб
      • 0
        Программ не знаю)) Просто после ежедневного задротства в сапера по пути домой пришла такая идея, но вообще таких ситуаций никогда не встречал) Всегда тыкаю в центральную в таком случае — там восьмерка)
    • НЛО прилетело и опубликовало эту надпись здесь
  • 0
    Даёшь 3Д Кваку на батниках!!!
    • +2
      У вас мандельброт уже досчитался?
      • 0
        Ой, не видел. Сейчас запущу, досчитается — отчитаюсь :-)
  • 0
    вы — псих, вам говорили? :)
  • 0
    Прочитал название и сразу как отркылась статья стал скроллить вниз в целях найти кнопку или ссылку скачать.
  • 0
    В сапере есть одна особенность: если рядом с клеткой 0 бомб, то открываем рядом стоящие клетки (не по диагонали).

    А вот это пофиксить никак нельзя, чтобы и по диагонали? А то как-то непривычно получается…
    • 0
      И еще одну ошибку нашел — после первого хода авто-открывание не работает.
    • 0
      И еще: Я заменил все вопросительные знаки (?) на точки (.) — Стало гораздо удобнее.
      А в обработчике команд — убрать команду «o». Вместо нее — просто номер клетки.
      • 0
        Окей, будет сделано. С багом автооткрытия клеток разбираюсь — почему-то он не всегда проявляется
        • 0
          По-моему нашел проблему — я не дописал функцию переноса бомбы
      • 0
        Все ваши замечания исправлены. Проверяйте.
        • 0
          Проверил. Спасибо, супер вещь!
  • 0
    Обновил сапер
  • 0
    Класс. Ну в топике добра любителей сапёра можно написать о другой классной sweep-based игре: Nonosweeper. Оформление такое же, но принцип логического «вскрытия» поля — другой (нонограммы). Затягивает жутко. Рекомендую попробовать для любителей сапёра как занятную альтернативу.
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Переделал, проблема в не пойми знает чем, но исправлена.
  • +2
    завидую вашему наличию свободного времени…
  • +1
    надеюсь там внутри ООП?
    • 0
      Конечно…
  • 0
    Готовлю ответ на ваш хабратопик :) yaminesweeper.appspot.com/
    • +1
      Ничего интересного, простите.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.