Friday, December 9, 2011

Creating Games using Batch file | Part - 4

Instructions:
> Copy the below scripts to notepad
> Save it as .bat
> Convert .bat to .exe using this software









Mine Sweeper:

  
:main
:: Minesweeper

 @echo off
 setlocal enabledelayedexpansion
 title ScrewTheLotOfYou's Minesweeper

 call :userInterface
 if "!exit!"=="true" exit /b

 echo.
 echo Loading grid...
 call :setupGrid

 echo Loading display variables...
 call :setupDisplayVariables

 echo Laying mines...
 call :setupMines

 call :gameLoop
 goto main
exit /b


:userInterface
::A function to provide the main menu for the player.

 ::Clears the screen and gives the player the options.
 cls
 echo 1. Play a game with begginer difficulty
 echo 2. Play a game with intermediate difficulty
 echo 3. Play a game with advanced difficulty
 echo 4. Play a game with custom difficulty
 echo 5. Exit
 echo.
 echo.

 ::Clears the menuChoice and requests the player's input.
 set menuChoice=
 set /p menuChoice="Enter the number of your choice - "

 ::The following IF statements check the player's input, setting the appropriate difficultly level and the
 ::right amount of space between the edge of the screen and the start of the board.

 if "!menuChoice!"=="1" (
  set difficulty=begginer
  set displaySpacer=   
  exit /b
 )

 if "!menuChoice!"=="2" (
  set difficulty=intermediate
  set displaySpacer=  
  exit /b
 )

 if "!menuChoice!"=="3" (
  set difficulty=advanced
  set displaySpacer=
  echo.
  echo To view the advanced game properly the window must be resized. Right click the
  echo CMD icon at the top left of the window, click properties and on the LAYOUT tab,
  echo change the width to a minimum of 95.
  echo Press enter once changed to refresh the display.
  echo.
  pause
  exit /b
 )

 if "!menuChoice!"=="4" (
  cls
  call :getCustomDimensions
  exit /b
 )
 if "!menuChoice!"=="5" (
  set exit=true
  exit /b
 )
goto userInterface


:getCustomDimensions
::A function to recieve custom dimensions from the user and check they are within sensible limits.

 set /p xDimension="Enter the width [1-30] - "
 if !xDimension! gtr 30 (
  echo The maximum width is 30.
  goto getCustomDimensions
 )
 if !xDimension! lss 1 (
  echo The minimum width is 1.
  goto getCustomDimensions

 )
 echo.
 set /p yDimension="Enter the height [1-30] - "

 if !yDimension! gtr 30 (
  echo The maximum height is 30.
  goto getCustomDimensions
 )
 if !yDimension! lss 1 (
  echo The minimum height is 1.
  goto getCustomDimensions

 )

 ::The maximum number of mines is 80% of the squares on the grid.
 ::The practical maximum number of mines (before causing stack overflow) is 499.
 echo.
 set /a maxMines= !xDimension! * !yDimension! - !xDimension! * !yDimension! / 5
 if /i %maxMines% geq 500 (
  set maxMines=499
 )

 ::The practical minimum number of mines is 1. The minimum for a given board is 1/12th of the
 ::total number of squares on the grid. This prevents stack overflow on the largest grids.
 set /a minMines= 1 + !xDimension! * !yDimension! / 12
 if /i maxMines geq 500 (
  set maxMines=499
 )
 
 set /p mineCount="Enter the number of mines [%minMines%-%maxMines%] - "

 if !mineCount! lss %minMines% (
  echo The minimum number of mines is %minMines%.
  goto getCustomDimensions

 )
 if !mineCount! gtr %maxMines% (
  echo The maximum number of mines for this board size is %maxMines%.
  goto getCustomDimensions

 )

 ::The space between the edge of the screen and the start of the board is adjusted depending on the size of the board.
 ::This just makes it look more central.
 if !xDimension! leq 10 (
  set displaySpacer=   
 ) else (
  if !xDimension! leq 17 (
   set displaySpacer=  
  ) else (
   if !xDimension! leq 22 (
    set displaySpacer= 
   ) else (
    set displaySpacer=
    if !xDimension! gtr 24 (
     echo To view large games properly the window must be resized. Right click the
     echo CMD icon at the top left of the window, click properties and on the LAYOUT tab,
     echo change the width. A width of 95 will encorporate all possible game sizes.
     echo Press enter once changed to refresh the display.
     echo.
     pause
    )
   )
  )
 )

 ::Sets the difficulty variable.
 set difficulty=custom
 
exit /b

:setupGrid

::Sets up the display grid to the correct dimensions, each position on the grid initally containing just a space.
::Variables in the 'D_Grid' (display grid) have the format DX_Y where X and Y are coordinates along the X and Y axes respectively.

 ::Sets the appropriate dimensions and number of mines to lay based on the difficulty.

 if "%difficulty%"=="begginer" (
  set xDimension=9
  set yDimension=9
  set mineCount=10
 )

 if "%difficulty%"=="intermediate" (
  set xDimension=16
  set yDimension=16
  set mineCount=40
 )

 if "%difficulty%"=="advanced" (
  set xDimension=30
  set yDimension=16
  set mineCount=99
 )

 ::The number of mine 'flags' placed by the player is zero at the start of the game.
 set flaggedCount=0

 ::Ensures the list of flagged coordinates is empty.
 set flaggedCoordinateList=

 ::Ensures the finalOutcome variable is undefined, it is defined only when a player wins or loses.
 set finalOutcome=

 ::Creates two grids of the appropriate dimensions. The D_grid is the grid displayed to the user, the G_grid is the grid containing all the mines and numbers.

 for /l %%I in (1,1,%xDimension%) do (
  for /l %%J in (1,1,%yDimension%) do (
   set D%%I_%%J= 
   set G%%I_%%J=0
  )
 )
exit /b


:setupDisplayVariables

::Creates a number of variables corresponding to the number of rows in the grid.
::Each variable contains the variable names of all the display grid locations on that row, each surround by percentage signs.
::This allows easy display of the grid, because 'CALL ECHO'ing these variables displays the value of all the display grid loactions on that row.
::Doing it this way means each line of display doesn't have to be generated every time the board is displayed.

 ::Sets pipe to the pipe character, this is just to draw the grid around the numbers.
 set pipe=^^^|

 ::First section of this for command is just placing the y coordinate and a couple of spaces at the start of each display line, forming the 'y axis label'.
 ::If the number is single digit (i.e. less than 10) one extra space is put in so everything lines up.
 ::The second bit builds the rest of the line by simply adding each grid entry one by one, surrounded by pipe characters to give a grid-look.

 for /l %%I in (1, 1, %yDimension%) do (
  if %%I lss 10 (
   set displayLine%%I=%%I  
  ) else (
   set displayLine%%I=%%I 
  )
  for /l %%J in (1, 1, %xDimension%) do (
   set displayLine%%I=!displayLine%%I!%%pipe%%%%D%%J_%%I%%%%pipe%%
  )
 )

 ::This for look is devoted to setting up the x axis label line.
 ::It starts off with an x and a couple of spaces, then just writes each number underneath the corresponding line on the grid.
 ::No need for the fancy delayed-expansion of the variables using %% or CALL ECHO, this line is fixed and won't change.
 ::Once again the number of spaces after each number is dictated by whether the number is one- or two-digit to keep it all in line.

 set displayLineBottom=  x 
 set rowDivider=    
 for /l %%J in (1, 1, %xDimension%) do (
  if %%J lss 10 (
   set displayLineBottom=!displayLineBottom!%%J  
  ) else (
   set displayLineBottom=!displayLineBottom!%%J 
  )
  set rowDivider=!rowDivider!-  
 )
exit /b



:setupMines

::Lays the number of mines appropriate for this difficulty level.

 set minesToPlant=%mineCount%
 set mineCoOrdinateList=

 ::Calculates a random position on the G_grid (game grid, containing all the mines and numbers),
 ::Sets Gcurrent to the value of the G_grid at these coordinates.
 ::Checks a mine has not already been planted here (it will have value 'MINE' if a mine has already been planted at this location).
 ::If the location has already been planted, add 1 to the failedMineCount.
 ::If the space has not already been planted, set that G_grid value to 'MINE' and call the updateG_grid function

 :layMines
  set failedMineCount=0
  for /l %%I in (1,1,%minesToPlant%) do (
  
   set /a xMine= 1 + !random! %% %xDimension%
   set /a yMine= 1 + !random! %% %yDimension%

   call set Gcurrent=%%G!xMine!_!yMine!%%

   if NOT "!Gcurrent!" == "MINE" (
    set G!xMine!_!yMine!=MINE
    set F!xMine!_!yMine!=*

    set mineCoOrdinateList=!mineCoOrdinateList!!xMine!_!yMine!,

    call :updateG_grid !xMine! !yMine!
   ) else (
    set /a failedMineCount= !failedMineCount! + 1
   )
  )

  ::Calls layMines again if any mines failed to plant, specifying that minesToPlant is the number of failed mines.

  if NOT %failedMineCount%==0 (
   set minesToPlant=%failedMineCount%
   goto layMines
  )
exit /b



:updateG_grid

::A function to update the values of the G_grid when a mine is planted, so all the sqaures around it show the correct number.
::Each time a mine is planted, this function is called. All the sqaures adjacent to the mine, assuming they are not mines, have their G-grid value increased by 1.
::When all the mines are laid, the value of the G_grid for each square contains the number of adjacent mines, which is how minesweeper works.

 ::These 8 pairs x and y values correspond to the 8 ADJacent squares.

 set /a adj1X=%1 + 1
 set /a adj1y=%2 + 1

 set /a adj2X=%1
 set /a adj2y=%2 + 1

 set /a adj3X=%1 - 1
 set /a adj3y=%2 + 1

 set /a adj4X=%1 + 1
 set /a adj4y=%2 - 1

 set /a adj5X=%1
 set /a adj5y=%2 - 1

 set /a adj6X=%1 - 1
 set /a adj6y=%2 - 1

 set /a adj7X=%1 + 1
 set /a adj7y=%2

 set /a adj8X=%1 - 1
 set /a adj8y=%2

 ::This FOR command cycles through the eight ADJacent squares, and, if they are not mines, increases their G_grid value by 1.

 for /l %%I in (1,1,8) do (
  call set squareValue=%%G!adj%%IX!_!adj%%Iy!%%
  if NOT "!squareValue!"=="MINE" (
   set /a G!adj%%IX!_!adj%%Iy!= !squareValue! + 1
  )
 )
exit /b


:gameLoop
::A function to recieve user input, evaluate it, adjust the necessary grids. Repreats until game is won or lost.

 ::Displays the board.

 call :displayBoard

 ::ECHOs the infoMessage. This is only defined if the user's input was invalid. Otherwise this is just an empty line.
 echo.%infoMessage%
 echo Flagged %flaggedCount%/%mineCount%
 echo.

 set infoMessage=

 ::Clears the input variable, then requests the user's input.
 set input=
 set /p input="Enter your move or type HELP for help [x y [*]] - "

 ::Gives help if the user typed help.

 if /i "!input!"=="help" (
  echo To reveal a square with coordinates x,y type: x y 
  echo To flag a sqaure with coordinates x,y as a mine, type: x y *
  echo To unflag a square that has already been flagged with coordinates x,y just type: x y *
  echo Type EXIT to end the game immediately.
  pause
  goto gameLoop
 )

 if /i "!input!"=="exit" (
  exit /b
 )

 ::Checks the input is valid, in the format "x y".
 ::The FOR command splits up the user's input into three parts, the x, the y and the *. The * may or may not have been entered.
 ::Checks that each x and y coordinate is within the bounds of the grid size.
 ::Checks if the * is present. If so, calls flagSquare, else calls revealSquare.

 for /f "tokens=1,2,3 delims=-, " %%I in ("!input!") do (

  if NOT %%I leq %xDimension% (
   set infoMessage=Input must be in the form "x y" where x is a number from 1 to %xDimension%.
   goto gameLoop
  )
  if NOT %%I gtr 0 (
   set infoMessage=Input must be in the form "x y" where x is a number from 1 to %xDimension%.
   goto gameLoop
  )
  if NOT %%J gtr 0 (
   set infoMessage=Input must be in the form "x y" where y is a number from 1 to %yDimension%.
   goto gameLoop
  )
  if NOT %%J leq %yDimension% (
   set infoMessage=Input must be in the form "x y" where y is a number from 1 to %yDimension%.
   goto gameLoop
  )
  if NOT "%%K"=="" (
   if NOT "%%K"=="*" (
    set infoMessage=Input must be in the form "x y" or "x y *".
    goto gameLoop
   ) else (
     call :flagSquare %%I %%J
   )
  ) else (
    call :revealSquare %%I %%J
  )
 )

 ::If the flagging of the or the revealing of the square resulted in a win or lose, exit this function.

 if defined finalOutcome (
  exit /b
 )

 ::Otherwise, loop round again for another turn.
 goto gameLoop


:displayBoard
::A function to clear the screen then display the updated board. 'CALL ECHO's the variables set up in the setupDisplayVariables function.

 cls
 for /l %%I in (%yDimension%, -1, 1) do (
  echo.%displaySpacer%%rowDivider%
  call echo.%displaySpacer%!displayLine%%I!
 )
 echo.%displaySpacer%y
 echo.%displaySpacer%%displayLineBottom%
 echo.
exit /b


:revealSquare
::Recieves the arguments %1=x and %2=y
::A function to reveal a square.

 ::Checks if the mine is flagged as a mine, if so, does not reveal it.
 ::Otherwise checks if the square is a mine, if so asserts LOSE.
 ::Else just sets the D_grid display variable to the previously hidden G_grid value.
 ::If the square was a zero, calls the revealZeros function.

 if NOT "!D%1_%2!"=="*" (
  if "!G%1_%2!"=="MINE" (
   set finalOutcome=LOSE
   call :generateLoseGrid
  ) else (
   if "!G%1_%2!"=="0" (
    set adjacentZeroList=%1_%2,
    call :revealZeros %1 %2
   ) else (
    set D%1_%2=!G%1_%2!
   )
  )
 ) else (
  set infoMessage=That square is flagged as a mine! To reveal it, unflag it first.
 )
exit /b


 :revealZeros
 ::A function to reveal the G_grid values of all the squares around a zero. If another zero is found, repeat this funtion for that square.

  ::Sets the display grid value of the zero-value square to zero.
  set D%1_%2=0

  ::These 8 pairs x and y values correspond to the 8 ADJacent squares.

  set /a adj1X=%1 + 1
  set /a adj1y=%2 + 1

  set /a adj2X=%1
  set /a adj2y=%2 + 1

  set /a adj3X=%1 - 1
  set /a adj3y=%2 + 1

  set /a adj4X=%1 + 1
  set /a adj4y=%2 - 1

  set /a adj5X=%1
  set /a adj5y=%2 - 1

  set /a adj6X=%1 - 1
  set /a adj6y=%2 - 1

  set /a adj7X=%1 + 1
  set /a adj7y=%2

  set /a adj8X=%1 - 1
  set /a adj8y=%2


  ::This FOR loop retrieves the G_grid and D_grid values of each of the adjacent squares.
  ::If the D_grid is *, the square has been flagged by the player and is not altered
  ::If the G_grid is zero, that square is added to the adjacentZeroList so that this function is repeated for that square.
  ::Otherwise the G_grid value for the square is revealed in the D_grid.

  for /l %%I in (1,1,8) do (
   call set Gadj=%%G!adj%%Ix!_!adj%%Iy!%%
   call set Dadj=%%D!adj%%Ix!_!adj%%Iy!%%
   if NOT "!Dadj!"=="*" (
    if "!Gadj!"=="0" (
     if NOT "!Dadj!"=="0" (
      set adjacentZeroList=!adjacentZeroList!!adj%%Ix!_!adj%%Iy!,
     )
    ) else (
     set D!adj%%Ix!_!adj%%Iy!=!Gadj!
    )
   )
  )

  ::Remove the square that has just been processed from the list of squares due for processing.

  set adjacentZeroList=!adjacentZeroList:%1_%2,=!

  ::Call the revealZeros function on the next adjacent zero in the list. If the list is empty this FOR command will do nothing.

  for /f "tokens=1,2 delims=,_" %%I in ("!adjacentZeroList!") do (
   call :revealZeros %%I %%J
  )
 
 exit /b


:flagSquare
 ::A function to flag or unflag a square.
 ::Recieves the arguments %1=x and %2=y

 ::If the square is unmarked and unrevealed, simply mark the square as a mine, increment the flaggedCount and add its coordinates
 ::to the flaggedCoordinateList.
 ::If the mine is already flagged with a *, unflag the mine and decrement the flaggedCount.
 ::Otherwise the square has already been revealed and cannot be flagged.

 if "!D%1_%2!"==" " (
  set D%1_%2=*
  set /a flaggedCount+=1
  set flaggedCoordinateList=!flaggedCoordinateList!%1_%2,
 ) else (
   if "!D%1_%2!"=="*" (
    set D%1_%2= 
    set /a flaggedCount-=1

    REM :: Remove the square being unflagged from the flaggedCoOrdinateList variable.
    set flaggedCoOrdinateList=!flaggedCoOrdinateList:%1_%2,=!
   ) else (
    set infoMessage=That square can't be flagged, it is already revealed.
   )
 )

 ::The player is notified if too many mines have been flagged.
 if %flaggedCount% gtr %mineCount% (
  set infoMessage=You have flagged too many mines; not all are correct. To remove a flag, type "x y *" for an already flagged square.
 )

 ::If the flaggedCount is equal to the number of mines planted, the solution must be checked to see if it is correct.

 if %flaggedCount%==%mineCount% (
  call :checkSolution !flaggedCoordinateList!
 )
exit /b






:checkSolution
::A function to check if all the mines flagged by the player are correct.
::This function is only called when exactly the right number of mines are flagged.
::Receives argument %* containing flaggedCoOrdinateList

 ::Sets the number of flags verified to be correct to 0 and calls the checkSolutionLoop, passing the flaggedCoOrdinateList (%*)
 set correctFlags=0

 call :checkSolutionLoop %*

 ::If the player has flagged all mines correctly, the number of correct flags is equal to the number of mines originally planted.
 if "%correctFlags%"=="%mineCount%" (
  set finalOutCome=WIN
  echo Congratulations you've identified all the mines^^^!
  pause
 )
exit /b


 :checkSolutionLoop
 ::A function to increment the correctFlags variable every time a flag turns out to be correct. Execution stops if an incorrect mine is found.
 ::Receives argument %* containing a list of flagged coordinates that haven't been checked yet.
 ::Only called by checkSolution.

  ::This FOR /F command takes the first element of the list of flagged coordinates passed as an argument
  ::and checks that the flagging is correct i.e. the G-grid value is MINE.
  ::If the flag was correct, the correctFlags variable is incremented and the loop is called again with the remaining elements of the list.
  ::If the flag was incorrect, the loop sets the infoMessage and exits.

  for /f "tokens=1* delims=," %%I in ("%*") do (
   if "!G%%I!"=="MINE" (
    set /a correctFlags= !correctFlags! + 1
    call :checkSolutionLoop %%J
   ) else (
    set infoMessage=All flagged squares aren't correct! To remove a flag, type "x y *" for an already flagged square.
   )
  )
 exit /b






:generateLoseGrid
::A function to set up the final grid shown to the player. This function is only called after the player has lost.
::The lose grid is just a moified D_grid (display grid).
::The only changes made are that unflagged mines are marked with ! and incorrectly flagged are marked as X.

 ::Sets excl to the exclamation mark character, for use in marking the unflaggedMines.
 set excl=^^^!

 call :markIncorrectFlagLoop !flaggedCoordinateList!
 call :markUnflaggedMinesLoop !mineCoordinateList!
 call :displayBoard

 echo.
 echo Sorry you lost the game!excl! The board above shows unidentified mines as !excl! and incorrectly flagged mines as X. 
 pause
exit /b

 :markIncorrectFlagLoop
 ::A function to change incorrect flags with a ! correct flags are left as *.
 ::Argument %* contains the list of flagged coordinates.
 ::Only called by generateLoseGrid.

  for /f "tokens=1* delims=," %%I in ("%*") do (
   if NOT "!G%%I!"=="MINE" (
    set D%%I=X
   )
   call :markIncorrectFlagLoop %%J
  )
 exit /b

 :markUnflaggedMinesLoop
 ::A function to change incorrect flags with a ! correct flags are left as *.
 ::Argument %* contains the list of flagged coordinates
 ::Only called by generateLoseGrid.

  for /f "tokens=1* delims=," %%I in ("%*") do (
   if NOT "!D%%I!"=="*" (
    set D%%I=!excl!
   )
   call :markUnflaggedMinesLoop %%J
  )
 exit /b





 Noughts and crosses 2:
  
@echo off
setlocal enabledelayedexpansion
title Noughts and crosses 2



:main
 call :titleScreen
 if "%EXIT%"=="1" exit /b
 call :firstSetup

 :main_Loop
 call :resetBoard
 call :gameLoop
 call :endGameScreen %win%
 if /i %rematch%==Y (
  goto main_Loop
 )
 exit /b


:titleScreen
 echo Welcome to Noughts and Crosses.
 echo.
 echo.
 echo Type EXIT to quit the game.
 echo.
 
 :askP1Type
  set p1Type=
  set /p p1Type="Player 1 - Human or computer? [H/C] - "
  if /i "!p1Type!"=="C" goto askP2Type
  if /i "!p1Type!"=="H" goto askP2Type
  if /i "!p1Type!"=="EXIT" (
   set EXIT=1
   exit /b
  )
 goto askP1Type

 :askP2Type
  set p2Type=
  set /p p2Type="Player 2 - Human or computer? [H/C] - "
  if /i "!p2Type!"=="C" exit /b
  if /i "!p2Type!"=="H" exit /b
  if /i "!p2Type!"=="EXIT" (
   set EXIT=1
   exit /b
  )
 goto askP2Type



:firstSetup
 set p1Score=0
 set p2Score=0
 set turn=
 set p1Char=X
 set p2Char=O
 set p1Score=0
 set p2Score=0
 set NL=^


 ::winList contains all the possible winning lines, separated by the new line character.
 set winList=1 2 3 !NL! 4 5 6 !NL! 7 8 9 !NL! 1 4 7 !NL! 2 5 8 !NL! 3 6 9 !NL! 1 5 9 !NL! 3 5 7
 exit /b

:ResetBoard
 for /l %%I in (1,1,9) do (
  set G%%I=%%I
 )
 if "%turn%"=="" (
  set /a turn=%random% %% 2 + 1
 ) else (
  set /a turn= 3 - %turn%
 )
 set turnCount=0
 set availableMoves=123456789
 set errorMessage=
 set win=0
 exit /b

:gameLoop
 set /a otherTurn= 3 - %turn%
 if /i "!p%turn%Type!"=="H" (
  call :humanTurn
 ) else (
  call :computerTurn %turn% !p%turn%Char! !p%otherTurn%Char!
 )
 set /a turnCount+=1
 set availableMoves=!availableMoves:%move%=!
 if NOT %win%==0 exit /b
 if %turnCount%==9 exit /b
 set /a turn= 3 - %turn%
 goto GameLoop

:humanTurn
 call :displayGrid
 echo.
 echo.%infoMessage%
 set infoMessage=
 set move=
 set /p move= Player %turn%'s turn. Enter the number of your move [1-9] - 
 if !move! lss 1 (
  set infoMessage=Invalid move, enter a number between 1 and 9.
  goto humanTurn
 )
 if !move! gtr 9 (
  set infoMessage=Invalid move, enter a number between 1 and 9.
  goto humanTurn
 )
 if NOT "!G%Move%!"=="!Move!" (
  set infoMessage=That move has already been made, choose another.
  goto humanTurn
 )
 set G%move%=!p%turn%Char!
 call :CheckWin %turn% !p%turn%Char!
 exit /b

:computerTurn

 set move=
 for /f "tokens=1-3 delims= " %%I in ("!winList!") do (  
  if "!G%%I!!G%%J!!G%%K!"=="%2%2%%K" (
   set move=%%K
  )
  if "!G%%I!!G%%J!!G%%K!"=="%2%%J%2" (
   set move=%%J
  )
  if "!G%%I!!G%%J!!G%%K!"=="%%I%2%2" (
   set move=%%I
  )

  if NOT "!move!"=="" (
   set G!move!=%2
   set win=%1
   exit /b
  )

 )

 for /f "tokens=1-3 delims= " %%I in ("!winList!") do (
  if "!G%%I!!G%%J!!G%%K!"=="%3%3%%K" (
   set move=%%K
  )
  if "!G%%I!!G%%J!!G%%K!"=="%3%%J%3" (
   set move=%%J
  )
  if "!G%%I!!G%%J!!G%%K!"=="%%I%3%3" (
   set move=%%I
  )

  if NOT "!move!"=="" (
   set G!move!=%2
   exit /b
  )
 )
 set /a moveGuess= %random% %% ( 9 - %turnCount% )
 set move=!availableMoves:~%moveGuess%,1!
 set G%move%=!p%turn%Char!
 exit /b

:displayGrid
 cls
 echo.
 echo                          ^|   ^|
 echo                        %G1% ^| %G2% ^| %G3%
 echo                      -------------
 echo                        %G4% ^| %G5% ^| %G6%
 echo                      -------------
 echo                        %G7% ^| %G8% ^| %G9%
 echo                          ^|   ^|
 exit /b

:checkWin
 for /f "tokens=1-3 delims= " %%I in ("!winList!") do (
  if "!G%%I!!G%%J!!G%%K!"=="%2%2%2" (
   set win=%1
   exit /b
  )
 )
 set win=0
 exit /b

:endGameScreen
 if NOT %win%==0 (
  set /a p%win%Score+=1
 )
 call :Displaygrid
 echo.
 echo.
 if %win% gtr 0 (
  echo Player %win% wins^!
 ) else (
  echo It's a draw^!
 )
 echo.
 echo.
 echo       Current Scores
 echo       --------------
  echo Player 1 Score - %p1Score%    Player 2 Score - %p2Score%
 echo.
 set rematch=
 set /p rematch="Would you like a rematch? [Y/N] - "
 if /i "!rematch!"=="Y" exit /b
 if /i "!rematch!"=="N" (
  exit /b
 ) else (
  goto endGameScreen
 )







Sudoku:
  @echo off



::Entering invalid puzzles will make the program act strangely or crash.
::It's better to get puzzles from the internet than to try to make them up.


::I've decided to write commentry/explanations as I code this.
::And I've tried to make it much easier to follow by leaving space, organising everything as sensibly as I can and offsetting in FOR/IF commands.



::Sets the title, and enables delayed expansion.

SetLocal EnableDelayedExpansion
Title ScrewTheLotOfYou's Batch Sudoku Solver

echo Welcome to ScrewTheLotOfYou's Batch Sudoku Solver.
echo.
echo.
echo.
echo Loading...


::Glossary:

:: Space: Each place that number can be placed. 81 of these on a sudoku puzzle.
:: Box: Each 3x3 grid. There are 9 of these.
:: Row: From left to right. 9 squares in each row. 9 rows alogether.
:: Column: From top to bottom. 9 squares in each column. 9 columns altogether.

:: X: X is the column number of a given space. Numbers 1-9 from left to right.
:: Y: Y is the row number of a given space. Numbers 1-9 from top to bottom.

:: Puzzle (grid): The grid of numbers that have been entered by the user or worked out by the program. These numbers are the final positions of each number.
:: Solution (grid): The grid of numbers that the program has worked out that COULD go in each space with the current Puzzle grid.
:: R (grid): This stands for Row Grid. Contains the possible locations that each number from 1-9 could reside.
:: C (grid): This stands for Column Grid. Contains the possible locations that each number from 1-9 could reside.
:: B (grid): This stands for Box Grid. Contains the possible locations that each number from 1-9 could reside.

:: Variables that are part of the Puzzle grid are in the format: PX-Y=Value
:: Variables that are part of the Solution grid are in the format: SX-Y=Possible solutions
:: Variables that are part of the R grid are in the format: RY-Value=Possible X values
:: Variables that are part of the C grid are in the format: CX-Value=Possible Y values
:: Variables that are part of the B grid are in the format: BBoxNumber-Value=Possible XY values
:: The R, C and S variables are a list of single digit numbers, not separated. The B variables are a list of two digit numbers, each separated by a space.




::Sets up the Puzzle Row Grid, a 9x9 square.
::Sets up the Puzzle Columns, a 9x9 square.
::Sets up the Column and Row grids.

for /L %%I in (1,1,9) do (

 for /L %%J in (1,1,9) do (
  set PuzzleRow%%I=!PuzzleRow%%I!#P%%J-%%I# 
  set PuzzleColumn%%I=!PuzzleColumn%%I!#P%%I-%%J# 

  for /L %%K in (1,1,9) do (
   set R%%I-%%J=!R%%I-%%J!%%K
   set C%%I-%%J=!C%%I-%%J!%%K
  )
 )
 set PuzzleDisplayRow%%I=!PuzzleRow%%I:~0,21!][!PuzzleRow%%I:~21,21!][!PuzzleRow%%I:~42,21!
 set PuzzleDisplayRow%%I=!PuzzleDisplayRow%%I: =!

)






::Sets up all 9 3x3 Puzzle boxes and all 9 3x3 solution boxes.

::Boxes are more complicated because they don't follow a simple rule, e.g. column 2 x=2.
::This is why I'm setting up solution list boxes.

set BoxNumber=0



for /l %%I in (0,1,2) do (
 for /l %%J in (0,1,2) do (
  set /a StartY=%%I * 3 + 1
  set /a EndY=%%I * 3 + 3
  set /a StartX=%%J * 3 + 1
  set /a EndX=%%J * 3 + 3
  call :SetUpBoxes !StartY! !EndY! !StartX! !EndX!
 )
)


goto SetUpAllSolutionSpaces






:SetUpBoxes

::%1=StartY %2=EndY %3=StartX %4=EndX

::SpaceCount is set to 1 each time so each solution box can have the format SolutionBox(BoxNumber)-SpaceCount
::This allows me to cycle through each space in a box as quickly as a row or column.

set SpaceCount=1

set /a BoxNumber+=1

for /l %%I in (%1,1,%2) do (
 for /l %%J in (%3,1,%4) do (
  set PuzzleBox%BoxNumber%=!PuzzleBox%BoxNumber%!#P%%J-%%I# 
  set SolutionBox%BoxNumber%-!SpaceCount!=S%%J-%%I
  
  for /L %%K in (1,1,9) do (
   set B%BoxNumber%-%%K=!B%BoxNumber%-%%K!%%J%%I 
  )

  set /a SpaceCount=!SpaceCount!+1
 )
)
exit /b



    



:SetUpAllSolutionSpaces

::Sets up all the solution spaces, so each one starts off as 1-9.

for /l %%I in (1,1,9) do (
 for /l %%J in (1,1,9) do (
  set S%%I-%%J=123456789
  set P%%I-%%J= 
 )
)



::goto SetupSolutionGrid




set P1-1=

:BeginEntering

set InputCount=0

cls




::Asks the user to input each space. Calls :InputEachSpace for the user to input. Calls to display the grid so far. Clears the screen. It does all this 9x9 times.
::The function RefreshPuzzleDisplay is further down, as it is used now and later.

for /L %%I in (1,1,9) do for /L %%J in (1,1,9) do echo Enter the puzzle below.& echo Just press enter if the space is blank.& echo.& set P%%J-%%I=&& Call :RefreshPuzzleDisplay& Call :InputEachSpace %%J %%I& cls
echo.
echo.
echo Processing puzzle...
goto SetUpSolutionGrid






:: This function is called as the user inputs each part of the grid.

:InputEachSpace
::Recieves arguments %1=X and %2=Y
echo.
echo Column:%1 Row:%2
echo.
::The format is Px-y (P stands for Puzzle. As these variables are part of the puzzle grid, not the solutions grid).
set /p P%1-%2=
set P%1-%2=!P%1-%2:~0,1!
if NOT !P%1-%2! leq 9 set P%1-%2= 
if NOT !P%1-%2! geq 1 set P%1-%2= 

if NOT "!P%1-%2!"==" "  set /a InputCount+=1&& set DefinedSpaces!InputCount!=P%1-%2=!P%1-%2!

exit /b








:SetUpSolutionGrid

:: Adjusts the solutions for each row, column and box containing a space defined by the user.
:: Works out which box the space is in using the formula: BoxNumber= ( (x+2)/3) ) + ( ((y-1)/3)*3 )
:: This works because set /a does not use decimals when dividing, so only the number of whole divisions is returned.
:: For example, set /a 2 / 3 returns 0. set /a 8 / 3 returns 2.


:: Set FoundSolutions to a single space, as trying to use envirnoment variable substition on a non existent variable causes trouble.

set FoundSolutions= 

::Sorry about it all being on one line, but the FOR command crashes if I try and put it in brackets on serparate lines. Think it's something to do with the brackets in the set /a command.
::The function AdjustSolutionGrid is quite far down, as it's used now and later.

for /l %%I in (1,1,%InputCount%) do set /a BoxNumber= ( ( (!DefinedSpaces%%I:~1,1! + 2 ) / 3) + ( ( ( !DefinedSpaces%%I:~3,1! - 1 ) / 3 ) * 3 ) )&& call :AdjustSolutionGrid !DefinedSpaces%%I:~1,1! !DefinedSpaces%%I:~3,1! !DefinedSpaces%%I:~5,1! !BoxNumber!





:BeginSolving
cls
echo Ready to solve puzzle.
echo.
echo.
echo After each number is found the program will pause.
echo Press enter to begin.
Pause>nul
cls
call :RefreshPuzzleDisplay


















:ProcessFoundSolutions
::This is where the FoundSolutions variable is processed.
::If it is empty, other algorithms are tried.



if "%FoundSolutions: =%"=="" goto CheckForLineBoxRules

for /f %%I in ("%FoundSolutions%") do (
 set SolutionTemp=%%I
 set FoundSolutions=!FoundSolutions:%%I=!
)

set /a BoxNumber= ( ( (!SolutionTemp:~0,1! + 2 ) / 3) + ( ( ( !SolutionTemp:~1,1! - 1 ) / 3 ) * 3 ) )

call :AdjustSolutionGrid %SolutionTemp:~0,1% %SolutionTemp:~1,1% %SolutionTemp:~2,1% %BoxNumber%

set /a InputCount+=1

set P%SolutionTemp:~0,1%-%SolutionTemp:~1,1%=%SolutionTemp:~2,1%
cls
call :RefreshPuzzleDisplay
echo.
echo.
echo A %SolutionTemp:~2,1% has been added at (%SolutionTemp:~0,1%,%SolutionTemp:~1,1%)
echo.
pause
goto ProcessFoundSolutions








::This is the function that displays the puzzle on screen. No arguments are recieved.

:RefreshPuzzleDisplay
echo. ^|^|%PuzzleDisplayRow1:#=!%^|^|
echo. ^|^|%PuzzleDisplayRow2:#=!%^|^|
echo. ^|^|%PuzzleDisplayRow3:#=!%^|^|
echo  ^|^|-------------^|^|
echo. ^|^|%PuzzleDisplayRow4:#=!%^|^|
echo. ^|^|%PuzzleDisplayRow5:#=!%^|^|
echo. ^|^|%PuzzleDisplayRow6:#=!%^|^|
echo  ^|^|-------------^|^|
echo. ^|^|%PuzzleDisplayRow7:#=!%^|^|
echo. ^|^|%PuzzleDisplayRow8:#=!%^|^|
echo. ^|^|%PuzzleDisplayRow9:#=!%^|^|
exit /b










:AdjustSolutionGrid



::Recieves arguments %1=X, %2=Y, %3=Value and %4=Box Number.

::This Function adjusts the solution grid by removing solutions from the row, column and box.
::It also takes out possible locations of values in affected boxes and the row, column and box in which the value resides.
::If this function finds a space that has just one solution, or a number that has just one possible location, it sets FoundSolutions to include its co-ordinates and value, for processing later.

set S%1-%2= 
set R%2-%3= 
set C%1-%3= 
set B%4-%3= 

::I know a few of the commands here are incredibly complicated and hard to follow. Can't be helped.
::Once again the box calculation algorithm had to go on the line because of the bracket disagreement.

::No commentary can be typed inside a FOR loop. Sorry.

for /l %%I in (1,1,9) do set /a AffectedBox1= ( ( (%1 + 2 ) / 3) + ( ( ( %%I - 1 ) / 3 ) * 3 ) )&& set /a AffectedBox2= ( ( (%%I + 2 ) / 3) + ( ( ( %2 - 1 ) / 3 ) * 3 ) )&& (


 set B%4-%%I=!B%4-%%I:%1%2 =!
 set C%1-%%I=!C%1-%%I:%2=!
 set R%2-%%I=!R%2-%%I:%1=!

 call set B!AffectedBox1!-%3=%%B!AffectedBox1!-%3:%1%%I =%%
 call set B!AffectedBox2!-%3=%%B!AffectedBox2!-%3:%%I%2 =%%
 

 set C%%I-%3=!C%%I-%3:%2=!
 set R%%I-%3=!R%%I-%3:%1=!
 set S%1-%%I=!S%1-%%I:%3=!
 set S%%I-%2=!S%%I-%2:%3=!

 
 call set !SolutionBox%4-%%I!=%%!SolutionBox%4-%%I!:%3=%%

 call set C!SolutionBox%4-%%I:~1,1!-%3=%%C!SolutionBox%4-%%I:~1,1!-%3:!SolutionBox%4-%%I:~3,1!=%%
 call set R!SolutionBox%4-%%I:~3,1!-%3=%%R!SolutionBox%4-%%I:~3,1!-%3:!SolutionBox%4-%%I:~1,1!=%%


 for /f %%J in ("!R%%I-%3!") do (
  if "!R%%I-%3:~1!"=="" (
   set FoundSolutions=!FoundSolutions:%%J%%I%3=! %%J%%I%3
  )
 )

 for /f %%J in ("!C%%I-%3!") do (
  if "!C%%I-%3:~1!"=="" (
   set FoundSolutions=!FoundSolutions:%%I%%J%3=! %%I%%J%3
  )
 )

 for /f %%J in ("!S%1-%%I!") do (
  if "!S%1-%%I:~1!"=="" (
   set FoundSolutions=!FoundSolutions:%1%%I%%J=! %1%%I%%J
  )
 )

 for /f %%J in ("!S%%I-%2!") do (
  if "!S%%I-%2:~1!"=="" (
   set FoundSolutions=!FoundSolutions:%%I%2%%J=! %%I%2%%J
  )
 ) 

 call set Temp=%%B!AffectedBox1!-%3: =%%

 if NOT "!Temp!"=="" if "!Temp:~2!"=="" (
  call set FoundSolutions=%%FoundSolutions:!Temp!%3=%% !Temp!%3
 )
 

 call set Temp=%%B!AffectedBox2!-%3: =%%

 if NOT "!Temp!"=="" if "!Temp:~2!"=="" (
  call set FoundSolutions=%%FoundSolutions:!Temp!%3=%% !Temp!%3
 )

)

exit /b




:CheckForLineBoxRules

::This is an alternate algorithm to just checking for single solutions and algorithms.

::It is based on the idea that if all solutions for one number of one row/column lie in one box, then only solutions on that row/column are possible in that box.
::The opposite is true, that if all solutions for a certain number in a box line up horisontally or vertically (So they lie on one column/row), only solutions in that box are possible for that row/column.


for /l %%I in (1,1,9) do (
 for /l %%J in (1,1,9) do (
  if NOT "!B%%I-%%J: =!"=="" (
   if "!B%%I-%%J:~9!"=="" (
    if "!B%%I-%%J:~0,1!" equ "!B%%I-%%J:~3,1!" (
     if "!B%%I-%%J:~6,1!"=="" (
      call :RemoveColumnSolutions %%I !B%%I-%%J:~0,1! %%J
     ) else (
      if "!B%%I-%%J:~0,1!" equ "!B%%I-%%J:~6,1!" (
       call :RemoveColumnSolutions %%I !B%%I-%%J:~0,1! %%J
      )
     )
    ) else (
     if "!B%%I-%%J:~1,1!" equ "!B%%I-%%J:~4,1!" (
      if "!B%%I-%%J:~7,1!"=="" (
       call :RemoveRowSolutions %%I !B%%I-%%J:~1,1! %%J
      ) else (
       if "!B%%I-%%J:~1,1!" equ "!B%%I-%%J:~7,1!" (
        call :RemoveRowSolutions %%I !B%%I-%%J:~1,1! %%J
       )
      )
     )
    )
   )
  )


  if NOT "!C%%I-%%J: =!"=="" (
   if "!C%%I-%%J:~3!"=="" (
    call :CheckSameBoxCOLUMN %%J %%I !C%%I-%%J:~0,1! !C%%I-%%J:~1,1! !C%%I-%%J:~2,1!
   )
  )

  if NOT "!R%%I-%%J: =!"=="" (
   if "!R%%I-%%J:~3!"=="" (
    call :CheckSameBoxROW %%J %%I !R%%I-%%J:~0,1! !R%%I-%%J:~1,1! !R%%I-%%J:~2,1!
   )
  )

 )
)


goto ReprocessSolutions



:CheckSameBoxCOLUMN
::Recieves %1=Value %2=X %3=Y1 %4=Y2 %5=Y3

if "%4"=="" exit /b

set /a AffectedBox1= ( ( (%2 + 2 ) / 3) + ( ( ( %3 - 1 ) / 3 ) * 3 ) )
set /a AffectedBox2= ( ( (%2 + 2 ) / 3) + ( ( ( %4 - 1 ) / 3 ) * 3 ) )
set /a AffectedBox3= ( ( (%2 + 2 ) / 3) + ( ( ( %5 - 1 ) / 3 ) * 3 ) )

if NOT "%AffectedBox1%"=="%AffectedBox2%" exit /b

if NOT "%5"=="" (
 if NOT "%AffectedBox1%"=="%AffectedBox3%" exit /b
)

for /l %%I in (1,1,9) do (
 if NOT "!SolutionBox%AffectedBox1%-%%I:~1,1!"=="%2" (
  call set !SolutionBox%AffectedBox1%-%%I!=%%!SolutionBox%AffectedBox1%-%%I!:%1=%%
 )
)

set Temp=

for /l %%I in (0,3,27) do (

 if "!B%AffectedBox1%-%1:~%%I,1!"=="" (
  set B%AffectedBox1%-%1=!Temp!
  exit /b
 )

 if "!B%AffectedBox1%-%1:~%%I,1!"=="%2" (
 set Temp=!Temp!!B%AffectedBox1%-%1:~%%I,3!
 )
)
exit /b 







:CheckSameBoxROW
::Recieves %1=Value %2=Y %3=X1 %4=X2 %5=X3

if "%4"=="" exit /b

set /a AffectedBox1= ( ( (%3 + 2 ) / 3) + ( ( ( %2 - 1 ) / 3 ) * 3 ) )
set /a AffectedBox2= ( ( (%4 + 2 ) / 3) + ( ( ( %2 - 1 ) / 3 ) * 3 ) )
set /a AffectedBox3= ( ( (%5 + 2 ) / 3) + ( ( ( %2 - 1 ) / 3 ) * 3 ) )

if NOT "%AffectedBox1%"=="%AffectedBox2%" exit /b

if NOT "%5"=="" (
 if NOT "%AffectedBox1%"=="%AffectedBox3%" exit /b
)

for /l %%I in (1,1,9) do (
 if NOT "!SolutionBox%AffectedBox1%-%%I:~3,1!"=="%2" (
  call set !SolutionBox%AffectedBox1%-%%I!=%%!SolutionBox%AffectedBox1%-%%I!:%1=%%
 )
)

set Temp=

for /l %%I in (0,3,27) do (

 if "!B%AffectedBox1%-%1:~%%I,1!"=="" (
  set B%AffectedBox1%-%1=!Temp!
  exit /b
 )

 if "!B%AffectedBox1%-%1:~%%I,2!"=="!B%AffectedBox1%-%1:~%%I,1!%2" (
 set Temp=!Temp!!B%AffectedBox1%-%1:~%%I,3!
 )
)
exit /b 






:RemoveColumnSolutions
::Recieves %1=Box Number %2=X %3=Value

for /l %%I in (1,1,9) do set /a AffectedBox= ( ( (%2 + 2 ) / 3) + ( ( ( %%I - 1 ) / 3 ) * 3 ) )&& (
 if NOT !AffectedBox!==%1 (
  set C%2-%3=!C%2-%3:%%I=!
  call set B!AffectedBox!-%3=%%B!AffectedBox!-%3:%2%%I =%%
  set S%2-%%I=!S%2-%%I:%3=!
 )
)
exit /b


:RemoveRowSolutions
::Recieves %1=Box Number %2=Y %3=Value

for /l %%I in (1,1,9) do set /a AffectedBox= ( ( (%%I + 2 ) / 3) + ( ( ( %2 - 1 ) / 3 ) * 3 ) )&& (
 if NOT !AffectedBox!==%1 (
  set R%2-%3=!R%2-%3:%%I=!
  call set B!AffectedBox!-%3=%%B!AffectedBox!-%3:%%I%2 =%%
  set S%%I-%2=!S%%I-%2:%3=!
 )
)
exit /b



:ReprocessSolutions

::Here, all the solutions, boxes, rows and columns are checked for single solutions/locations. This is because the Line-Box rule may have created single solutions.


for /l %%I in (1,1,9) do (
 for /l %%J in (1,1,9) do (
 
  for /f %%K in ("!R%%I-%%J!") do (
   if "!R%%I-%%J:~1!"=="" set FoundSolutions=!FoundSolutions:%%K%%I%%J=! %%K%%I%%J
  )


  for /f %%K in ("!B%%I-%%J!") do (
   if "!B%%I-%%J:~3!"=="" set FoundSolutions=!FoundSolutions:%%K%%J=! %%K%%J
  )


  for /f %%K in ("!C%%I-%%J!") do (
   if "!C%%I-%%J:~1!"=="" set FoundSolutions=!FoundSolutions:%%I%%K%%J=! %%I%%K%%J
  )


  for /f %%K in ("!S%%J-%%I!") do (
   if "!S%%J-%%I:~1!"=="" set FoundSolutions=!FoundSolutions:%%J%%I%%K=! %%J%%I%%K
  )


 )
)


if NOT "%FoundSolutions: =%"=="" goto ProcessFoundSolutions



:EndSolving

::The code only reaches this point if the code cannot solve the puzzle.


::If all 81 numbers are filled then the puzzle has been solved.

if %InputCount%==81 (
echo Puzzle Solved^!
) ELSE (
echo Sorry, your puzzle could not be solved.
echo It may have been entered incorrectly, or it is too difficult for this program.
)



:RequestStartAgain

::Asks if the user wants to enter another puzzle.

echo Would you like to start again? [Y/N]

set /p Response=
if /i "%Response:~0,1%"=="Y" goto ClearPreviousPuzzle
if /i "%Response:~0,1%"=="N" exit /b
goto RequestStartAgain



:ClearPreviousPuzzle

::Clears all variables that may interfere with the next puzzle entered.

for /l %%I in (1,1,9) do (
 for /l %%J in (1,1,9) do (
  set S%%J-%%I=
  set P%%J-%%I=
  set R%%J-%%I=123456789
  set C%%J-%%I=123456789
  set B%%J-%%I=123456789
  set InputCount=0
 )
)


goto SetUpAllSolutionSpaces


No comments:

Post a Comment