Stacker, A batch interpretation of the game.

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
T3RRY
Posts: 243
Joined: 06 May 2020 10:14

Stacker, A batch interpretation of the game.

#1 Post by T3RRY » 20 Apr 2023 21:34

Yet another batch game, this time it's the classic Stacker game
Spacebar to drop the current block onto the stack, customization menu at game start for colors, characters and difficulty.

Code: Select all

===================================
@REM Stacker by T3RRY. Save with utf-8 encoding.
@REM Requires windows 10 v10589 or newer. Some features will not work as intended in Windows Terminal.
===================================
%= Do not modify Thread Launcher =%
@Echo off
	(Title )
	REM execute target threads
	If not "%~1"=="" Goto:%1

	Set "ForceQuit="
	For /F "Delims=" %%G in ('Where Powershell.exe')Do If not Defined ForceQuit Set "ForceQuit=%%G -c "$wshell = New-Object -ComObject wscript.shell; $wshell.SendKeys('{TAB}')"

	If not defined ForceQuit (
		Call:Error 0x01
		Exit /b
	)

 	(1>"%~f0:Status" Echo(verify.NTFS) || (
	 	Call:Error 0x04
		Exit /b
	)

for /F %%a in ('Echo(prompt $E^| cmd')Do Set \E=%%a

Set "fontsize="
CD /D "%~dp0"
:LoadDefaults
(For /F "UsebackQ delims=" %%G in ("%~n0:defaults")Do Set "%%~G") 2> nul

If not defined fontsize (
	(
		Echo("fontSize=16"
		Echo("fontType=Lucida Console"
		Echo("s[1]col=1"
		Echo("FGcol=255"
		Echo("BGi[1]=0"
		Echo("BGi[2]=1"
		Echo("SPi=2"
		Echo("Difficulty=1"
	) >"%~n0:defaults"
	Goto:LoadDefaults
)

	CHCP 65001 > nul
	Set "CharSet= *✚✮-+ΦΞ❦•○Δ✸✹✼✾❀❂❈❣✜✟✠✥✪✫✰▄▬▀█░▒▓▲▼►◄┼═╬❤❥♥♦♣♠◙x"

:menu
	Call:Setfont %FontSize% "%fontType%"
	CLS
	Setlocal EnableDelayedExpansion
	Echo( [0]    Start the game
	Echo( [P]    Block      Color: %\E%[5m%\E%[38;5;%s[1]col%m%s[1]col%%\E%[0m
	Echo( [WS]   Block       Char: "!CharSet:~%SPi%,1!"
	Echo( [ED]   Background Char1: "!CharSet:~%BGi[1]%,1!"
	Echo( [RF]   Background Char2: "!CharSet:~%BGi[2]%,1!"
	Echo( [C]    Background Color: %\E%[5m%\E%[38;5;%FGcol%m%\E%[?12h%FGcol%%\E%[?12l%\E%[0m
	Echo( [Z]    Change  Fontsize: !FontSize!
	Echo( [1~9]  Difficulty: !Difficulty!%\E%[1E%\E%7
	Endlocal
	For /F "delims=" %%G in ('%SystemRoot%\System32\Choice.exe /n /C:WPCFR0ZEDS123456789')Do (
		If /i "%%G" == "W" Set /A "SPi=SPi %% 48 + 1"
		If /i "%%G" == "P" Call:GetInt s[1]col "Block color"
		If /i "%%G" == "C" Call:GetInt FGcol "Background color"
		If /i "%%G" == "F" If %BGi[2]% GTR 0 Set /A "BGi[2]-=1"
		If /i "%%G" == "R" Set /A "BGi[2]=BGi[2] %% 48 + 1"
		If /i "%%G" == "0" Goto:Setup
		If /i "%%G" == "Z" Set /a "fontsize=fontsize %%30 + 2"
		If /i "%%G" == "E" Set /A "BGi[1]=BGi[1] %% 48 + 1"
		If /i "%%G" == "D" If %BGi[1]% GTR 0 Set /A "BGi[1]-=1"
		If /i "%%G" == "S" If %SPi% GTR 0 Set /A "SPi-=1"
		2> nul Set /A "1/%%G" && Set "Difficulty=%%G"
	)
	If %fontsize% LSS 10 Set "fontsize=10"

Goto:menu

===================================
EXIT & Rem this line never executed

! Error Handling !
Error:1	Powershell not found in %Path%
Error:2	Xcopy not in C:\Windows\System32
Error:3	No write permission
Error:4	Non NTFS system
===================================

:GameInit %= Define any initialization variables / output pre-loop screen =%

	Setlocal EnableDelayedExpansion

REM some math assigned to key array kKEY to handle directional movement.
REM uses divide by zero error to abort movement if it would take the player OOBounds
REM uses -ve & +ve Modulo to adjust Current X/Y index within Bounds.

REM E,W ; A,D ; 4,6
	Set "kD="1/(P[1]x-MaxX)","P[1]x=P[1]x %%MaxX + 1""
	Set "kA="1/(P[1]x-1)","P[1]x=((P[1]x - MaxX) %%MaxX) + (MaxX-1)""
	Set "k6="1/(P[1]x-MaxX)","P[1]x=P[1]x %%MaxX + 1""
	Set "k4="1/(P[1]x-1)","P[1]x=((P[1]x - MaxX) %%MaxX) + (MaxX-1)""

REM Initial Delay Interval.
REM If you find this game runs too fast at Difficulty 1, increase the value of ControlSpeed.
REM If you find this game runs too slow at Difficulty 1, decrease the value of ControlSpeed.
	Set "ControlSpeed=75000"
	Set /A "BaseDelay=ControlSpeed-(Difficulty*(ControlSpeed/10))"

REM maximum delay interval
	Set "MaxDelay=82500"
	Set /A "ModuloStep=ControlSpeed / 100"
REM	Set "ModuloStep=1250"

REM 'Delay Time' control key definitions.
REM -ve Modulo constrained to 0 lower limit by 1/Basedelay - Divide by zero error halts execution of line.
	Set "k-="1/BaseDelay","BaseDelay=((BaseDelay - MaxDelay) %%MaxDelay) + (MaxDelay-ModuloStep)""

REM +ve Modulo constrained to 80000 [ModuloStep offset is to accomadate -ve modulo.]
	Set "k+="1/(MaxDelay-ModuloStep-BaseDelay)","BaseDelay=BaseDelay %%(MaxDelay-ModuloStep) + ModuloStep""

REM Field Dims
	Set /A "MaxX=30","MaxY=24","oMaxX=MaxX"
	mode 31,25

REM initial starting pos
	Set /A "P[1]x=MaxX/2","P[1]y=MaxY"
	Set "Tail=!CharSet:~%SPi%,1!!CharSet:~%SPi%,1!!CharSet:~%SPi%,1!!CharSet:~%SPi%,1!!CharSet:~%SPi%,1!!CharSet:~%SPi%,1!!CharSet:~%SPi%,1!"
	Set "p[1]Sprite=%\E%[^!P[1]y^!;^!P[1]x^!H!\E![5m!\E![48;5;!S[1]col!m!\E![38;5;1m^!Tail^!"	

	 Set "P[1]len=7"
REM	:::  Set "null=%tail:@=" &Set /A "P[1]len+=1" & Set "null=%"

	Set "screen=%\E%[38;5;%FGcol%m"
	Set "num=0"
	for /l %%y in (1,1,%MaxY%) do (
		for /l %%x in (1,1,%MaxX%) do (
			set /a "num=num %% 2 + 1"
			if !num! equ 2 (
				Set "screen=!Screen!!CharSet:~%BGi[2]%,1!"
			) Else Set "screen=!Screen!!CharSet:~%BGi[1]%,1!"
		)
        	Set "screen=!screen!!LF!"
    	)

REM Supress Cursor
   	<nul Set /P "=%\E%[?25l"
	Title Place:Space Quit:TAB

REM Gameloop
Set /A "MaxX=oMaxX-P[1]len+1"
Set "Tower="
Set "Key=!k6!"
Set "LastKey=!Key!"
	2> nul 1> Con (
		For /L %%. in ()Do (
			%= Read last key press from input buffer without waiting. aka Non blocking input =%
			If not "!Lastkey!"=="Pause" If not "!Lastkey!"=="+" If not "!Lastkey!"=="-" Set "LastKey=!Key!"
			Set "NewKey="
			Set /P "NewKey="
			If "!NewKey!"=="4" Set "Newkey="
			If "!NewKey!"=="6" Set "Newkey="
			If /I "!NewKey!"=="A" Set "Newkey="
			If /I "!NewKey!"=="D" Set "Newkey="
			If !p[1]y! LSS 1 (
				Set "NewKey=quit"
				For /L %%i in (-1000000 1 1000000)Do rem game won
				%ForceQuit%
				Call:cleanup
				EXIT
			)
			If not "!NewKey!"=="" (
				If /I "!NewKey:~-4!" == "quit" (
					Call:Cleanup
				)
				If not "!NewKey:{ENTER}!"=="!NewKey!" (
					Set "NewKey=!NewKey:~-1!"
				)Else Set "NewKey={ENTER}"
				For /f "delims=" %%v in ("!NewKey!")Do (
					If not "!k%%v!"=="" (
						%= assign key array item to key =%
						Set "key=!k%%v!"
					)
				)
			)
			%= Implement Control actions. =%
			If not "!Key!"=="" (
				If "!NewKey!"=="+" (
				REM	Set /A !Key!
					Set "Key=!LastKey!"
				)Else If "!NewKey!"=="-" (
				REM	Set /A !Key!
					Set "Key=!LastKey!"
				)
				If "!Key!"=="Pause" (
					Set "NewKey="
					If not "!LastDropX!"=="" (
						Set "oldsprite=%p[1]sprite:[38=[48%"
						Set /A "TrimLeft=0","trimRight=0","right1=(p[1]x+p[1]len)","right2=(LastDropX+LastDropLen)"
						If !LastDropX! GTR !p[1]X! (
							Set /A "TrimLeft=LastDropX-p[1]x","p[1]x=LastDropX","p[1]len-=TrimLeft","MaxX=oMaxX-P[1]len+1"
						)Else (
							If !right1! GTR !right2! (
								Set /A "TrimRight=Right1-Right2","p[1]len-=TrimRight","MaxX=oMaxX-P[1]len+1"
							)
						)
						If !p[1]len! LSS 1 (
							<nul Set /p "=!\E![1;1H!screen!!Tower!!oldsprite!%\E%[0m
							Echo/|Choice /n
							For /L %%i in (-20000 1 20000)Do (Call )
							%ForceQuit%
							Call:cleanup
							EXIT
						)
						REM Set /A "P[1]len=P[1]len-trimLeft-trimRight"
						If not "!Tail!"=="" For /F "delims=" %%i in ("!P[1]len!")Do Set "Tail=!Tail:~-%%i!"
						Set "Tower=!Tower!%p[1]Sprite%"
						Set /A "LastDropX=p[1]x","LastDropLen=p[1]Len"
					)Else	(
						Set /A "LastDropX=p[1]x","LastDropLen=p[1]Len"
						Set "Tower=!Tower!%p[1]Sprite%"
					)
					Set /A "NewDir=!Random! %% 2",!k-!
					If !NewDir! EQU 0 (
						Set /A "p[1]y-=1","p[1]x=1"
						Set "Key=!k6!"
					)Else (
						Set /A "p[1]y-=1","p[1]x=MaxX"
						Set "Key=!k4!"
					)
				)
			)
			%= bounce horizontally =%
			If "!key!"=="!k4!" If "!P[1]x!"=="1" Set "key=!k6!"
			If "!key!"=="!k6!" If "!P[1]x!"=="!maxX!" Set "key=!k4!"

			If not "!key!"=="" Set /A !Key!
			<nul Set /p "=!\E![1;1H!screen!!Tower!%P[1]Sprite%%\E%[0m"
				
			Set /A "Delay=BaseDelay"
			Set /A "Delay=Delay*((MaxX*1000/(MaxY*1000))"
			For /L %%i in (1 1 !Delay!)Do rem we go way to fast without a delay
		)
	)

	REM the bellow line should never execute.
	EXIT


==========================================================================================================
REM no changes should be made to the below utilities
==========================================================================================================
:Setup
	(
		Echo("fontSize=%fontSize%"
		Echo("fontType=%fontType%"
		Echo("s[1]col=%s[1]col%"
		Echo("FGcol=%FGcol%"
		Echo("BGi[1]=%BGi[1]%"
		Echo("BGi[2]=%BGi[2]%"
		Echo("SPi=%Spi%"
		Echo("Difficulty=%Difficulty%"
	) >"%~n0:defaults"
	REM Define essential vars for multithreaded controller
	REM Signal file that the Controller uses to pass keypress to the game via without blocking execution.
	REM - Note - This is modified later to a Lockfile in format: %TEMP%\%~n0_%PID%_Signal.cmd
	Set "SignalFile=%TEMP%\%~n0__Signal.cmd"

	REM Control Key Definitions
	Call :createChars || (
		Exit /B 1
	)
	REM QuitKey must be defined prior to starting Multithreaded controller. TAB is recommended
	Set "QUITKEY=%TAB%"
	Set "kP=Pause" & Set "Pause=1" & Set "k =Pause"

	(Title %~n0)

:GetSession
	REM get session ID to prevent File Read / write error if multiple instances running
	For /F "Skip=2 tokens=2" %%G in ('Tasklist /fi "windowtitle eq %~n0"')Do Set "Lock=%%G"
	CALL Set "SignalFile=%%SignalFile:__=_%Lock%_%%"
	For /F "tokens=2 Delims=:" %%G in ('CHCP')Do >"%TEMP%\%~n0_%Lock%_restore.cmd" Echo(@CHCP %%G ^> nul
	Del "%SignalFile:Signal=Abort%" 2> nul

	CHCP 65001 > nul

	Start /Wait /B "" "%~F0" XCOPYCONTROLLER 1>"%SignalFile%" 2> nul | "%~F0" GameInit <"%SignalFile%" 2> nul

	REM game has resolved; end script.
	DEL "%SignalFile%" 2> nul 1> nul
	Endlocal & Goto:Eof
=========================

:createChars
	for /f "Delims=" %%e in ('Echo Prompt $E^|cmd') Do Set "\E=%%e"
	for /f "delims= " %%T in ('robocopy /L . . /njh /njs' )do set "TAB=%%T"
	for /f %%C in ('copy /Z "%~dpf0" nul') do set "CR=%%C"
	for /F "delims=#" %%B in ('"prompt #$H# &echo on &for %%b in (1) do rem"') do Set "BS=%%B"
	Set "BS=%BS:~0,1%"
	del sub.tmp 2> nul
	copy nul sub.tmp /a > nul
	If not exist sub.tmp (
		Call:Error 0x03
		Exit /B 1
	)
	for /F %%a in (sub.tmp) DO (
		set "sub=%%a"
	)
	del sub.tmp
	(Set LF=^


	)%= Do Not Modify. Linefeed Variable =%
	exit /b

:XCOPYCONTROLLER
	If not exist C:\Windows\System32\Xcopy.exe (
		Call:Error 0x02 >"%SignalFile:Signal=Abort%"
		<nul Set /P "=quit"
		EXIT
	)
	Setlocal DISABLEdelayedExpansion
	REM Environment handling allows use of ! key
	For /l %%C in () do (

		Set "Key="
		for /f "delims=" %%A in ('C:\Windows\System32\xcopy.exe /w "%~f0" "%~f0" 2^>nul') do If not Defined Key (
	      	set "key=%%A"
			Setlocal ENABLEdelayedExpansion
	      	set key=^!KEY:~-1!
			If "!key!" == "!QUITKEY!" (
				>"%SignalFile:Signal=Abort%" Echo(
				<nul Set /P "=quit"
				EXIT
			)
			If not "!Key!" == "%BS%" If not "!Key!" == "!CR!" (%= Echo without Linefeed. Allows output of Key and Space =%
				1> %~n0txt.tmp (echo(!key!!sub!)
				copy %~n0txt.tmp /a %~n0txt2.tmp /b > nul
				type %~n0txt2.tmp
				del %~n0txt.tmp %~n0txt2.tmp
			)Else (
				If "!Key!" == "%BS%" <nul Set /p "={BACKSPACE}"
				If "!Key!" == "!CR!" <nul Set /p "={ENTER}"
			)
			Endlocal
		)
	)


:Cleanup
	<Nul Set /P "=%\E%[1;1H%\E%[2J%\E%[?25h%\E%[0mGame over. "
	<"%SignalFile:Signal=Abort%" Set /P "XcopyError="
	If Defined XcopyError Echo(!XcopyError!
	CALL "%TEMP%\%~n0_%Lock%_restore.cmd" > nul
	Del %TEMP%\%~n0_%lock%_*.cmd 1> nul 2> nul 
	(Title )
EXIT

:GetInt <VarName> <"Description Text">
	Set "input="
	Set /p "input=%\E%8%\E%[32m%\E%[0JEnter a value for %~2: %\E%[0m"
	2>nul Set /A "input+=0","1/input" || Goto:GetInt
	If %input% LEQ 0 Goto:GetInt
	If %input% GTR 255 Goto:GetInt
	Set /A "%~1=input"
Exit /b 0

:delTemp
	REM this file creates signal files to identify the session and communicate input.
	REM If the game is force closed instead of Quit with TAB key
	REM the files will persist. To remove them, Call this file from the command line with The argument: deltemp
	Del "%TEMP%\%~n0_*_*.cmd"
Goto:Eof

:setFont <integerSize> <stringFontName>
REM from the Library created by IcarusLives
REM https://github.com/IcarusLivesHF/Windows-Batch-Library/tree/8812670566744d2ee14a9a68a06be333a27488cc
if "%~2" equ "" goto :eof
call :init_setfont
%setFont% %~1 %~2
goto :eof

:init_setfont DON'T CALL
:: - BRIEF -
::  Get or set the console font size and font name.
:: - SYNTAX -
::  %setfont% [fontSize [fontName]]
::    fontSize   Size of the font. (Can be 0 to preserve the size.)
::    fontName   Name of the font. (Can be omitted to preserve the name.)
:: - EXAMPLES -
::  Output the current console font size and font name:
::    %setfont%
::  Set the console font size to 14 and the font name to Lucida Console:
::    %setfont% 14 Lucida Console
setlocal DisableDelayedExpansion
set setfont=for /l %%# in (1 1 2) do if %%#==2 (^
%=% for /f "tokens=1,2*" %%- in ("? ^^!arg^^!") do endlocal^&powershell.exe -nop -ep Bypass -c ^"Add-Type '^
%===% using System;^
%===% using System.Runtime.InteropServices;^
%===% [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)] public struct FontInfo{^
%=====% public int objSize;^
%=====% public int nFont;^
%=====% public short fontSizeX;^
%=====% public short fontSizeY;^
%=====% public int fontFamily;^
%=====% public int fontWeight;^
%=====% [MarshalAs(UnmanagedType.ByValTStr,SizeConst=32)] public string faceName;}^
%===% public class WApi{^
%=====% [DllImport(\"kernel32.dll\")] public static extern IntPtr CreateFile(string name,int acc,int share,IntPtr sec,int how,int flags,IntPtr tmplt);^
%=====% [DllImport(\"kernel32.dll\")] public static extern void GetCurrentConsoleFontEx(IntPtr hOut,int maxWnd,ref FontInfo info);^
%=====% [DllImport(\"kernel32.dll\")] public static extern void SetCurrentConsoleFontEx(IntPtr hOut,int maxWnd,ref FontInfo info);^
%=====% [DllImport(\"kernel32.dll\")] public static extern void CloseHandle(IntPtr handle);}';^
%=% $hOut=[WApi]::CreateFile('CONOUT$',-1073741824,2,[IntPtr]::Zero,3,0,[IntPtr]::Zero);^
%=% $fInf=New-Object FontInfo;^
%=% $fInf.objSize=84;^
%=% [WApi]::GetCurrentConsoleFontEx($hOut,0,[ref]$fInf);^
%=% If('%%~.'){^
%===% $fInf.nFont=0; $fInf.fontSizeX=0; $fInf.fontFamily=0; $fInf.fontWeight=0;^
%===% If([Int16]'%%~.' -gt 0){$fInf.fontSizeY=[Int16]'%%~.'}^
%===% If('%%~/'){$fInf.faceName='%%~/'}^
%===% [WApi]::SetCurrentConsoleFontEx($hOut,0,[ref]$fInf);}^
%=% Else{(''+$fInf.fontSizeY+' '+$fInf.faceName)}^
%=% [WApi]::CloseHandle($hOut);^") else setlocal EnableDelayedExpansion^&set arg=
endlocal &set "setfont=%setfont%"
if !!# neq # set "setfont=%setfont:^^!=!%"
exit /b

:Error
	<nul Set /p "=Error:" & CMD /C Set /A %1
Exit /B


einstein1969
Expert
Posts: 941
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Stacker, A batch interpretation of the game.

#2 Post by einstein1969 » 21 Apr 2023 09:56

Well done T3rry!

It's fun and difficult, but definitely a great idea.

thank you for sharing!

T3RRY
Posts: 243
Joined: 06 May 2020 10:14

Re: Stacker, A batch interpretation of the game.

#3 Post by T3RRY » 25 Apr 2023 23:21

einstein1969 wrote:
21 Apr 2023 09:56
Well done T3rry!

It's fun and difficult, but definitely a great idea.

thank you for sharing!
Thanks Einstein. Icarus Yeshi and I are collaborating on a game engine at the moment that is going to allow for far more exciting batch games

einstein1969
Expert
Posts: 941
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Stacker, A batch interpretation of the game.

#4 Post by einstein1969 » 26 Apr 2023 04:15

Today I relaunched the game but it doesn't work anymore.

I practically press the space and it does nothing.

If I press tab instead it exits.

I have it on the desktop, I saw that it creates a file on the desktop.

einstein1969
Expert
Posts: 941
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Stacker, A batch interpretation of the game.

#5 Post by einstein1969 » 26 Apr 2023 05:03

I tried to delete the batch file and now it started working again.

There must be a bug maybe on stream management.

T3RRY
Posts: 243
Joined: 06 May 2020 10:14

Re: Stacker, A batch interpretation of the game.

#6 Post by T3RRY » 26 Apr 2023 10:40

einstein1969 wrote:
26 Apr 2023 05:03
I tried to delete the batch file and now it started working again.

There must be a bug maybe on stream management.
I haven't experienced this bug, but I typically run scripts from the command line
When you say stream, are you thinking ther may be an error with the Alternate Data stream the file uses for saving settings, or the multithreading with use of lock file?

einstein1969
Expert
Posts: 941
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Stacker, A batch interpretation of the game.

#7 Post by einstein1969 » 26 Apr 2023 13:30

Yes, I was referring to alternate data streams.
By deleting the batch file you have reset.
At least I think that is where the problem may be.
The "space" key no longer worked.

T3RRY
Posts: 243
Joined: 06 May 2020 10:14

Re: Stacker, A batch interpretation of the game.

#8 Post by T3RRY » 26 Apr 2023 13:50

einstein1969 wrote:
26 Apr 2023 13:30
Yes, I was referring to alternate data streams.
By deleting the batch file you have reset.
At least I think that is where the problem may be.
The "space" key no longer worked.
If you encounter the problem again, Insert the below code into the start of the file after @Echo off, run it, then remove the lines.

Code: Select all

Powershell -nologo -noprofile -command "remove-item -path '%~nx0' -Stream '*'" 2> nul
Goto:eof
That will remove the Alternate data streams from the file.
If that fixes it, then it's the ADS that's the issue and I'll change the save mechanism to use a txt file instead.

einstein1969
Expert
Posts: 941
Joined: 15 Jun 2012 13:16
Location: Italy, Rome

Re: Stacker, A batch interpretation of the game.

#9 Post by einstein1969 » 29 Apr 2023 09:43

For the moment it is working.

Post Reply