Incremental Progress Bar in Windows Batch File - windows

I have a batch file which scans the computer taking various readings and storing each in to a variable. During this time I would like a little more than a simple message telling the user to 'Please wait - Gathering system information'.
I've tried the quick, easy, (and poor), way of doing this, as follows, as I want this to be a true incremental progress bar.
cls
echo.
echo.
echo Please wait - Gathering system information
echo ----------------------------------
echo Progress: ░░░░░░░░░░░░░░░░░░░░ 0%%
echo ----------------------------------
>nul timeout /t 1 &cls
cls
REM Do something here.
echo.
echo.
echo Please wait - Gathering system information...
echo ----------------------------------
echo Progress: █░░░░░░░░░░░░░░░░░░░ 5%%
echo ----------------------------------
>nul timeout /t 1 &cls
cls
REM Do something else here.
echo.
echo.
echo Please wait - Gathering system information.
echo ----------------------------------
echo Progress: ██░░░░░░░░░░░░░░░░░░ 10%%
echo ----------------------------------
>nul timeout /t 1 &cls
cls
REM And something else..
echo.
echo.
echo Please wait - Gathering system information..
echo ----------------------------------
echo Progress: ███░░░░░░░░░░░░░░░░░ 15%%
echo ----------------------------------
>nul timeout /t 1 &cls
cls
REM Etc...
However, the effect is not exactly great, as the cls command causes the command window to flicker, and there has to be a 'false' pause after each update, which slows the whole process… Also not great.
I have the following that looks great, but I have no idea how to 'increment' the bar after every section of code, as per my initial example, and having it play from 0-100% before running anything else is a bit pointless.
#echo off &cls
mode con: cols=70 lines=5 &color f0
call :setESC
chcp 65001 >nul
set progress=
set/a progressnum=0
:gettingdata
set progress=%progress%%ESC%[96m█%ESC%[30m
cls
echo.
echo. Please wait - Gathering system information...
echo. %progress% (%progressnum%/20)
ping localhost -n 2 >nul
set/a progressnum=%progressnum% +1
if %progressnum%==20 goto finished
goto gettingdata
:finished
echo.
echo. Finished
echo. %ESC%[92m████████████████████%ESC%[30m (20/20)
echo. Press any key to exit &>nul timeout /t -1 &exit /B
:setESC
REM Define escape char
for /F "tokens=1,2 delims=#" %%a in ('"prompt #$H#$E# & echo on & for %%b in (1) do rem"') do (
set ESC=%%b
exit /B 0
)
So, my question is, how can I get the effect of the second script, but actually increment the bar after each section of my batch script has executed?

Ok I figured it out (with the help of this post).
Below is how I set up and used the script with only small additions to make it work with Unicode symbols.
First, set up your environment however you please. For the purpose of this post we will use the following:
#echo off &cls
mode con: cols=70 lines=4 &color f0
setlocal
Now in between each segment of your code you'll need to define the percentage of the progress bar by calling :drawProgressBar like so:
REM Your code here (segment 1)
REM - Progress Bar 0% - 33% ------------------------------------------------------
echo. Please wait - Gathering system information...
for /l %%f in (0 1 33) do (
call :drawProgressBar %%f "Code segment 1 finished"
)
REM ------------------------------------------------------------------------------
Repeat as required increasing the percentage each time like so:
REM Your code here (segment 2)
REM - Progress Bar 34% - 66% -----------------------------------------------------
for /l %%f in (34 1 66) do (
call :drawProgressBar %%f "Code segment 2 finished"
)
REM ------------------------------------------------------------------------------
REM Your code here (segment 3)
REM - Progress Bar 67% - 100% ----------------------------------------------------
for /l %%f in (67 1 100) do (
call :drawProgressBar %%f "Code segment 2 finished"
)
REM ------------------------------------------------------------------------------
REM The rest of your script goes here
endlocal
echo. &echo. All done. Press any key to Exit. &>nul timeout /t -1 &exit /B
Addendum: If you would like each segment to show 0-100% (as opposed to counting up from 0-100% overall) then you can use the following each time and simply update the description:
REM - Progress Bar 0% - 100% -----------------------------------------------------
for /l %%f in (0 1 100) do (
call :drawProgressBar %%f "Code segment finished"
)
REM ------------------------------------------------------------------------------
Ok to continue and this is personal preference here, but I then like to resize the CMD Window with another cls &mode con: cols=70 lines=60 &color f0 (or whatever size and/or colour you need) then continue to the output. As I say, this is just personal choice but I think it looks better with the progress meter in a smaller windows while the information is gathered before moving on to the 'main output' of the script.
Finally, add the following to the end of your batch file:
:drawProgressBar value [text]
if "%~1"=="" goto :eof
if not defined pb.barArea call :initProgressBar
setlocal enableextensions enabledelayedexpansion
set /a "pb.value=%~1 %% 101", "pb.filled=pb.value*pb.barArea/100", "pb.dotted=pb.barArea-pb.filled", "pb.pct=1000+pb.value"
set "pb.pct=%pb.pct:~-3%"
if "%~2"=="" ( set "pb.text=" ) else (
set "pb.text=%~2%pb.back%"
set "pb.text=!pb.text:~0,%pb.textArea%!"
)
<nul set /p "pb.prompt= !pb.fill:~0,%pb.filled%!!pb.dots:~0,%pb.dotted%! [%pb.pct%%%] %pb.text%!pb.cr!"
endlocal
goto :eof
:initProgressBar [fillChar] [dotChar]
chcp 65001 >nul
if defined pb.cr call :finalizeProgressBar
for /f %%a in ('copy "%~f0" nul /z') do set "pb.cr=%%a"
if "%~1"=="" ( set "pb.fillChar=▓" ) else ( set "pb.fillChar=%~1" )
if "%~2"=="" ( set "pb.dotChar=▒" ) else ( set "pb.dotChar=%~2" )
set "pb.console.columns="
for /f "tokens=2 skip=4" %%f in ('mode con') do if not defined pb.console.columns set "pb.console.columns=%%f"
set /a "pb.barArea=pb.console.columns/2-2", "pb.textArea=pb.barArea-9"
set "pb.fill="
setlocal enableextensions enabledelayedexpansion
for /l %%p in (1 1 %pb.barArea%) do set "pb.fill=!pb.fill!%pb.fillChar%"
set "pb.fill=!pb.fill:~0,%pb.barArea%!"
set "pb.dots=!pb.fill:%pb.fillChar%=%pb.dotChar%!"
set "pb.back=!pb.fill:~0,%pb.textArea%!
set "pb.back=!pb.back:%pb.fillChar%= !"
endlocal & set "pb.fill=%pb.fill%" & set "pb.dots=%pb.dots%" & set "pb.back=%pb.back%"
chcp 1252 >nul
goto :eof
:finalizeProgressBar [erase]
if defined pb.cr (
if not "%~1"=="" (
setlocal enabledelayedexpansion
set "pb.back="
for /l %%p in (1 1 %pb.console.columns%) do set "pb.back=!pb.back! "
<nul set /p "pb.prompt=!pb.cr!!pb.back:~1!!pb.cr!"
endlocal
)
)
for /f "tokens=1 delims==" %%v in ('set pb.') do set "%%v="
goto :eof
This is probably an easy task for the seasoned guys & gals here but I struggled with how to do this so I thought that I would share what worked for me and how I used the script.
Full credit goes to MC ND who is the original author of the script used. Please see the original post for more customisation options.

Related

Arrange the pinging of multiple website in order with batch?

This is a batch that pings the servers of a game I play, so that I am able to find the best server for me.
Is there a way I can have it ping all the servers then list them in order from the slowest response to the highest?
#TITLE OSRS Ping Checker
#ECHO off
SET usaworlds=5,6,7,13,14,15,19,20,21,22,23,24,29,30,31,32,37,38,39,40,45,46,47,48,53,54,55,56,57,61,62,69,70,74,77,78,86,117
#ECHO ---------------------------------------------------
#ECHO USA
#ECHO ---------------------------------------------------
FOR %%i IN (%usaworlds%) DO (
Echo | SET /p=World %%i
FOR /F "tokens=5" %%a IN ('Ping oldschool%%i.runescape.com -n 1 ^| FIND "time="') DO Echo %%a
)
PAUSE
#ECHO off
setlocal enabledelayedexpansion
TITLE OSRS Ping Checker
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
del x.tmp 2>nul
SET usaworlds=5,6,7,13,14,15,19,20,21,22,23,24,29,30,31,32,37,38,39,40,45,46,47,48,53,54,55,56,57,61,62,69,70,74,77,78,86,117
ECHO ---------------------------------------------------
ECHO USA
ECHO ---------------------------------------------------
FOR %%i IN (%usaworlds%) DO (
<nul set /p "=checking World %%i !CR!"
FOR /F "tokens=5" %%a IN ('Ping oldschool%%i.runescape.com -n 1 ^| FIND "TTL="') DO (
for /f "tokens=2 delims==" %%b in ("%%a") do (
set tim=00000%%b
set tim=!tim:~-7,-2!
)
)
echo !tim! World %%i>>x.tmp
)
for /f "tokens=3" %%c in ('sort /r x.tmp') do set fastest=%%c
echo fastest response from World %fastest%
PAUSE
Note: the time of a one-time ping isn't reliable (check the different times with ping -t) and so this approach may give you false results. better check "Average" with ping -n 5 or even higher, but that will of course decrease the performance of the script.
To make it faster and more reliable using the average times, you can run the pings in parallel (I have used that method years ago in another context)
#ECHO off
setlocal
TITLE OSRS Ping Checker
for /f %%a in ('copy /Z "%~dpf0" nul') do set "CR=%%a"
del %temp%\x.tmp 2>nul
SET usaworlds=wrgl,5,6,7,13,14,15,19,20,21,22,23,24,29,30,31,32,37,38,39,40,45,46,47,48,53,54,55,56,57,61,62,69,70,74,77,78,86,117
#ECHO ---------------------------------------------------
#ECHO USA
#ECHO ---------------------------------------------------
FOR %%i IN (%usaworlds%) DO (
start /min "Pinging" cmd /v:on /c "(FOR /F "tokens=9 delims= " %%a IN ('Ping oldschool%%i.runescape.com -n 5^|findstr /e "ms"') do set avrg= %%a)& >> %temp%\x.tmp echo ^!avrg:~-7,-2^!" World %%i
)
:wait
timeout 1 >nul
tasklist /FI "WINDOWTITLE eq Pinging" |find ".exe" >nul && goto :wait
for /f "tokens=3" %%c in ('sort /r %temp%\x.tmp') do set fastest=%%c
echo fastest response from World %fastest%
PAUSE
Using start /min instead of start /b does keep your screen clean in case of errors (makes it a bit slower, but you won't notice it)
Since there are many hosts to check/ping, sequencial processing lasts for a quite long time, particularly when following Stephan's recommendation of using more than one echo requests and taking the average reply time.
So I suggest to use a different approach and let the ping requests happen in simultaneous processes:
#title OSRS Ping Checker
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ARG=%~1" & if defined _ARG shift /1 & goto :DO_PING & ^
rem // (jump to label `DO_LOOP` in case arguments are provided)
set "_USAWORLDS=5,6,7,13,14,15,19,20,21,22,23,24,29,30,31,32,37,38,39,40,45,46,47,48,53,54,55,56,57,61,62,69,70,74,77,78,86,117"
set "_ECHOREQUS=10" & rem // (number of echo requests to send per host)
set "_TEMPFILEB=%TEMP%\%~n0_%RANDOM%" & rem // (path and base name of temporary files)
set "_FINALFILE=%~dpn0.txt" & rem // (path and full name of return file)
rem // Process items in sub-routine but in parallel processes:
for %%I in (%_USAWORLDS%) do (
rem // Redirect output of every process to individual temporary file:
> "%_TEMPFILEB%_%%~I.tmp" start /B "" cmd /C "%~f0" :DO_PING %%~I %_ECHOREQUS%
)
rem /* Wait until all temporary files are write-accessible, meaning that
rem all the parallel processes have been completed/terminated: */
:POLL
for %%I in (%_USAWORLDS%) do (
rem // Try appending nothing to check for write-access:
2> nul (>> "%_TEMPFILEB%_%%~I.tmp" rem/) || (
rem // Wait a bit to not overload the processor:
> nul timeout /T 1 /NOBREAK
goto :POLL
)
)
rem // Combine all individual temporary files into one:
> nul copy /Y "%_TEMPFILEB%_*.tmp" "%_TEMPFILEB%.tmp" /B
rem // Sort data as desired (alphabetic sorting):
sort /R "%_TEMPFILEB%.tmp" /O "%_TEMPFILEB%.tmp"
rem // Create return file, write header:
> "%_FINALFILE%" echo ms host
rem // Append sorted data to return file:
> nul copy /Y "%_FINALFILE%" + "%_TEMPFILEB%.tmp" "%_FINALFILE%" /B
rem // Clean up temporary files:
del "%_TEMPFILEB%_*.tmp" "%_TEMPFILEB%.tmp"
endlocal
exit /B
:DO_PING
rem // Build host URL to ping, set number of echo requests to send:
set "URL=oldschool%~1.runescape.com"
set /A "NUM=%~2"
rem /* Perform ping and capture last line of response, which should contain
rem the average reply time: */
set "STR="
for /F "delims=" %%P in ('2^> nul ping "%URL%" -n %NUM%') do set "STR=%%P"
rem // Check whether last line of response contains average reply time:
if not defined STR exit /B
set "AVG=%STR:*Average =%"
set "AVG=%AVG:~1%"
if "%AVG%"=="%STR%" exit /B
rem /* Convert average reply time to pure left-zero-padded number; the padding
rem is intended to simplify the later (purely alphabetic) sorting: */
set /A "AVG=AVG"
set "AVG=000000%AVG%"
rem // Return average reply time together with respective host URL:
echo %AVG:~-6% "%URL%"
exit /B

How to require user to activate my program?

I was wondering...
I have a program, and I want to charge money for it.
it runs on Windows, and is written mostly in VB and Batch-files...
how can I force the user to buy a product key for it, and activate it to use the Paid Version?
Thanks in advance!
~ #Cascading-style
This just a little example showing you that you can limit the number of execution of your program , so if the maximum of number of execution is reached the program will Auto delete by him self
#echo off
Setlocal enabledelayedexpansion
Title Count the number of times my BATCH file is run
Mode Con Cols=70 lines=7 & color 0E
Set /a MaxExecution=3
set /a count=1
set "FileCount=%tmp%\%~n0.dll"
If Not exist "%FileCount%" (
echo !count! > "%FileCount%"
) else (
For /F "delims= " %%a in ('Type "%FileCount%"') Do (
set /a count=!count! + %%a
echo !count! > "%FileCount%"
)
)
echo.
echo This Program is running for "!count!" time(s)
Call :SelfDelete
pause>nul & Exit /b
::**************************************************************
:SelfDelete
echo.
If !count! GTR !MaxExecution! (
Color 0C
echo The maximum execution of this "%~nx0" is set to "!MaxExecution!"
echo and it is reached & Timeout /T 5 /Nobreak>nul & Del %~f0
) else (
echo The counting is "!count!" and the max is set to "!MaxExecution!"
)
Goto :EOF
::**************************************************************

Manipulate cmd ping color based on time

My internet is not always working properly and I'd like to check the quality based on the cmd windows tool. I believe it's a task simple enough for it to handle.
I've begun by making a shortcut so I can have easy access to the command:
C:\Windows\System32\PING.EXE 8.8.8.8 -t
Now I was trying to transform the cmd ping command into a visually responsive one based on the output. I'd like to make the color change according to the time response.
After looking and not finding anything related, I believe it's either impossible or no one has ever tried.
Thank you very much :)
PD: (In case there was anything unclear just ask and I'll gladly answer)
Based on Magoo's post, I wrote this little batch program.
It asks for the target, the number of requests to make, the max time allowed and the time between requests and then prints in red if the request is over the time max, otherwise it sums the number of requests. It includes timestamp to be more accurate.
Copy and paste in a text file and name it with extension ".bat" (But don't name it "ping.bat" otherwise the program will enter in an infinite loop).
REM CMD PING TOOL
REM By Daweb
REM https://stackoverflow.com/users/3779294/daweb
#ECHO OFF
REM Needed for Line colored
SETLOCAL EnableDelayedExpansion
FOR /F "tokens=1,2 delims=#" %%a IN ('"PROMPT #$H#$E# & echo on & for %%b in (1) do rem"') do (
SET "DEL=%%a"
)
for /f %%a in ('copy /Z "%~f0" nul') do set "CR=%%a"
ECHO *****************
ECHO * CMD PING TOOL *
ECHO *****************
REM Start
:start
ECHO.
ECHO Set yours values
REM SET Target
SET /p hostInput=" - Target (ip or hostname): "
If "%hostInput%"=="" ECHO.&GOTO start
REM SET loops
SET /p loopsInput=" - Requests number: "
SET /a loops=loopsInput
REM SET time limit
SET /p maxmsInput=" - Maximum Time Limit (ms): "
SET /a maxms=maxmsInput
REM Value used for sleep between loops
SET /p sleepInput=" - Delay between requests (s): "
SET /a sleepDelay=sleepInput+1
REM Variables
SET displayText=""
SET /a countRequestsOk=0
SET /a countRequestsKo=0
SET /a totalRequests=0
SET /a maxTime=0
ECHO.
ECHO START at %TIME% [target: %hostInput%, requests: %loops%, time limit: %maxms% ms, delay: %sleepInput% s]
ECHO.
REM Loop
:loop
REM Set time
FOR /f "tokens=1-3 delims=/:" %%a IN ("%TIME%") DO (SET mytime=%%ah%%bm%%cs)
REM Get ping value
FOR /f "tokens=3delims==" %%a IN ('PING -n 1 %hostInput%') DO FOR /f "delims=m" %%b IN ("%%a") DO (
SET /a timems=%%b
SET /a totalRequests+=1
REM Check result
IF !timems! GTR %maxms% ( GOTO failed ) ELSE ( GOTO success )
)
REM Request success
:success
SET /a countRequestsOk+=1
IF !timems! GTR !maxTime! ( SET /a maxTime=timems )
<nul set /P "=!countRequestsOk! requests [Max !maxTime! ms]!CR!"
GOTO next
REM Request failed
:failed
IF !countRequestsOk! GTR 0 ECHO.
SET /a countRequestsOk=0
SET /a countRequestsKo+=1
SET displayText=" %mytime% - !timems!ms"
CALL :ColorText 0c !displayText!
GOTO next
REM Next loop
:next
REM Sleep a little bit
IF %sleepDelay% GTR 1 ( ping -n %sleepDelay% localhost > nul )
REM Check continue
SET /a loops-=1
IF %loops% gtr 0 GOTO loop
REM Display result
IF !countRequestsOk! GTR 0 ECHO.
ECHO.
ECHO STOP at %TIME%
ECHO.
if !countRequestsKo! GTR 0 (
SET displayText="FAILED - !countRequestsKo! requests over %maxms% ms on !totalRequests! requests in total"
CALL :ColorText 0c !displayText!
) ELSE (
SET displayText="SUCCESS - No request over %maxms% ms on !totalRequests! requests in total"
CALL :ColorText 02 !displayText!
)
REM Ask if restart
ECHO.&ECHO *********************
SET /p restartInput="Do it again ? (Y/N): "
If "%restartInput%"=="" ECHO *********************&GOTO start
If /I "%restartInput%"=="y" ECHO *********************&GOTO start
If /I "%restartInput%"=="n" ECHO *********************&GOTO end
REM End
:end
PAUSE
GOTO :EOF
REM Line color
:ColorText
ECHO off
ECHO %DEL% > "%~2"
FINDSTR /v /a:%1 /R "^$" "%~2" NUL
DEL "%~2" > NUL 2>&1
#ECHO OFF
SETLOCAL
SET loops=10
:loop
FOR /f "tokens=3delims==" %%a IN ('PING 8.8.8.8 -n 1') DO FOR /f "delims=m" %%b IN ("%%a") DO ECHO %%b&COLOR %%b&GOTO cchgd
:cchgd
PAUSE
SET /a loops-=1
IF %loops% gtr 0 GOTO loop
COLOR
GOTO :EOF
A simple demonstration - repeats the ping 10 times, changing colours depending on the response. Manipulate to do as you wish...
I am not sure that I know what the desired output should be, but this will output GREEN text for response times 0-39 ms, YELLOW for 40-79 ms, and RED for 80+ ms.
Run this from a cmd.exe prompt using the following command or put it into a .bat file script. Change the directory to the location where the Get-PingColor.ps1 file is landed.
powershell -NoLogo -NoProfile -File "%USERPROFILE%\bin\Get-PingColor.ps1"
=== Get-PingColor.ps1
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string[]]$ComputerNames
,[Parameter(Mandatory=$false)]
[int]$Count = 4
,[Parameter(Mandatory=$false)]
[int]$SpeedMinimumSlow = 80
,[Parameter(Mandatory=$false)]
[int]$SpeedMinimumMedium = 40
)
foreach ($ComputerName in $ComputerNames) {
$Pings = Test-Connection -ComputerName $ComputerName -Count $Count
$Average = ($Pings | Measure-Object -Property responsetime -Average).Average
$ForegroundColor = 'Green'
if ($Average -ge $SpeedMinimumSlow) { $ForegroundColor = 'Red'}
else { if ($Average -ge $SpeedMinimumMedium) { $ForegroundColor = 'Yellow' }}
Write-Host -ForegroundColor $ForegroundColor -BackgroundColor 'Black' "$ComputerName $Average ms"
}
=== Execution examples
I am loathe to put images into a post, but I do not see a way to produce color on SO.

Return variable through start command

I am looking for a way to get a value return from a start-command launched batch script. Let me explain:
I need to take advantage of multiprocessing by launching multiple sub-batch scripts simultaneously from a main batch script, then retrieve every sub batch file return value when they're done.
I've been using return variables with the call-command as very well explained by dbenham.
That solution does not allow multithreading, since sub-batch scripts are run one after the other.
Using the start-command allows multiple running batch scripts, but values are not returned to my main script because apparently the start-command creates a whole new variable context.
Does anybody have a solution/workaround to return values from the sub-scripts to the main script ?
Below is a model of what I need:
mainScript.bat
#echo off
setlocal
set "retval1=0"
set "retval2=0"
REM run two scripts in parallel:
start "" subscript1.bat arg1 retval1
start "" subscript2.bat arg1 retval2
REM wait for returned value
:waiting
call :sleep 1
set /a "DONE=1"
if %retval1% equ 0 set "DONE=0"
if %retval2% equ 0 set "DONE=0"
if %DONE% equ 0 goto :waiting
echo returned values are %retval1% %retval2%
exit /b
subscript1.bat
#echo off
setlocal
set "arg1=%~1"
set "retval1=%~1"
REM do some stuff...
REM return value
(endlocal
set "%retval1%=%foo%"
)
exit /b
Can't see any alternative to writing your return values to files, so
main
#ECHO OFF
SETLOCAL
for %%a in (1 2) do (
del "%temp%\retval%%a" 2>nul
)
start /min "" q225220791.bat arg1 retval1
choice /t 1 /d y >nul
start /min "" q225220791.bat arg1 retval2
:waiting
choice /t 1 /d y >nul
ECHO wait...%retval1%...%retval2%
if not exist "%temp%\retval1" goto waiting
if not exist "%temp%\retval2" goto waiting
for %%a in (1 2) do (
for /f "usebackqdelims=" %%i in ("%temp%\retval%%a") do set "retval%%a=%%i"
)
for %%a in (1 2) do (
del "%temp%\retval%%a" 2>nul
)
echo returned values are %retval1% %retval2%
GOTO :EOF
q225220791.bat
#ECHO OFF
SETLOCAL
:: wait a random time 2..10 sec.
SET /a timeout=%RANDOM% %% 8 + 2
choice /t %timeout% /d y >nul
:: return a random result 12..20
SET /a foo=%RANDOM% %% 8 + 12
>"%temp%\%2" echo %foo%
ENDLOCAL
exit
Relying on the value of the second parameter to the sub-process to set the tempfile name. I've changed the names of the batches to suit my system.
Not sure if this is even practical, but just testing to avoid the temporary file. So looking for a place in child process that can be readed from parent process i decided to use the window title.
task.cmd
#echo off
setlocal
rem Retrieve task information and set window title
set "taskID=%~1"
title [task];%taskID%;working;
rem Retrieve the rest of parameters. For this sample, a random value
set /a "timeToWait=%~2 %% 30"
rem Simulate command processing
timeout /t %timeToWait%
rem Calculate a return value for this task
for /f "tokens=1-10 delims=,.:/ " %%a in ("%date%%time%_%~2") do set "returnValue=%%a%%b%%c%%d%%e%%f%%g%%h%%i%%j"
rem Signal the end of the task
title [task];%taskID%;ended; my return value is %returnValue% ;
rem Wait for master to end this tasks
cls
echo Waiting for master....
waitfor %taskID%
rem Cleanup
endlocal
master.cmd
#echo off
setlocal enableextensions enabledelayedexpansion
rem Configure tasks
set "taskPrefix=myTSK"
set "numTasks=5"
rem Start tasks
for /l %%a in (1 1 %numTasks%) do (
set "return[%taskPrefix%%%a]=unknown"
start "[task];%taskPrefix%%%a;working;" cmd /c "task.cmd %taskPrefix%%%a !random!"
)
rem Wait for tasks to start
timeout /t 2 > nul
rem Wait for tasks to end. Get the list of cmd.exe windows with window title
rem to see the state of the task
rem Tasks in working state indicate master needs to keep working
rem Tasks in ended state have the return value in the window title and are
rem waiting for the master to retrieve the value and end them
:wait
set "keepWaiting="
for /f "tokens=9 delims=," %%a in ('tasklist /fi "imagename eq cmd.exe" /fo csv /v ^| findstr /l /c:"[task];%taskPrefix%"'
) do for /f "tokens=2-4 delims=;" %%b in ("%%a") do (
if "%%c"=="working" (
set "keepWaiting=1"
) else if "%%c"=="ended" (
set "return[%%b]=%%d"
start "" /min waitfor.exe /si %%b
)
)
rem If any task has been found in working state, keep waiting
if defined keepWaiting (
echo %time% : waiting ...
timeout /t 5 > nul
goto wait
)
rem All tasks have ended. Show return values
for /l %%a in (1 1 %numTasks%) do (
echo task #%%a ended with exit value :[!return[%taskPrefix%%%a]!]
)
endlocal
In this sample, the task waits for master using waitfor command. In XP this is not available, and can be replaced with just a pause, and from master.cmd the waiting loop must be modified to include the processID token in the tasklist processing, so the waiting task can be closed with taskkill.

optimization of a windows batch file arcade game

I made a batch file game, and it works, but it is choppy and ugly. I already know about threading, but I don't want to implement that in my first version. I am hoping to have the optimization down before I start doing more advanced things with this game. my question is this: What optimizations can I make to this game, so that it will 1. not be choppy and 2. not be quite so annoying in the display. any ideas or comments about how to make it faster clearer or take less memory are welcome, however, please do not post answers like: "don't use batch" "rewrite it in (insert language here)" "do this part with vb-script" etc... as they are not helpful, nor do they answer the question. any and all non batch hating criticism is welcomed.
here is the code:
#setlocal enableextensions enabledelayedexpansion
#echo off
color 0a
mode con lines=35 cols=50
cls
set instructions=use a and d to move left and right, w to fire. use q to quit and p to pause.
set height=30
set length=
set screen=50
set swidth=20
set amo=8
set lives=3
set 1=0
set 2=1
set 3=2
set 4=3
set 5=4
set 6=0
set 7=1
set 8=2
set 9=3
set 10=4
echo. What quality would you like?
echo. 1. fast, but the graphics suck!
echo. 2. medium both ways.
echo. 3. slow, but the graphics are better!
choice /n /c:123
set firequal=%errorlevel%00
cls
echo %instructions%
echo.
pause
cls
::main
:controls
cls
if %height% EQU 2 goto gameover
if %lives% LSS 1 goto gameover
cls
set /a shouldbomb+=1
set /a whenbomb=shouldbomb%%15
if %whenbomb% == 9 call :bomb
if '%ret%'=='1' exit /b
set ret=
cls
set alive=
for /l %%i in (1,1,10) do if defined %%i set alive=true
if not defined alive goto win
cls
for /l %%i in (1,1,5) do (
if defined %%i (
set /p a=[_] <nul
) else (
set /p a=... <nul
)
)
echo.
for /l %%i in (6,1,10) do (
if defined %%i (
set /p a=[_] <nul
) else (
set /p a=... <nul
)
)
for /l %%a in (1,1,%height%) do echo.
echo %length%[]
echo.
for /l %%i in (1,1,%amo%) do set /p a=^|<nul
echo.
choice /c adwqp0 /n /t 1 /d 0
if %errorlevel% equ 1 goto :left
if %errorlevel% equ 2 goto :right
if %errorlevel% equ 3 goto :fire
if %errorlevel% equ 4 (cls&exit /b)
if %errorlevel% equ 5 pause&goto controls
if %errorlevel% equ 6 goto :inactive
goto controls
::move player left
:left
if '!length!' NEQ '' set length=!length:~0,-1!
goto controls
::move player right
:right
call :strlen shiplen length
if %shiplen% GTR %swidth% goto controls
set length=%length%
goto controls
::fire a shot upwards
:fire
if '!amo!' LSS '1' goto controls
cls
set /a amo-=1
for /l %%i in (%height%,-1,2) do (
cls
for /l %%i in (1,1,5) do (
if defined %%i (
set /p a=[_] <nul
) else (
set /p a=... <nul
)
)
echo.
for /l %%i in (6,1,10) do (
if defined %%i (
set /p a=[_] <nul
) else (
set /p a=... <nul
)
)
for /l %%j in (1,1,%%i) do echo.
echo %length% ^
set /a ship=height-%%i-1
for /l %%b in (1,1,!ship!) do echo.
echo %length%[]
echo.
for /l %%i in (1,1,%amo%) do set /p a=^|<nul
echo.
for /l %%a in (1,1,%firequal%) do call >nul 2>&1
)
call :checkshot
set /a shouldbomb+=1
set /a whenbomb=shouldbomb%%2
if %whenbomb% == 0 call :bomb
goto controls
:inactive
if %amo% LSS 10 set /a amo+=1
if !height! NEQ 2 set /a height-=1
call :bomb
goto controls
:bomb
:btop
set bombx=
for /l %%a in (1,1,10) do (
if defined %%a (
set /a randomnum=%random%%%5
if '!%%a!'=='%randomnum%' (
set /a "bombx=5*(!%%a!)"
)
)
)
)
if not defined bombx goto btop
cls
set bomb=
for /l %%b in (1,1,!bombx!) do (
set bomb=!bomb!
)
set /a bombh=height-1
for /l %%c in (1,1,!bombh!) do (
cls
for /l %%i in (1,1,5) do (
if defined %%i (
set /p a=[_] <nul
) else (
set /p a=... <nul
)
)
echo.
for /l %%i in (6,1,10) do (
if defined %%i (
set /p a=[_] <nul
) else (
set /p a=... <nul
)
)
for /l %%b in (1,1,%%c) do echo.
echo !bomb!x
set /a ship=height-%%c-1
for /l %%b in (1,1,!ship!) do echo.
echo %length%[]
echo.
for /l %%i in (1,1,%amo%) do set /p a=^|<nul
echo.
for /l %%a in (1,1,%firequal%) do call >nul 2>&1
)
if "%bomb%" == "%length%" call :looselife
if "%bomb% " == "%length%" call :looselife
if "%bomb%" == "%length% " call :looselife
if "%bomb% " == "%length%" call :looselife
if "%bomb%" == "%length% " call :looselife
exit /b
:strlen <resultVar> <stringVar>
(
setlocal EnableDelayedExpansion
set "s=!%~2!#"
set "len=0"
for %%P in (1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~1=%len%"
exit /b
)
:checkshot
call :strlen slen length
for /l %%i in (0,5,20) do (
if '!slen!' == '%%i' (
set /a hit=%%i
set /a hit=hit/5+1
set /a hit2=hit+5
if not defined !hit2! set !hit!=
if defined !hit2! set !hit2!=
)
)
exit /b
:looselife
set /a lives-=1
set length=
set 1=0
set 2=1
set 3=2
set 4=3
set 5=4
if %lives% GTR 1 timeout /nobreak 1 >nul 2>&1
exit /b
:win
cls
echo YOU WIN^!^!^!^!
echo.
echo GOOD JOB^!^!^!
echo.
pause
cls
exit /b
:gameover
cls
echo YOU LOOSE.
echo.
echo PLEASE TRY AGAIN.
echo.
pause
cls
set ret=1
thank you in advance for any help.
P.S. I am writing this game to convince a friend to learn something besides html, and while batch isn't the best, he uses windows, and he will only do something simple for now. He is twelve, so I think batch is best option.
You can definitely improve things considerably. I know, because I have already produced a very smooth and playable version of SNAKE using pure Windows batch! Give it a try - I think you will be surprised and impressed with what musty old batch can do :-)
Of course the link has the code, but it also has pointers on some of the techniques I used to make the came perform so well. Read the entire first post carefully, and read the remainder of the thread for some additional important developments.
Optimization is a large topic. Rather than repeat all the information here, I will simply summarize. Follow the link for more details.
1) Minimize GOTO and CALL statements.
For major speed improvements over traditional batch function calls, we developed batch macros with arguments at DosTips. That first macro link develops a number of important concepts. However, the macro form I actually used in the game uses a more elegant solution with arguments appended.
A GOTO loop can be replaced by an infinite FOR loop that runs in a new process. You break out of the loop by EXITing the child process.
2) Greatly improve key press detection in a non-blocking way.
A major limitation of batch is the inability to easily detect a keypress without blocking progress of the game. The problem can be solved by using two processes, both running in the same console window. The controller process reads keypresses and sends them to the main game process via a text file. The controller has multiple modes of operation. The game process sends commands to the controller via another text file. This technique requires careful coordination of input and output redirection.
The CHOICE command is not available on XP. Some folks at DosTips discovered how to use XCOPY to simulate most of the features of CHOICE, and it works on all versions of Windows. Very cool!
3) Screen painting
Building the screen character by character is extremely slow. It is much faster to build the initial screen once, using an "array" of strings with fixed length. Each character within a string represents one "pixel". The position within a string represents the X coordinate, and the string row number represents the Y coordinate. Generally, only a few pixels change for any given screen refresh. Pixels can be "plotted" by using SET with simple substring operations. The entire screen can then be quickly refreshed using CLS followed by ECHO of each line in the screen array.
4) Smooth animation
The amount of work required to perform game logic and screen plotting can vary significantly depending on the current game context. But you want the animation to be smooth. Rather than have a fixed delay between each round of movement, you can instead measure the time since the screen was last updated. Only continue when a pre-determined amount of time has elapsed. As long as all game logic and plotting can occur within the delay time period, then the animation will always be smooth.
Here is pseudo code that describes the timing logic:
initialize delayTime
initialize previousTime
loop (
get currentTime
set diffTime = currentTime - previousTime
if diffTime >= delayTime (
set previousTime = currentTime
perform user input, game logic, and screen refresh
)
)
And here is actual code that computes the elapsed time since last movement. The currentTime (t2) is measured as centiseconds (1/100 second) since midnight. It is parsed and computed using FOR /F and basic math. The diffTime (tDiff) will be negative if the previousTime (t1) is before midnight and the currentTime (t2) is after midnight. If negative, then 1 day is added to diffTime to get the correct time interval.
%=== compute time since last move ===%
for /f "tokens=1-4 delims=:.," %%a in ("!time: =0!") do set /a "t2=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100, tDiff=t2-t1"
if !tDiff! lss 0 set /a tDiff+=24*60*60*100
There is so much more that can be discussed. Try the SNAKE.BAT game, study the post and the code, and see where your imagination can take you.
Some optimizations
1) It's better to use good variable names.
Names like 1, 2 ... 10 are really bad, nobody knows what they are good for, even you self will not remember in a month.
And then it's also a bad idea as it can have many side effects to use variables begining with digits, in batch there are many where these will simply fail.
2) You should combine your output to complete lines before outputting it.
Then you don't need set/p only echo and it's faster.
3) calls to functions like :strlen should be avoided, calls at all are expensive and in your case it should be possible to solve the same without strlen at all.
4) The function :checkshot don't need a for loop.
I don't understand what you try to do there, but you test slen if it is a muliple of 5.
This could be solved with
set /a remainder=slen %% 5
if !remainder! EQU 0 (
...
5) Follow the tips of dbenham :-)

Resources