Pull to refresh

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

Abnormal programming *

Бат-файлы лишены возможности передавать по сети какую-нибудь полезную информацию друг другу.

Нет сокетов, ну и ладно, зато есть простая работа с именованными областями данных, т.е. файлами. Создание и чтение однострочных текстовых файлов вообще упрощено до предела. Достаточно выполнить echo Text>file.txt и file.txt будет содержать строку «Text». Прочитать строку из файла можно так: set /p var=<file.txt (после выполнения переменная var будет содержать строку «Text»).

Итак, можно передавать информацию между батниками через файлы. А если эти файлы будут находиться на расшаренных сетевых ресурсах, то между собой вполне могут общаться батники, запущенные на различных компьютерах.

Вот небольшой пример клиент-серверного приложения, такого себе rexec для бедных.

файл rexec_server.bat:
set checkFolder=\\IMP5\PUBLIC_RW
:loop
@ping -n 2 127.0.0.1 > nul
@if not exist "%checkFolder%\!cmd" goto loop
@call "%checkFolder%\!command.bat" > "%checkFolder%\!result"
@del "%checkFolder%\!cmd"
@goto loop


файл rexec_client.bat:
@echo off
set checkFolder=\\IMP5\PUBLIC_RW

:mainloop
set /p c=^>
if "%c%"=="exit" exit
if "%c%"=="quit" exit
echo %c%>"%checkFolder%\!command.bat"
echo.>"%checkFolder%\!cmd"

:waitloop
ping -n 2 127.0.0.1 > nul
if exist "%checkFolder%\!cmd" goto waitloop
type "%checkFolder%\!result"
del "%checkFolder%\!result"
goto mainloop


Вот пример работы:


Делает оно следующее: rexec_server.bat ждёт от клиента командную строку и выполняет её на том компьютере, на котором он запущен. С клиентом он общается через расшаренную папку «\\IMP5\PUBLIC_RW» (разумеется, вам нужно будет заменить её на свою, если захотите поэкспериментировать). Чтобы клиент и сервер не пытались раньше времени прочитать файлы, которые сейчас для них пишутся, были предусмотрены файлы-флаги, которые служат только для того, чтобы обозначить, что информация уже сохранена полностью.

Ну и на закуску — мультиплеерная игра на .bat «крестики-нолики, три в ряд».
Здесь для сохранения целостности данных реализован механизм критических секций для блокирования попыток одновременного обращения к файлам.
Помните, что перед запуском нужно в строке «set FLAG_DIR=\\IMP5\SHARED_RW» указать свою расшаренную папку (кстати, через одну папку могут играться сразу несколько игр).

Файл xo.bat:
@echo off
cls
set FLAG_DIR=\\IMP5\SHARED_RW

call :create_chars

:restart
echo Please wait...
call :sys_utils init "%0"
set cmdPrefix=$p%__PID%

rem  ------- connecting --------

call :sys_utils enter_critical_section
cls
call :sys_utils fetch_flag ready_to_play
if "%RESULT%"=="" (
  call :sys_utils set_flag ready_to_play %cmdPrefix%
  set symbol=X
  set symbol2=O
  set server=1
) else (
  set cmdPrefix=%RESULT%
  call :sys_utils set_flag %RESULT%_client_ready ---
  set symbol=O
  set symbol2=X
  set server=0
)
call :sys_utils leave_critical_section

if "%server%"=="0" goto client1
  call :wait_for_client
  call :sys_utils set_flag %cmdPrefix%_server_ready_too ---
  goto skipClient1
:client1
  call :wait_for_server
  if "%serverFailure%"=="1" goto restart
:skipClient1

rem  ------- connected --------

call :clear_field
if "%server%"=="1" (set curMode=your_turn) else (set curMode=enemy_turn)

:main_game_loop
  cls
  call :check_win
  call :render_field

  if "%winSymbol%"=="%symbol%" goto you_win
  if "%winSymbol%"=="%symbol2%" goto enemy_win
  
  if not "%curMode%"=="your_turn" goto skipTurn1
    echo.
    echo Your are "%symbol%"
    echo.
    set /p tmp=Please enter <nul
    if "%f1%"=="1" set /p tmp=1, <nul
    if "%f2%"=="2" set /p tmp=2, <nul
    if "%f3%"=="3" set /p tmp=3, <nul
    if "%f4%"=="4" set /p tmp=4, <nul
    if "%f5%"=="5" set /p tmp=5, <nul
    if "%f6%"=="6" set /p tmp=6, <nul
    if "%f7%"=="7" set /p tmp=7, <nul
    if "%f8%"=="8" set /p tmp=8, <nul
    if "%f9%"=="9" set /p tmp=9, <nul
    echo or 'q' for quit
    echo.
    set /p "tmp=Your turn: "

    if "%tmp%"=="q" goto quit_game
    if "%tmp%"=="Q" goto quit_game

    call set varName=%%f%tmp%%%
    if not "%varName%"=="%tmp%" goto main_game_loop
    call set f%tmp%=%symbol%

    call :sys_utils set_flag %cmdPrefix%_%symbol%_move f%tmp%
    set curMode=enemy_turn
    goto main_game_loop
  :skipTurn1

  if not "%curMode%"=="enemy_turn" goto skipTurn2
    echo.
    echo Waiting for another player
    :wait_for_player0
      set /p tmp=.<nul
      ping -n 2 127.0.0.1 > nul
      call :sys_utils fetch_flag %cmdPrefix%_%symbol2%_move
    if "%RESULT%"=="" goto wait_for_player0

    echo %RESULT%

    if "%RESULT%"=="q" goto other_player_quit

    call set %RESULT%=%symbol2%
    set curMode=your_turn
    goto main_game_loop
  :skipTurn2

goto main_game_loop

:quit_game
  call :sys_utils set_flag %cmdPrefix%_%symbol%_move q
exit

:other_player_quit
  cls
  echo Other player has left the game.
  echo Press 'Enter' to search for another one.
  pause > nul
goto restart

:you_win
  echo.
  echo You Win
  echo.
  echo Press 'Enter'
  pause > nul
goto restart

:enemy_win
  echo.
  echo You Lose
  echo.
  echo Press 'Enter'
  pause > nul
goto restart

:wait_for_client
  echo Waiting for client
  :wait_for_client1
    set /p tmp=.<nul
    ping -n 2 127.0.0.1 > nul
    call :sys_utils fetch_flag %cmdPrefix%_client_ready
  if "%RESULT%"=="" goto wait_for_client1
exit /b

:wait_for_server
  echo Waiting for server
  set serverFailure=0
  set /a waitCnt=4
  :wait_for_server1
    set /a waitCnt-=1
    if "%waitCnt%"=="0" (
      set serverFailure=1
      exit /b
    )
    set /p tmp=.<nul
    ping -n 2 127.0.0.1 > nul
    call :sys_utils fetch_flag %cmdPrefix%_server_ready_too
  if "%RESULT%"=="" goto wait_for_server1
exit /b

:clear_field
  set "f1=1"
  set "f2=2"
  set "f3=3"
  set "f4=4"
  set "f5=5"
  set "f6=6"
  set "f7=7"
  set "f8=8"
  set "f9=9"
exit /b


:create_chars

  set "charX0= #   # ^|"
  set "charX1=  # #  ^|"
  set "charX2=   #   ^|"
  set "charX3=  # #  ^|"
  set "charX4= #   # ^|"
  set "charX5=-------+"

  set "charO0=  ###  ^|"
  set "charO1= #   # ^|"
  set "charO2= #   # ^|"
  set "charO3= #   # ^|"
  set "charO4=  ###  ^|"
  set "charO5=-------+"

  set "charXW0=.#...#.^|"
  set "charXW1=..#.#..^|"
  set "charXW2=...#...^|"
  set "charXW3=..#.#..^|"
  set "charXW4=.#...#.^|"
  set "charXW5=-------+"

  set "charOW0=..###..^|"
  set "charOW1=.#...#.^|"
  set "charOW2=.#...#.^|"
  set "charOW3=.#...#.^|"
  set "charOW4=..###..^|"
  set "charOW5=-------+"

  for %%i in (1,2,3,4,5,6,7,8,9) do call :create_empty_char %%i

exit /b

:create_empty_char
  set "char%10=       ^|"
  set "char%11=       ^|"
  set "char%12=   %1   ^|"
  set "char%13=       ^|"
  set "char%14=       ^|"
  set "char%15=-------+"
exit /b

:check_win
  set winSymbol=.
  call :check3 1 2 3
  call :check3 4 5 6
  call :check3 7 8 9
  call :check3 1 4 7
  call :check3 2 5 8
  call :check3 3 6 9
  call :check3 1 5 9
  call :check3 3 5 7
exit /b

:check3
  call set tmp1=%%f%1%%%%f%2%%%%f%3%%
  call set tmp2=%%f%1%%%%f%1%%%%f%1%%
  if "%tmp1%"=="%tmp2%" (
    call set "winSymbol=%%f%1%%"
    call set "f%1=%%f%1%%W"
    call set "f%2=%%f%2%%W"
    call set "f%3=%%f%3%%W"
  )
exit /b

:render_field
  call :render_line %f1% %f2% %f3%
  call :render_line %f4% %f5% %f6%
  call :render_line %f7% %f8% %f9%
exit /b

:render_line
   call echo %%char%10%%%%char%20%%%%char%30%%
   call echo %%char%11%%%%char%21%%%%char%31%%
   call echo %%char%12%%%%char%22%%%%char%32%%
   call echo %%char%13%%%%char%23%%%%char%33%%
   call echo %%char%14%%%%char%24%%%%char%34%%
   call echo %%char%15%%%%char%25%%%%char%35%%
exit /b


rem ----------------- atom --------------
:sys_utils
goto %1
exit /b

:init
  if "%FLAG_DIR%"=="" set FLAG_DIR=.
  set UNIQ_BAT_ID=%COMPUTERNAME%_%USERNAME%_%~n2
  set /A CRITICAL_SECTION_LEVEL=0

  call :enter_critical_section
  call :test_flag --null-- $pid$
  set /A __PID=%RESULT%
  if [%__PID%]==[0] set /A __PID=10000000
  if [%__PID%]==[] set /A __PID=10000000
  if [%__PID%]==[99999999] set /A __PID=10000000
  set /A __PID=__PID+1
  call :set_flag --null-- $pid$ %__PID% 
  call :leave_critical_section

  set UNIQ_BAT_ID=%UNIQ_BAT_ID%_pid%__PID%
exit /b

:set_flag
  call :enter_critical_section
  echo %3>"%FLAG_DIR%\%2"
  call :leave_critical_section
exit /b

:remove_flag
  call :enter_critical_section
  if exist "%FLAG_DIR%\%2" del "%FLAG_DIR%\%2"
  call :leave_critical_section
exit /b

:fetch_flag
  call :enter_critical_section
  set "RESULT="
  if exist "%FLAG_DIR%\%2" (
    set /p RESULT=<"%FLAG_DIR%\%2"
    del "%FLAG_DIR%\%2"
  )
  call :leave_critical_section
exit /b

:test_flag
  call :enter_critical_section
  set "RESULT="
  if exist "%FLAG_DIR%\%2" set /p RESULT=<"%FLAG_DIR%\%2"
  call :leave_critical_section
exit /b

:enter_critical_section
  set /A CRITICAL_SECTION_LEVEL=CRITICAL_SECTION_LEVEL+1
  if not [%CRITICAL_SECTION_LEVEL%]==[1] exit /b

  set /A __WAIT_CNT=500

  :wait_enter_critical_section
  set /A __WAIT_CNT=__WAIT_CNT-1
  if [%__WAIT_CNT%]==[0] del "%FLAG_DIR%\$cs$_*"
  if exist "%FLAG_DIR%\$cs$_*" goto wait_enter_critical_section

  echo.>"%FLAG_DIR%\$cs$_%UNIQ_BAT_ID%"
  set /A __CNT=0
  for %%i in ( "%FLAG_DIR%\$cs$_*" ) do set /a __CNT=__CNT+1
  if [%__CNT%]==[1] goto all_right_1
    del "%FLAG_DIR%\$cs$_%UNIQ_BAT_ID%"
    goto wait_enter_critical_section
  :all_right_1
exit /b

:leave_critical_section
  set /A CRITICAL_SECTION_LEVEL=CRITICAL_SECTION_LEVEL-1
  if [%CRITICAL_SECTION_LEVEL%]==[0] del "%FLAG_DIR%\$cs$_%UNIQ_BAT_ID%"
exit /b

:EOF
Tags:
Hubs:
Total votes 275: ↑263 and ↓12 +251
Views 11K
Comments Comments 100