Jun 29, 2015 06:39 AM
(This post was last modified: Jun 29, 2015 06:44 AM by annikk.exe.)
Here's a script I made today.
Code @echo off
setlocal EnableDelayedExpansion
REM Keyboard cursor prototype
call :createBuffer 40 30
call :createPlayer 0 0
set frame=0
:loop
cls
echo %buffer%
set /a frame+=1
title X: %playerX% Y: %playerY% FRAME: %frame%
call :getKey
call :moveChar %playerX% %playerY% %key%
goto :loop
pause
exit /b
:createPlayer
call :insertChar %1 %2 "X"
set playerX=%1
set playerY=%2
goto :eof
:moveChar
REM Moves a character. Replaces the character at the previous position with a space.
set oldX=%1
set oldY=%2
set k=%~3
set newX=%oldX%
set newY=%oldY%
REM Which direction should the character move in?
if /i %k% == W set /a newY-=1
if /i %k% == A set /a newX-=1
if /i %k% == S set /a newY+=1
if /i %k% == D set /a newX+=1
REM Is the new position out of bounds? If so, take no action
if %newX% GEQ %bWidth% goto :eof
if %newY% GEQ %bHeight% goto :eof
if %newX% LSS 0 goto :eof
if %newY% LSS 0 goto :eof
REM Calculate new and old buffer indices
set /a oldIndex=(%oldY% * %bWidth%) + %oldX%
set /a newIndex=(%newY% * %bWidth%) + %newX%
REM Capture the character to be moved
set char=!buffer:~%oldIndex%,1!
REM Adjust positions to correct for 0 indexing
set /a oldIndexAdj=%oldIndex% + 1
set /a newIndexAdj=%newIndex% + 1
REM Replace the existing character with a space
set "buffer=!buffer:~0,%oldIndex%! !buffer:~%oldIndexAdj%,%bufferSize%!"
REM Insert the character in its new position
set "buffer=!buffer:~0,%newIndex%!%char%!buffer:~%newIndexAdj%,%bufferSize%!"
REM Update the Player with the new position
set playerX=%newX%
set playerY=%newY%
goto :eof
:insertChar
REM Inserts a character into the buffer, replacing whatever char was there before
set posX=%1
set posY=%2
set char=%3
set /a charPos=(%posY% * %bWidth%) + %posX%
set "buffer=!buffer:~0,%charPos%!%~3!buffer:~%charPos%,%bufferSize%!"
goto :eof
:getKey
REM Gets the key presses from the user
for /F "delims=" %%a in ('xcopy /l /w "%~f0" "%~f0" 2^>nul') do (
set "key=%%a"
goto :gotKey
)
:gotKey
set "key=%key:~-1%"
for %%a in (W A S D) do (if /i "%key%"=="%%a" goto :eof)
goto :getKey
:createBuffer
REM Set up the buffer and screen mode, init some variables
set bWidth=%1
set bHeight=%2
set /a bufferSize=%bWidth% * %bHeight%
for /L %%a in (1,1,%bufferSize%) do (set "buffer=!buffer! ")
set /a bHeight+=2
mode %bWidth%,%bHeight%
set /a bHeight-=2
goto :eof
The user presses the WASD keys to control a little "X". They can move the X around within the bounds of the screen.
The mode (screen size) can be changed and the script will adapt to this dynamically.
This shows some basics of buffers and so on, but that's not what I want to talk about today.
The problem with the approach outlined in this script is that it's impossible to have something continuing in the background while we wait for a key press.
For example, we might animate a "Z" character bouncing between the left and right walls. However, the animation will only advance a frame when the user presses a key. If the user stops pressing keys, the Z will not move.
How can we capture key presses in this way, but allow the script to continue running if no key was pressed? Developing a fast and elegant system that allows us to do this could potentially open the floodgates for a heap of cool batch games.
waiting for the miracle... for the miracle to come
Okay, here is my solution:
Code @echo off
echo.>press
if not "%~1"=="" goto background
start "" /b /d %cd% cmd /c ""%~0" "background""
setlocal enabledelayedexpansion
for /f "tokens=1-4 delims=:.," %%a in ("!time: =0!") do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
set mover=1.1
set tmpA=1
set tmpB=1
for /l %%. in () do (
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"
set /a tDiff=t2-t1
if !tDiff! lss 0 set /a tDiff+=24*60*60*100
if !tDiff! geq 10 (
set t1=%t2%
set "buffer="
for /l %%A in (1,1,25) do (
for /l %%B in (1,1,25) do (
set buffer=!buffer!!%%A.%%B!
))
mode con cols=25 lines=25
for /l %%A in (1,1,25) do (
for /l %%B in (1,1,25) do (
set "%%A.%%B= "
))
for /f %%A in (press) do (
set "tomove=%%A"
title !tomove!
)
for /f "tokens=1-2 delims=." %%A in ("!mover!") do (
if %%A lss 25 (
set /a tmpA=%%A+1
) else (
set tmpA=1
if %%B lss 25 (
set /a tmpB=%%B+1
) else (
set tmpB=1
)
)
)
set "!mover!= "
set mover=!tmpA!.!tmpB!
set "!mover!=X"
cls
echo !buffer!
)
)
:background
:getKey
for /f "delims=" %%K in ('xcopy /l /w "%~f0" "%~f0" 2^>nul') do (
set "key=%%K"
goto :next
)
:next
set "key=%key:~-1%"
if /i %key%==a echo a>press
if /i %key%==d echo d>press
goto getKey
Unfortunately, I could not get it working like I hoped, but you can see that when you are running this, the keys "A" and "D" will change the title, while not blocking the animation.
Jun 30, 2015 12:23 AM
(This post was last modified: Jun 30, 2015 02:46 AM by annikk.exe.)
That's an interesting proof of concept, Qwerty.
These lines seem critical:
Code if not "%~1"=="" goto background
start "" /b /d %cd% cmd /c ""%~0" "background""
So on first run, we check for a parameter, and there isn't one.. The conditional basically says "go to background if parameter 1 is blank". On the first run, parameter 1 will definitely be blank since the user probably double-clicked the file from the Windows GUI.
So then it proceeds to the second line, where it starts itself and passes the word "background" as the first parameter.
This means when the newly launched process checks the conditional, this time there IS a parameter, which means we jump straight to the Background section and start outputting key presses to the "press" file.
Meanwhile, the initial process continues on after starting the background process, animates the display, and checks for a key press on each loop.
So, my question is how are we able to have both processes running within the same Window? I had no idea that was possible. It's groundbreaking..
I need to see some more examples of it working. I will try to code up some tests for it today.
Update:
Here's a simplified version:
Code @echo off
echo.X>catPress
if not "%~1"=="" goto :getKey
start "" /b /d %cd% cmd /c ""%~0" "CATS""
set numCats=0
:loop
for /f %%a in ('type catPress') do (title %%a)
set /a numCats+=1
echo %numCats%
timeout /t 1 /nobreak >nul
goto :loop
:getKey
for /f "delims=" %%K in ('xcopy /l /w "%~f0" "%~f0" 2^>nul') do (
set "key=%%K"
goto :next
)
:next
set "key=%key:~-1%"
if /i %key%==a echo a>catPress
if /i %key%==d echo d>catPress
goto getKey
This accepts user inputs whilst running an "animation" in real time.
This is definitely possible, then.
I now need to:
1. Implement the new technique into the existing script
2. Create a reliable sub-second delay function
3. I'll have a shot at making that bouncing "Z"...
Jun 30, 2015 09:32 AM
(This post was last modified: Jun 30, 2015 10:05 AM by WOLGA.)
Let me talk about your post.
Quote:The conditional basically says "go to background if parameter 1 is blank".
Exactly the opposite, I'm sure you meant that, but you should fix it in the post.
Quote:So, my question is how are we able to have both processes running within the same Window? I had no idea that was possible. It's groundbreaking..
Not sure myself about the "how", but it's a trick White taught me, so kudos to him.
Okay, so I got my code working like I was hoping it would.
Code @echo off
echo.>press
if not "%~1"=="" goto background
start "" /b /d %cd% cmd /c ""%~0" "background""
setlocal enabledelayedexpansion
mode con cols=25 lines=27
for /f "tokens=1-4 delims=:.," %%a in ("!time: =0!") do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
set mover=1.1
set tmpA=1
set tmpB=1
set char=5.5
set tmpAC=5
set tmpBC=5
for /l %%. in () do (
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"
set /a tDiff=t2-t1
if !tDiff! lss 0 set /a tDiff+=24*60*60*100
if !tDiff! geq 20 (
set t1=%t2%
set "buffer="
for /l %%A in (1,1,25) do (
for /l %%B in (1,1,25) do (
set buffer=!buffer!!%%A.%%B!
))
for /l %%A in (1,1,25) do (
for /l %%B in (1,1,25) do (
set "%%A.%%B= "
))
for /f "tokens=1-2 delims=." %%A in ("!char!") do (
set tmpAC=%%A
set tmpBC=%%B
)
for /f %%A in (press) do (
set "tomove=%%A"
if !tomove!==w (
if !tmpAC! gtr 1 (
set /a tmpAC=!tmpAC!-1
) else (
set tmpAC=1
)
echo.>press
set "tomove="
)
if !tomove!==a (
if !tmpBC! gtr 1 (
set /a tmpBC=!tmpBC!-1
) else (
set tmpBC=1
)
echo.>press
set "tomove="
)
if !tomove!==s (
if !tmpAC! lss 25 (
set /a tmpAC=tmpAC+1
) else (
set tmpAC=25
)
echo.>press
set "tomove="
)
if !tomove!==d (
if !tmpBC! lss 25 (
set /a tmpBC=!tmpBC!+1
) else (
set tmpBC=25
)
echo.>press
set "tomove="
)
)
for /f "tokens=1-2 delims=." %%A in ("!mover!") do (
if %%A lss 25 (
set /a tmpA=%%A+1
) else (
set tmpA=1
if %%B lss 25 (
set /a tmpB=%%B+1
) else (
set tmpB=1
)
)
)
set "!char!= "
set "char=!tmpAC!.!tmpBC!"
title !char!
set !char!=@
set "!mover!= "
set mover=!tmpA!.!tmpB!
set "!mover!=X"
cls
echo !buffer!
)
)
:background
:getKey
for /f "delims=" %%K in ('xcopy /l /w "%~f0" "%~f0" 2^>nul') do (
set "key=%%K"
goto :next
)
:next
set "key=%key:~-1%"
if /i %key%==w echo w>press
if /i %key%==a echo a>press
if /i %key%==s echo s>press
if /i %key%==d echo d>press
goto getKey
Now you can use WASD to control the character @.
I also got rid of a TON of flicker.
(Jun 30, 2015 09:32 AM)WOLGA Wrote: Code @echo off
echo.>press
if not "%~1"=="" goto background
start "" /b /d %cd% cmd /c ""%~0" "background""
setlocal enabledelayedexpansion
mode con cols=25 lines=27
for /f "tokens=1-4 delims=:.," %%a in ("!time: =0!") do set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100"
set mover=1.1
set tmpA=1
set tmpB=1
set char=5.5
set tmpAC=5
set tmpBC=5
for /l %%. in () do (
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"
set /a tDiff=t2-t1
if !tDiff! lss 0 set /a tDiff+=24*60*60*100
if !tDiff! geq 20 (
set t1=%t2%
set "buffer="
for /l %%A in (1,1,25) do (
for /l %%B in (1,1,25) do (
set buffer=!buffer!!%%A.%%B!
))
for /l %%A in (1,1,25) do (
for /l %%B in (1,1,25) do (
set "%%A.%%B= "
))
for /f "tokens=1-2 delims=." %%A in ("!char!") do (
set tmpAC=%%A
set tmpBC=%%B
)
for /f %%A in (press) do (
set "tomove=%%A"
if !tomove!==w (
if !tmpAC! gtr 1 (
set /a tmpAC=!tmpAC!-1
) else (
set tmpAC=1
)
echo.>press
set "tomove="
)
if !tomove!==a (
if !tmpBC! gtr 1 (
set /a tmpBC=!tmpBC!-1
) else (
set tmpBC=1
)
echo.>press
set "tomove="
)
if !tomove!==s (
if !tmpAC! lss 25 (
set /a tmpAC=tmpAC+1
) else (
set tmpAC=25
)
echo.>press
set "tomove="
)
if !tomove!==d (
if !tmpBC! lss 25 (
set /a tmpBC=!tmpBC!+1
) else (
set tmpBC=25
)
echo.>press
set "tomove="
)
)
for /f "tokens=1-2 delims=." %%A in ("!mover!") do (
if %%A lss 25 (
set /a tmpA=%%A+1
) else (
set tmpA=1
if %%B lss 25 (
set /a tmpB=%%B+1
) else (
set tmpB=1
)
)
)
set "!char!= "
set "char=!tmpAC!.!tmpBC!"
title !char!
set !char!=@
set "!mover!= "
set mover=!tmpA!.!tmpB!
set "!mover!=X"
cls
echo !buffer!
)
)
:background
:getKey
for /f "delims=" %%K in ('xcopy /l /w "%~f0" "%~f0" 2^>nul') do (
set "key=%%K"
goto :next
)
:next
set "key=%key:~-1%"
if /i %key%==w echo w>press
if /i %key%==a echo a>press
if /i %key%==s echo s>press
if /i %key%==d echo d>press
goto getKey
Now you can use WASD to control the character @.
I also got rid of a TON of flicker.
Holy shit; that is amazing. It's threading, but there is barely any flicker. Threading alone is a bitch, but how well it performs is amazing.
Jul 1, 2015 01:55 AM
(This post was last modified: Jul 1, 2015 01:57 AM by eXaByte.)
I fancy the batch threading as well. Never thought of it as a possibility. Thank you for sharing this querty!
Wow.... okay, that works correctly!
But I am having difficult understanding your code. Moreover, I don't understand why yours works and mine does not. They seem to use identical methods..
Jul 1, 2015 02:37 AM
(This post was last modified: Jul 1, 2015 02:37 AM by eXaByte.)
(Jul 1, 2015 02:20 AM)annikk.exe Wrote: Wow.... okay, that works correctly!
But I am having difficult understanding your code. Moreover, I don't understand why yours works and mine does not. They seem to use identical methods..
Well i have to say its hard for me to read it as well, mainly because of the indentation 😝 (im an indent freak)
Jul 1, 2015 03:52 AM
(This post was last modified: Jul 1, 2015 03:57 AM by annikk.exe.)
I went through Qwerty's code in detail today, adding a few comments, indenting the code, and generally overhauling it so as to better understand how it's working.
One cool thing I noticed is this line:
Code for /l %%. in () do (STUFF)
This is a neat method for creating an infinite loop..
Would be interesting to compare how it performs versus a "goto" statement and some brackets.
Anyway, after the commenting/indenting was done, I decided it would be even clearer if I could put some of the longer parts into seperate functions, and call them, so as to reduce the size (visually, at least) of the main loop.
I was making sure to test everything as I went along, and it turns out this may be the cause of the "crash".
Take a look at these two versions:
Working
Not Working
The "working" one is just qwerty's code with indentation and some comments.
The "not working" one is exactly the same, except that some of the code has been moved into a function.
In my tests, the "not working" version crashes after a minute or two of heavy use (rapid, constant key presses).
I haven't been able to get the "working" version to crash at all.
Could it be that this "engine" needs to be built entirely without function calls?
Perhaps "goto :function" and "goto :endOfFunction" style of object oriented coding will actually be functionally superior here. purr, such pun, oh my fur
I will continue my tests, just wanted to update you guys with my progress. We're on the verge of something big here, I sense.. :>
@Icarus:
Thanks! The trick here was fixing a bug. I had mode con in the wrong place for whatever reason. Additionally, the buffer is a very important part of how it displays (hopefully) without or with as little as possible flicker.
@Exabyte:
Sorry about my formatting, I tend to put related commands together. (helps with debugging). I'll admit it can make it hard to follow and read. You can also thank White for teaching me how to thread.
@Annikke, I have no doubt that one day in the near future we will be able to make games with just a few function calls. I am almost completely certain that the for loop is far quicker, however, I cannot seem to break out of it... More pressingly, however, I cannot seem to reproduce your crash. Can you identify what caused it? Do you get some sort of error, or does it just start behaving weirdly?
Jul 1, 2015 06:31 PM
(This post was last modified: Jul 1, 2015 06:36 PM by annikk.exe.)
(Jul 1, 2015 09:52 AM)WOLGA Wrote: I have no doubt that one day in the near future we will be able to make games with just a few function calls. I am almost completely certain that the for loop is far quicker, however, I cannot seem to break out of it... More pressingly, however, I cannot seem to reproduce your crash. Can you identify what caused it? Do you get some sort of error, or does it just start behaving weirdly?
The same thing happens as happened with my own version - after a while it simply stops accepting the inputs/the "@" symbol no longer moves. Another symtom is that the characters that I type are momentarily displayed in the lower left hand corner. Interestingly, the automatically animated character continues on its merry way without incident. So in some sense at least, the script has not crashed; it is still running. Perhaps the "background" thread crashed and the error message is hidden from us or something..
By the way, I noticed that your timer should be running one frame per 20 centiseconds, which is 5 frames per second, but it runs much much faster than this on my machine - perhaps 10 or 15 frames per second. I'm wondering if your method of calculating the time difference between one frame and the next is going above the 32 bit integer limit. I'll check it more in the morning.
Other thoughts I had this evening include the possibility of "start"ing the delay portion of the script by first echoing it to a seperate .bat file, and then waiting until it is finished before proceeding. The reason I think this might have a positive effect is that during my debugging I noticed that sometimes the debugging output of threads collide. With echo on, REM statements on the CLS and Mode commands, and running from a command prompt instead of double clicking, it's occasionally possible in my original code to spot artefacts such as "setset".
"setset" isn't a valid command obviously, and yet it produces no errors. Both "threads" are reporting their output simultaneously. If they don't respect each others' precendence and simply run concurrently with other commands in the other thread, it would be possible to have a condition where both threads try to access the same variable at the same time, and the universe breaks. Currently, this is my best guess at the reason for it breaking; it certainly isn't obvious why it happens!
My own code breaks much more quickly than the "not working" version of your code, Qwerty.
I could suggest trying to reproduce the bug using my original code; at least then you can see the problem first hand. :> PURR my brothers! victory awaits!!
What is the case that you cannot break out of a for loop ?
Jul 2, 2015 05:57 AM
(This post was last modified: Jul 2, 2015 05:58 AM by annikk.exe.)
I think qwerty meant this style of loop:
Code for /l %%a in () do (stuff)
I normally exit loops with a "goto" statement leading to a label that occurs after the end of the loop.
(Jul 2, 2015 05:57 AM)annikk.exe Wrote: I think qwerty meant this style of loop:
Code for /l %%a in () do (stuff)
I normally exit loops with a "goto" statement leading to a label that occurs after the end of the loop.
Exactly, its pretty easy to exit 😶
@Annikke, @Exabyte
It might be just me, but I can't get
Code @echo off
setlocal enabledelayedexpansion
echo %time%
for /l %%a in () do (
set /a a+=1
if !a!==500 goto ool
echo !a!
)
:ool
pause
to work as expected. Maybe you can be of some assistance.
|