I'm making a Text Adventure game using batch-script, and I would like to know if there is a way to program it to automatically save at a certain point.
I'm asking this as I recently read that a youtuber once coded one of his RPGs to autosave, though he never revealed it publicly.
So is there a way to make a batch RPG/text adventure publish autosaves? If so, how do you do it?
No reason go go haring off in pursuit of others' obsession with their one-true-way. Like any language, batch has its quirks, but the fundamental methodology is common.
With procedural languages, I've followed a similar structure for decades. The key is to simply only ever have ONE procedure that interacts with the user. The rest of the program thenbecomes a state-machine, directed from one state to the next in response to the user's input. It may take a little getting used to, but I've successfully (and quickly) developed programs using this principle in batch, cobol, pascal, basic, progress, mantis and others.
#ECHO OFF
SETLOCAL
SET state=0
SET "savefile="
:mainloop
CALL :state%state%
IF %state% neq 999999 GOTO mainloop
GOTO :EOF
::
:: USER I/O
::
:userio
IF NOT DEFINED savefile GOTO useriolp
set>%savefile%
:useriolp
SET "response="
SET /p response="%message% ? "
IF NOT DEFINED response GOTO useriolp
SET response=%response: =_%
FOR %%r IN (EXIT quit) DO IF /i %response%==%%r SET state=999999&GOTO :EOF
SET "state="
FOR %%r IN (%vr%) DO IF DEFINED state (
IF /i %%r==%response% GOTO :EOF
SET "state="
) ELSE (SET state=%%r)
IF DEFINED state GOTO :eof
ECHO Invalid response
GOTO useriolp
::State 0 - get username
:state0
SET "vr=100"
SET message=Your name
GOTO userio
:: State 100 - have username.
:: Load user's savefile if it exists
:state100
SET savefile=%response%
IF exist %savefile% GOTO S100.1
:: New game - initialise data
:: set start-state in STATE
SET example=exampledata
SET state=1000
GOTO :eof
:: Load game - including STATE
:S100.1
SET "state="
FOR /f "tokens=1*delims==" %%i IN (%savefile%) DO IF NOT defined %%i SET %%i=%%j
GOTO :eof
:: State 1000 - start game
:state1000
:: ** Just for a demo, show example as loaded from savefile
ECHO example=%example%
ECHO You see a shimmering blue-hued house with four doors numbered 1,2,3 and 4
SET "vr=1001 1 1002 2 1003 3 1004 4 1001 one 1002 two 1003 three 1004 four"
SET message=Which door do you choose
GOTO userio
:: State 1001 - door 1
:state1001
ECHO You see John Lodge
SET example=John Lodge
:: OK, from here, you're on your own. I'll force a state and loop
SET state=1000
GOTO :eof
:: State 1002 - door 2
:state1002
ECHO You see Graeme Edge
SET example=Graeme Edge
:: OK, from here, you're on your own. I'll force a state and loop
SET state=1000
GOTO :eof
:: State 1003 - door 3
:state1003
ECHO You see Justin Hayward
SET example=Justin Hayward
:: OK, from here, you're on your own. I'll force a state and loop
SET state=1000
GOTO :eof
:: State 1004 - door 4
:state1004
ECHO You see Ray Thomas
SET example=Ray Thomas
:: OK, from here, you're on your own. I'll force a state and loop
SET state=1000
GOTO :eof
With the above code, you can see a skeleton. :mainloop simply establishes a 'repeat until exitstructure, whereEXIT` is state=999999. I've used numbers for the states, but that's just a matter of choice.
For any particular state, you (optionally) do something, then set up a prompt message (don't call it prompt - that's a batch keyword) and a set of valid responses (vr is easier to type than valid_responses) then go to the :userio procedure (which returns to the :mainloop after accepting a response.
:userio saves the entire current environment (hence having an "autosave" feature) then prompts with the message and acepts a response. Set /p will leave response unchanged if enter is pushed, so setting response to [nothing] first will ensure that enter doesn't repeat the previous response.
I've chosen to replace any space characters entered with underscores - this makes processing easier as you don't have to worry about spaces in strings (it's not complete - commas, tabs and semicolons; redirectors and ampersand can also be problematic...)
Next, if the user enters EXIT or QUIT the exit state is assigned.
Otherwise, the valid-responses sting is matched to the response received. The vr string is simply
nextstate ifthisresponse nextstate ifthisresponse nextstate ifthisresponse nextstateotherwise
nextstateotherwise does not need to appear, and if it doesn't, an invalid response message is generated and the input re-requested.
So - from state 0, you're promted for your name, and the next state will be 100 regardless.
State100 sets savefile to the user's response (name) and if that file exists, any variable recorded on the savefile is reloaded into the environment, restoring the game to its exact state the last time the program was EXITed. If the file doesn't exist, then you have the opportunity to establish any game data you need - I've just set up a variable example as an - er, example.
State1000 starts the game. I've just shown the value of example for demo purposes. It should start out as exampledata as established in state100-no-savefile.
So - you get a scenario-description, establish the prompt message and vr defines the next state to be 1001 if your response is (1 or one); 1002 for (2 or two)...
Choosing 1,2,3 or 4 switches you to the next state; the example string is changed, and - well, I've forced state1000 to be the destination. What you'd do is simply follow the bouncing ball in state1000.
Now - if you type exit, the program ends. Re-run and type the same username, and the value of example will be displayed as the last-established value, restored from the savefile.
As a little extension, you could add a help system - simply by adding in
IF /i %response%==help call :help&goto useriolp
after the FOR %%r IN (EXIT quit) ... line. Note that you can make the :help routine context-sensitive because you have the value of state to tell you where you are.
Equally, you could implement a look system, if you like, same principle.
All in batch. No need for other languages at all... :)
There are scores, perhaps hundreds of languages that are better suited to adventure gaming than MS-DOS batch scripts. Try Python, TCL, Lua, Java, C#, or any other language besides batch. An object oriented language is often used for fantasy adventure games because of their ability to do modelling well. A language with a persistence framework (such as Hibernate) might be handy for auto-save. But that tends to involve storing data in a relational database, which are also good for modelling. But don't try to bite off too much at a time.
Since you are in the frame of mind to try a text based game, with a very simple language. You might do well with TCL. But as I said, there are scores of good options. Batch is not considered one of them.
This is actually quite simple, although it consumes a large amount of space, depending on how many variables you wish to save. You want to have another batch file in the same directory as the batch file you are running. This batch file can be titled save.bat, variables.bat, or whatever. For every action completed, you want it to save the variables to that batch file. For instance:
#echo off
call save.bat
:direction
cls
echo.
echo Would you like to
echo 1) go north
echo 2) go south
set /p answer=:
if %answer% equ 1 goto north
if %answer% equ 2 goto south
cls
echo.
echo That command isn't recognized.
echo.
pause
goto direction
:north
echo set /a money=%money%>>save.bat
echo set /a health=%health%>>save.bat
echo set /a mana=%mana%>>save.bat
cls
echo.
echo You head North.
echo.
pause
exit
:south
echo set /a money=%money%>>save.bat
echo set /a health=%health%>>save.bat
echo set /a mana=%mana%>>save.bat
cls
echo.
echo You head South.
echo.
pause
exit
You notice how it saves the variables to save.bat, which is then called at the beginning of the game. The >> indicate that it's adding a new line to the file. If you just use a single > it will delete everything from before, and add the variable to the file. Although you will have to save the variables after every action, it's worth it. If you would like to conserve space, you could do this:
#echo off
call save.bat
:direction
cls
echo.
echo Would you like to
echo 1) go north
echo 2) go south
set /p answer=:
if %answer% equ 1 goto north
if %answer% equ 2 goto south
cls
echo.
echo That command isn't recognized.
echo.
pause
goto direction
:north
call autosave.bat
cls
echo.
echo You head North.
echo.
pause
exit
:south
call autosave.bat
cls
echo.
echo You head South.
echo.
pause
exit
And inside of autosave.bat:
echo set /a money=%money%>>save.bat
echo set /a health=%health%>>save.bat
echo set /a mana=%mana%>>save.bat
So yeah, that's how you can autosave the game.
Related
I'm trying to create a batch file that will search a .txt file for a specific word and give me a response based on the word found. But when i search for "time" and "timer", they both give the same response as if it only found "time". Is there any way i can search for the whole word or a whole series of words and not just a fraction of the word?
I've tried quoting the words and double quoting the words/phrases but it still gives the same response
This is a section of the code:
:: Searches for the word "time" and if found, runs the file "Time.bat" in the folder, modules
:Q3
find /I "Time" CurrentConversation.txt
if errorlevel 1 (
goto :Q4
) else (
Call Modules/Time.bat
)
:: Searches for the word "timer" and if found, runs the file "Timer.bat" in the folder, modules
:Q4
find /I "Timer" CurrentConversation.txt
if errorlevel 1 (
goto :Q5
) else (
call Modules/Timer.bat
)
I expected that if the file "CurrentConversation.txt" had the word timer then it would run "Timer.bat". And that if the file had the word time then it would run "Time.bat" but instead it only runs "Time.bat" regardless of the word timer being present
There are some better ways to achieve this, but to stick to your current code..
You need to realise that the string Timer also contains Time so if it will launch time.bat as the search matches. So let's rather use findstr and we tell it to search for Time ending \> so that it does not match Timer when searching for Time.
:Q3
findstr /I "Time\>" CurrentConversation.
if not errorlevel 0 goto :Q4
Call Modules\Time.bat
:: Searches for the word "timer" and if found, runs the file "Timer.bat" in the folder, modules
:Q4
findstr /I "Timer" CurrentConversation.txt
if not errorlevel 0 goto :Q5
call Modules\Timer.bat
I also made some changes to get rid of the code blocks, it is not needed here.. We just test for if not errorlevel 0. Once it does not meet that errorlevel it will fall through to the call bat.bat. If however it is anything other than errorlevel 0it will run goto.
Also, another way to do the line mathcing would be a double findstr to include and exclude, but it is a bit of an overkill.
findstr /I "Time" CurrentConversation.txt | findstr /VI "Timed" test.txt CurrentConversation.txt
See help from cmd.
findstr /?
if /?
You should be able to do that as a single line in a batch-file
For %%A In (Time,Timer)Do FindStr /IRC:"\<%%A\>" "CurrentConversation.txt">Nul&&Call "Modules\%%A.bat"
Just propagate the content between the parentheses with your comma or space separated words, and you should be good to go.
I need some help in using the CMD. I am an amateur programmer so please be gentle with technical terms! However, I think that what I need is quite simple for a programmer!
I am developing a simple text encode/decode bot (with WinAutomation software) that will run in a very slow MS Windows PC. I use my own encoding/decoding method for years so I am not interested in using a ready-made solution.
I want to be able to replace any letter in any text (as a variable) with symbols and vice versa using the command line.
Example:
“a” will be “[?]”
“b” will be “[*]”
“c” will be “[/]”
Vice versa
“[?]” will be “a”
“[*]” will be “b”
Etc
I am currently using a loop to do the 130 letter replacements. Loop is executed very fast and causes CPU and memory overload in slow PC. From the other hand, If I place a waiting action at the end of the loop (1 sec minimum), the replacements will finish in 130 seconds. This is time wasting!
I need a ONE LINE code in order to make the replacements at once through CMD.
A programmer suggested me the piece of code below that seems to work but it is not one line
SET _test=12345abcabc
SET _result=%_test:ab=xy%
SET _result=%_result:1=2%
How can I convert it in one line? I have tried && and || but I am doing something wrong!
Below I have uploaded an image from WinAutomation’s Run Command Window. You can view it here https://imgur.com/3hbcCZE.
Thank you
This can be done in one line. ENABLEDELAYEDEXPANSION needs to be set. The code before and after the one (1) line is just to ensure that the variables are set to nothing and show that they are set to nothing.
C:>type and.bat
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "_test="
SET "_result="
ECHO _test is %_test%, _result is %_result%
REM Here is the one (1) line.
SET "_test=12345abcabc" && SET "_result=!_test:ab=xy!" && SET "_result=!_result:1=2%!
ECHO _test is %_test%, _result is %_result%
Here is a result from running the script.
C:>CALL and.bat
_test is , _result is
_test is 12345abcabc, _result is 22345xycxyc
I need a method to split files into multiple (or even half) based on KB not on number of lines.
I am a Senior EDI Analyst and wrapped data tends to show up as one single long line. Every "solution" I find splits based on number of lines. I need something that will split based on size.
The end-goal is to "Unwrap" this data, meaning each segment will be on its own line. To do this I need to change the delimiters (as there are "special characters" as delimiters).
I do have a solution for that (see below), but for some reason this will not work on files larger than 10 KB. If you know anything about EDI, that's not very big.
I need to find a solution to split files into smaller files of about 5KB each (then I can use the string replacement and re-combine them myself).
Does anyone have an idea of how I might accomplish this with one, huge line?
(Sorry I have to remove the code I placed here only as AN EXAMPLE because someone flagged this as a duplicate WITHOUT READING IT. Please read above and advise.)
The reason you cannot process files > 10k byte is because batch variables (and command lines) are limited to ~8191 bytes.
You are attacking the problem in an inefficient way. Rather than look for a way to split a file into chunks so that you can use your slow batch "solution", you should be looking for a tool that allows you to work with the large files directly, without resorting to splitting, processing, and re-assembly.
As others have stated, PowerShell, JavaScript, and VBS are all good scripting languages that can solve your problem, and they are native to Windows.
If your files are all less than 1 gigabyte in length, then I suggest you try JREPL.BAT - a regex text processing utility. It is pure script (hybrid batch/JScript) that runs natively on any Windows machine from XP onward - no 3rd party exe file required. Full documentation is available from the command line via jrepl /?, or jrepl /?? for paged help.
To Unwrap a file, translating | into *\r\n (\r is carriage return, and \n a newline):
jrepl "|" "*\r\n" /l /m /x /f "wrappedFileName" /o "unwrappedFileName"
To wrap a file (reverse the process)
jrepl "*\r\n" "|" /l /m /x /f "unwrappedFileName" /o "wrappedFileName"
If you put either command within a batch script, then you must use call jrepl instead of jrepl. This is because JREPL is also a batch script, so control will not return to your script unless you use CALL.
Although your description is extensive, there are multiple points that are not clear. There are too many unrelated details that just deviates from the core point of the problem. If each segment in the line is separated by a | delimiter (you did not explained this point, but it is assumed from the example code) and you want to split the file based on a certain KB size (you did not specified how many KB), then a segment may be splitted in two different files. Also, I don't understand how changing the | delimiters by asterisks may help to solve the problem. After read this question several times, I assumed that the problem is this:
"Split a file that just contain a very long line (with not a single CR+LF pair) into segments delimited by | character, so each segment will be on its own line".
The Batch file below is a solution for this problem:
#echo off
setlocal EnableDelayedExpansion
call :ProcessFile < input.txt > output.txt
goto :EOF
:ProcessFile
set "previous="
:nextChunk
rem Read the next 1023-bytes chunk
set /P "chunk="
if errorlevel 1 goto endOfFile
rem Break segment if previous one ends at a chunk limit
if "!chunk:~0,1!" equ "|" if defined previous (
echo !previous!
set "previous="
)
rem Extract each segment from the chunk and place it on its own line
set "last="
for /F "delims=" %%a in (^"!chunk:^|^=^
% This line separate segments by the given delimiter %
!^") do (
if defined last echo !last!
set "last=!previous!%%a"
set "previous="
)
set "previous=!last!"
goto nextChunk
:endOfFile
rem Show the last segment
if defined previous echo !previous!
exit /B
EDIT: JScript solution added
As others have mentioned, you may also use a solution based on JScript, that is a standard programming language preinstalled in all Windows versions from XP on. In this way, the solution is really simple, because you just need to insert the following two lines in your Batch file:
echo WScript.Stdout.Write(WScript.Stdin.ReadAll().replace(/\^|/g,"\r\n")) > replace.js
cscript //nologo replace.js < input.txt > output.txt
This is a very simple, but powerful method that you may use in other similar replace operations; just read the corresponding documentation.
Split file into 5kB chunks:
set file="x.edb"
set max=5000
REM Findstr line limit 8k
REM Workaround: wrap in an archive to generate CRLF pairs for chunks > 8kB
for %i in (%file%) do (
set /a num=%~zi/%max% >nul &REM No. of chunks
set /a last=%~zi%%max% >nul &REM size of last chunk
if %last%==0 set /a num=num-1 &REM ove zero byte chunk
set size=%~zi
)
ren %file% %file%.0
for /l %i in (1 1 %num%) do (
set /a s1=%i*%max% >nul
set /a s2="(%i+1)*%max%" >nul
set /a prev=%i-1 >nul
echo Writing %file%.%i
type %file%.!prev! | (
(for /l %j in (1 1 %max%) do pause)>nul& findstr "^"> %file%.%i)
FSUTIL file seteof %file%.!prev! %max% >nul
)
if not %last%==0 FSUTIL file seteof %file%.%num% %last% >nul
echo Done.
Tested on Win 10
When I run the following code, the batch processing is exited with a syntax error message output that it does not recognize the opening parenthesis.
if %errorlevel2%==s (
echo.
echo.
echo Press any key to continue...
echo.
echo.
pause>nul
start haxmenu\pin.bat
goto menu
)
Are my parentheses set wrong or is something about code inside the block which lets the script fail on execution?
The error message is:
( was unexpected at this time.
Introduction
So you have a code similar to this one:
#echo off
:menu
set /P "errorlevel2=Please enter your choice: "
if %errorlevel2%==s (
echo/
echo/
echo Press any key to continue...
echo/
echo/
pause>nul
start haxmenu\pin.bat
goto menu
)
First you have to take into consideration that Windows command processor replaces %errorlevel2% with the string of this variable before the line is parsed for a command.
So it depends on existence and value of environment variable errorlevel2 how the line with the IF condition looks like on execution of the batch file.
For example with errorlevel2 not defined at all and user on prompt just hits the key RETURN or ENTER the variable errorlevel2 is still not existing after user prompt and the IF condition line looks therefore as follows:
if ==s (
This IF condition is of course invalid and command processor exits batch processing because of the syntax error.
Using double quotes around both strings to compare is a first step to avoid the syntax error.
if "%errorlevel2%"=="s" (
Now the resulting line on execution of the batch file with nothing entered by the user is:
if ""=="s" (
This is a valid syntax.
But it does not prevent your batch code completely on exiting because of a syntax error as it can be seen by entering on prompt for example a single double quote. This would result in
if """=="s" (
which results again in an exit of batch processing because of a syntax error caused by the odd number of double quotes on left side of the equal operator.
Before thinking about a solution for this additional issue, we should also think about what happens when the user really just press RETURN or ENTER on prompt without entering anything. Run the following code:
#echo off
set /P "UserInput=Enter a number: "
echo UserInput=%UserInput%
set /P "UserInput=Hit key RETURN: "
echo UserInput=%UserInput%
pause
After entering on first prompt a number like 23 and hit on second prompt just RETURN as requested, the number 23 is output also a second time.
The current value of the environment variable is not modified if the user just hits RETURN on user prompt. This should be always taken into account when using set /P in a menu, especially if this menu is within a loop because in this case the variable has already a value after first choice made by the user which would be kept unmodified on next prompt if the user does not enter anything.
Solution 1: Remove all double quotes before string comparison
One solution is predefining the variable with a single double quote and remove all double quotes from input string.
#echo off
:menu
set "errorlevel2=""
set /P "errorlevel2=Please enter your choice: "
set "errorlevel2=%errorlevel2:"=%"
if /I "%errorlevel2%"=="s" (
echo/
echo/
echo Press any key to continue...
echo/
echo/
pause>nul
start haxmenu\pin.bat
goto menu
)
It is important that the value of variable errorlevel2 is predefined with a string containing at least one double quote for this solution if not one of the options the user should choose from is defined as default value because the variable errorlevel2 must exist with any value to avoid a syntax error on line removing all double quotes from string.
See Why is no string output with 'echo %var%' after using 'set var = text' on command line? for an explanation why using syntax set "variable=value" even with value containing a double quote.
Solution 2: Using delayed expansion on string comparison
A syntax error on string comparison because of string entered by the user could be avoided by using delayed expansion because the IF condition line itself is not modified in this case by the input string.
#echo off
setlocal EnableDelayedExpansion
:menu
set "errorlevel2="
set /P "errorlevel2=Please enter your choice: "
if "!errorlevel2!"=="s" (
echo/
echo/
echo Press any key to continue...
echo/
echo/
pause>nul
start haxmenu\pin.bat
goto menu
)
endlocal
The variable errorlevel2 is deleted now always before prompting the user.
Whatever the user enters does not modify the batch code line itself and therefore the string comparison always works even with a string entered with an odd number of double quotes.
Solution 3: Using command choice for simple menus
Windows offers the command choice (Microsoft article) for tasks where a user should select one of offered options by key. Using this command is nearly always better for single character choices than using set /P.
But how choice (SS64 article) can be used depends also on version of Windows respectively which version of choice is used as there are multiple versions with different syntax and capabilities. choice should be used for simple menus if compatibility of batch file with Windows versions prior Windows Vista is not needed anymore as the batch file is definitely never run on Windows XP or even older Windows.
In all probability, the problem is that errorlevel2 is not set (ie is set to nothing
You could get over this with
if "%errorlevel2%"=="s" (
but It's likely that you're expecting errorlevel2 to have been set in a block statement (ie within a parenthesised series of statements) and in that case, you'd need to look at about 10,000 aricles on SO relating to delayedexpansion.
Or, of course, show us a little more of your code - back to the if or do that's supposed to set errorlevel2
I am trying to write a batch program that writes more code based on what the user inputs. I am stuck on the string manipulation, as "!", ">", "&", and "%" all need to be escapeded before they are to be outputted to other files. This is what I have so far:
#echo off
set /p code=
set newcode=%code:^%=^%%,^>=^^>,!=^^!,^&=^^&%
echo %newcode%>>file.bat
All this escapinging escaped stuff is making my brain hurt, so can you please help me?
Thanks!
Since you haven't explained clearly what you are trying to do with the input, I am assuming you are trying to get the user to type something and then for that to be entered into a file. If that is the case te utilise copy in combination with con
#echo off
Echo Enter ^^Z (Ctrl + Z) and return it to end input
copy con file.txt
And that will allow the user to type WHATEVER they want, and it to be put in a file to be examined. Otherwise you're gonna have a lot of fun (And trouble) dealing with all the escaping you're gonna have to do (Rather ironic wasn't that?).
Mona.
Simply use delayed expansion in your case, as delayed expansion needs no further escaping for the content.
#echo off
setlocal EnableDelayedExpansion
set /p code=
echo(!code!
The echo( is for safe text output, even if the text is empty or is something like /?.
To the comment of #foxidrive:
When delayed expansion is enabled, then exclamation marks ! are altered, when they are visible before the delayed expansion is done, but the expanded content of delayed expanded variables will never be altered.
#echo off
set "var=Hello!"
setlocal EnableDelayedExpansion
echo "!var!" "Hello!"
This will result to: "Hello!" "Hello"