Page 1 of 2

Pure Batch Colored Bouncing ball animation

Posted: 28 Mar 2018 23:36
by IcarusLives
Hello everyone!

I am always really shy posting things here..

So lately I have been experimenting with WINDOWS 10 VT100 escape sequences. This allows me to place characters throughout the console in any color I want....

So I have produced a number of macros; plot, circle, line, and arc. So far, only plot and circle take color arguments, but I'll implement shortly. For now, you can color with plot and circle, but line and arc remain at 30/255 in terms of colour.

This is a little flickery.... but hey, it's pure batch!
Image

le code

Code: Select all

@echo off & setlocal enableDelayedExpansion
mode 50,50

set ^"LF=^

^" Above empty line is required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
for /F %%a in ('echo prompt $E^| cmd') do set "ESC=%%a"

set plot=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-3 delims=:" %%1 in ("^!args^!:^!ESC^!") do (%\n%
	for /f "tokens=1-4" %%a in ("%%~1") do ^<nul set /p "=%%3[%%b;%%aH%%3[38;5;%%cm%%3[48;5;%%dm%%~2%%3[0m"%\n%
)) else set args=

set circle=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-6" %%1 in ("^!args^!") do (%\n%
		if "%%~5" equ "" ( set "hue=30" ) else ( set "hue=%%~5")%\n%
        for /l %%y in (-%%3,1,%%3) do for /l %%x in (-%%3,1,%%3) do (%\n%
            set /a "S=(%%x * %%x) + (%%y * %%y) - (%%3 * %%3)", "_3div2=%%3 / 2", "tx=%%x+%%1", "ty=%%y+%%2"%\n%
			for /f "tokens=1-5" %%a in ("^!S^! ^!_3div2^! ^!tx^! ^!ty^! ^!hue^!") do (%\n%
					   if "%%6" equ "/f" ( if %%a leq 1    ( ^!plot^! %%c %%d %%e 0 :%%~4)%\n%
				) else if "%%6" equ "/n" ( if %%a geq -%%3 ( ^!plot^! %%c %%d %%e 0 :%%~4)%\n%
				) else if %%a geq -%%3 if %%a leq %%b      ( ^!plot^! %%c %%d %%e 0 :%%~4)%\n%
		))%\n%
        set "s="%\n%
)) else set args=

for /l %%b in (0,1,4) do (
	set /a "x%%b=!random! %% 40 + 10"
	set /a "y%%b=!random! %% 40 + 10"
	set /a "i%%b=!random! %% 3 + 1"
	set /a "j%%b=!random! %% 2 + 1"
	set /a "s%%b=!random! %% 3 + 2"
	set /a "b%%b=50 - s%%b"
	set /a "c%%b=!random! %% 255"
)

for /l %%# in () do ( for /l %%b in (0,1,4) do (
	
		set /a "x%%b+=i%%b", "y%%b+=j%%b"
		
		if !x%%b! geq !b%%b! set /a "x%%b=b%%b", "i%%b*=-1"
        if !y%%b! geq !b%%b! set /a "y%%b=b%%b", "j%%b*=-1"
        if !x%%b! leq !s%%b! set /a "x%%b=s%%b", "i%%b*=-1"
        if !y%%b! leq !s%%b! set /a "y%%b=s%%b", "j%%b*=-1"
		
		%circle% !x%%b! !y%%b! !s%%b! . !c%%b!
	)
	cls
)
Other macros

Code: Select all

:macros
set ^"LF=^

^" Above empty line is required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
for /F %%a in ('echo prompt $E^| cmd') do set "ESC=%%a"

set plot=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-3 delims=:" %%1 in ("^!args^!:^!ESC^!") do (%\n%
	for /f "tokens=1-4" %%a in ("%%~1") do ^<nul set /p "=%%3[%%b;%%aH%%3[38;5;%%cm%%3[48;5;%%dm%%~2%%3[0m"%\n%
)) else set args=

set circle=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-6" %%1 in ("^!args^!") do (%\n%
		if "%%~5" equ "" ( set "hue=30" ) else ( set "hue=%%~5")%\n%
        for /l %%y in (-%%3,1,%%3) do for /l %%x in (-%%3,1,%%3) do (%\n%
            set /a "S=(%%x * %%x) + (%%y * %%y) - (%%3 * %%3)", "_3div2=%%3 / 2", "tx=%%x+%%1", "ty=%%y+%%2"%\n%
			for /f "tokens=1-5" %%a in ("^!S^! ^!_3div2^! ^!tx^! ^!ty^! ^!hue^!") do (%\n%
					   if "%%6" equ "/f" ( if %%a leq 1    ( ^!plot^! %%c %%d %%e 0 :%%~4)%\n%
				) else if "%%6" equ "/n" ( if %%a geq -%%3 ( ^!plot^! %%c %%d %%e 0 :%%~4)%\n%
				) else if %%a geq -%%3 if %%a leq %%b      ( ^!plot^! %%c %%d %%e 0 :%%~4)%\n%
		))%\n%
        set "s="%\n%
)) else set args=

set line=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-5" %%1 in ("^!args^!") do (%\n%
	set /a "x0=%%~1", "y0=%%~2", "x1=%%~3", "y1=%%~4", "dx=%%~3 - %%~1", "dy=%%~4 - %%~2"%\n%
	for /f "tokens=1-2" %%6 in ("^!dx^! ^!dy^!") do (%\n%
		if %%~7 lss 0 ( set /a "dy=-%%~7", "stepy=-1" ) else ( set "stepy=1" )%\n%
		if %%~6 lss 0 ( set /a "dx=-%%~6", "stepx=-1" ) else ( set "stepx=1" )%\n%
		set /a "dx<<=1", "dy<<=1"%\n%
	)%\n%
	for /f "tokens=1-8" %%a in ("^!dx^! ^!dy^! ^!x0^! ^!x1^! ^!y0^! ^!y1^! ^!stepx^! ^!stepy^!") do (%\n%
		if %%~a gtr %%~b (%\n%
			set /a "fraction=%%~b - (%%~a >> 1)"%\n%
			for /l %%x in (%%~c,%%~g,%%~d) do (%\n%
				for /f "tokens=1" %%6 in ("^!fraction^!") do if %%~6 geq 0 set /a "y0+=%%~h", "fraction-=%%~a"%\n%
				set /a "fraction+=%%~b"%\n%
				for /f "tokens=1" %%6 in ("^!y0^!") do (%\n%
					if 0 leq %%x if %%x lss 199 if 0 leq %%~6 if %%~6 lss 199 ^!plot^! %%x %%~6 30 0 :%%~5%\n%
				)%\n%
			)%\n%
		) else (%\n%
			set /a "fraction=%%~a - (%%~b >> 1)"%\n%
			for /l %%y in (%%~e,%%~h,%%~f) do (%\n%
				for /f "tokens=1" %%6 in ("^!fraction^!") do if %%~6 geq 0 set /a "x0+=%%~g", "fraction-=%%~b"%\n%
				set /a "fraction+=%%~a"%\n%
				for /f "tokens=1" %%6 in ("^!x0^!") do (%\n%
					if 0 leq %%~6 if %%~6 lss 199 if 0 leq %%y if %%y lss 199 ^!plot^! %%~6 %%y 30 0 :%%~5%\n%
				)%\n%
			)%\n%
		)%\n%
	)%\n%
)) else set args=

set arc=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-6" %%a in ("^!args^!") do (%\n%
	set /a "arc=%%~d + %%~f"%\n%
    for /f "tokens=1-3" %%g in ("^!arc^! sin^(x^) cos^(x^)") do for /l %%j in (%%~f,%%~e,%%~g) do (%\n%
		set /a "_x=%%~c * ^!%%~h:x=%%~j^! + %%~a", "_y=%%~c * ^!%%~i:x=%%~j^! + %%~b"%\n%
		for /f "tokens=1,2" %%X in ("^!_x^! ^!_y^!") do ^!line^! %%~a %%~b %%~X %%~Y .%\n%
	)%\n%
)) else set args=

	set /a "PI=(35500000/113+5)/10, PI_div_2=(35500000/113/2+5)/10, PIx2=2*PI, PI32=PI+PI_div_2"
    set "_SIN=a-a*a/1920*a/312500+a*a/1920*a/15625*a/15625*a/2560000-a*a/1875*a/15360*a/15625*a/15625*a/16000*a/44800000"
    set "SIN(x)=(a=(x * 31416 / 180)%%62832, c=(a>>31|1)*a, a-=(((c-47125)>>31)+1)*((a>>31|1)*62832)  +  (-((c-47125)>>31))*( (((c-15709)>>31)+1)*(-(a>>31|1)*31416+2*a)  ), %_SIN%) / 10000"
    set "COS(x)=(a=(15708 - x * 31416 / 180)%%62832, c=(a>>31|1)*a, a-=(((c-47125)>>31)+1)*((a>>31|1)*62832)  +  (-((c-47125)>>31))*( (((c-15709)>>31)+1)*(-(a>>31|1)*31416+2*a)  ), %_SIN%) / 10000"
    set "_SIN="
    set "Sqrt(N)=( x=(N)/(11*1024)+40, x=((N)/x+x)/2, x=((N)/x+x)/2, x=((N)/x+x)/2, x=((N)/x+x)/2, x=((N)/x+x)/2 )"
    set "Sign(n)=(n>>31)|((-n>>31)&1)"
    set "Abs(x)=(((x)>>31|1)*(x))"
	REM  Max1(x)=  if (x geq y)  then x    else y
	set "Max(x)=( ?=((x-y)>>31)+1, ?*x + ^^^!?*y )"
	set "Max=( ?=(((((t1)>>31|1)*(t1))-(((t2)>>31|1)*(t2)))>>31)+1, ?*(2*(((t1)>>31|1)*(t1))-(((t2)>>31|1)*(t2))-((((t1)>>31|1)*(t1))-(((t2)>>31|1)*(t2)))) + ^^^!?*((((t1)>>31|1)*(t1))-(((t2)>>31|1)*(t2))-((((t1)>>31|1)*(t1))-(((t2)>>31|1)*(t2))*2)) )"
	set "getDistance(x2,x1,y2,y1)=( @=x2-x1, $=y2-y1, ?=(((@>>31|1)*@-(($>>31|1)*$))>>31)+1, ?*(2*(@>>31|1)*@-($>>31|1)*$-((@>>31|1)*@-($>>31|1)*$)) + ^^^!?*((@>>31|1)*@-($>>31|1)*$-((@>>31|1)*@-($>>31|1)*$*2)) )"
    set "swap(x,y)=t=x, x=y, y=t"
goto :eof
I hope the use of VT100 sparks attention here on DOSTIPS :) Enjoy ^^

Re: Pure Batch Colored Bouncing ball animation

Posted: 29 Mar 2018 10:24
by misol101
Removed

Re: Pure Batch Colored Bouncing ball animation

Posted: 29 Mar 2018 12:42
by IcarusLives
Misol, I appreciate the enthusiasm for graphics, but I would really like to not have another one of my topics hijacked by you and cmdgfx. If you read anything I wrote in the op, youd realize I'm looking for anti flicker methods and vt100 exploration.

We all know that whatever batch can do, cmdgfx can make it look better, but tbh it's not pure batch and that kills all of the fun for me.

I hope you understand

Re: Pure Batch Colored Bouncing ball animation

Posted: 29 Mar 2018 13:48
by misol101
Sorry mate, next time I will refrain. Back to the topic at hand!

Re: Pure Batch Colored Bouncing ball animation

Posted: 29 Mar 2018 14:21
by IcarusLives
Thank you for understanding!! That was much less painless than I was expecting. :mrgreen: :mrgreen:

Re: Pure Batch Colored Bouncing ball animation

Posted: 30 Mar 2018 10:27
by dbenham
Nicely done.

I noticed that the balls get compressed when they bounce of the top or left edge. But they do not compress when they bounce of the right or bottom. I'm assuming this is an artifact of a computation error, but it looks cool. It would be nice if the compression was symmetric, but I don't know how hard that would be to implement.

It would be nice if you added some documentation - At a minimum document the calling syntax for each macro.
I recommend use of %= Comment =% style comments for use within parenthesized blocks, and also within macros. It is especially nice in macros because the "comment" expands to nothing (takes no environment space).

I made a number of simple changes that resulted in much improved output and behavior:

1) Parametized the ball count, and optionally pass a ball count to the main script to specify how many balls to display. Defaults to 5 as you originally had it.

2) The cursor display is very distracting. Added an escape sequence at the beginning to hide the cursor, and another sequence at the end to show the cursor.

3) Simplified the plot macro
- There is no need to delay expansion of the Esc character. The escape literal can be embedded directly within the macro
- No need for two loops or : delimiter to parse the parameters. I switched to a single FOR loop that parses all 5 parameters. This also impacts the plot calls in the circle macro.

4) The flicker was easily eliminated by having plot write to a screen variable instead of stdout. Then when the entire frame is "plotted", I write the entire frame (screen) to stdout in one operation.

5) The program is terminated by pressing Ctrl-C. But I want the show cursor code to execute after the script is terminated. So I run the main loop in a new cmd.exe process so that the parent process can continue when the sub-process terminates. At the top of the program I dynamically generate a Yes and No file using the local language. Control is then transferred to the :init routine with input redirected to No so that the outer routine does not terminate upon Ctrl-C. The main loop sub-process has input redirected to Yes so that it does terminate upon Ctrl-C.

6) I changed the circle pixel character from period (.) to asterisk (*) - I think it looks much better.

Code: Select all

@echo off & setlocal enableDelayedExpansion
if "%~1"=="INIT" goto :init
if "%~1"=="RUN" goto :run

:: Set number of balls based on %1 - default to 5
set cnt=5
if "%~1" neq "" set /a cnt=%~1
if %cnt% leq 0 set cnt=5

::  Build Yes and No files using local language
if not exist "%~f0.yes" (
  for /f "delims=(/ tokens=2,3" %%A in (
    'copy /-y nul "%~f0" ^<nul'
  ) do if not exist "%~f0.yes" (
    >"%~f0.yes" echo %%A
    >"%~f0.no"  echo %%B
	)
)

:: Relaunch to init with input redirected to No
"%~f0" INIT <"%~f0.no"

:init
mode 50,50

set ^"LF=^

^" Above empty line is required - do not remove
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"
for /F %%a in ('echo prompt $E^| cmd') do set "ESC=%%a"

set plot=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-5" %%1 in ("^!args^!") do (%\n%
  set "screen=^!screen^!!esc![%%2;%%1H!esc![38;5;%%3m!esc![48;5;%%4m%%~5!esc![0m"%\n%
)) else set args=

set circle=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-6" %%1 in ("^!args^!") do (%\n%
    if "%%~5" equ "" ( set "hue=30" ) else ( set "hue=%%~5")%\n%
        for /l %%y in (-%%3,1,%%3) do for /l %%x in (-%%3,1,%%3) do (%\n%
            set /a "S=(%%x * %%x) + (%%y * %%y) - (%%3 * %%3)", "_3div2=%%3 / 2", "tx=%%x+%%1", "ty=%%y+%%2"%\n%
      for /f "tokens=1-5" %%a in ("^!S^! ^!_3div2^! ^!tx^! ^!ty^! ^!hue^!") do (%\n%
             if "%%6" equ "/f" ( if %%a leq 1    ( ^!plot^! %%c %%d %%e 0 %%~4)%\n%
        ) else if "%%6" equ "/n" ( if %%a geq -%%3 ( ^!plot^! %%c %%d %%e 0 %%~4)%\n%
        ) else if %%a geq -%%3 if %%a leq %%b      ( ^!plot^! %%c %%d %%e 0 %%~4)%\n%
    ))%\n%
        set "s="%\n%
)) else set args=

for /l %%b in (1,1,%cnt%) do (
  set /a "x%%b=!random! %% 40 + 10"
  set /a "y%%b=!random! %% 40 + 10"
  set /a "i%%b=!random! %% 3 + 1"
  set /a "j%%b=!random! %% 2 + 1"
  set /a "s%%b=!random! %% 3 + 2"
  set /a "b%%b=50 - s%%b"
  set /a "c%%b=!random! %% 255"
)

:: Hide the cursor
<nul set /p "=!esc![?25l"

:: Run the main loop in new process with input redirected to Yes
cmd /c "%~f0" RUN <"%~f0.yes"

:: Show the cursor
<nul set /p "=!esc![?25h"
cls

exit /b

:run
for /l %%# in () do ( 
  set "screen="
  for /l %%b in (1,1,%cnt%) do (
  
    set /a "x%%b+=i%%b", "y%%b+=j%%b"
    
    if !x%%b! geq !b%%b! set /a "x%%b=b%%b", "i%%b*=-1"
        if !y%%b! geq !b%%b! set /a "y%%b=b%%b", "j%%b*=-1"
        if !x%%b! leq !s%%b! set /a "x%%b=s%%b", "i%%b*=-1"
        if !y%%b! leq !s%%b! set /a "y%%b=s%%b", "j%%b*=-1"
    
    %circle% !x%%b! !y%%b! !s%%b! * !c%%b!
  )
  cls
  <nul set /p "=!screen!"
)
Dave Benham

Re: Pure Batch Colored Bouncing ball animation

Posted: 30 Mar 2018 11:17
by IcarusLives
Oh my god! Truly breath taking! You've managed to take away the flicker! THIS IS BEAUTIFUL!!!!
dbenham wrote:
30 Mar 2018 10:27
Nicely done.

I noticed that the balls get compressed when they bounce of the top or left edge. But they do not compress when they bounce of the right or bottom. I'm assuming this is an artifact of a computation error, but it looks cool. It would be nice if the compression was symmetric, but I don't know how hard that would be to implement.
Would be an easy adjustment to s%%b by + 1 during initialize, but for the sake of keeping the code small, I figured I could deal with the minor display error :P
dbenham wrote:
30 Mar 2018 10:27
It would be nice if you added some documentation - At a minimum document the calling syntax for each macro.
I recommend use of %= Comment =% style comments for use within parenthesized blocks, and also within macros. It is especially nice in macros because the "comment" expands to nothing (takes no environment space).
Excellent suggestion! I will comment my macros with their usage and share them. I'm not used to people wanting to read my code :oops:
dbenham wrote:
30 Mar 2018 10:27
I made a number of simple changes that resulted in much improved output and behavior:

1) Parametized the ball count, and optionally pass a ball count to the main script to specify how many balls to display. Defaults to 5 as you originally had it.

2) The cursor display is very distracting. Added an escape sequence at the beginning to hide the cursor, and another sequence at the end to show the cursor.

3) Simplified the plot macro
- There is no need to delay expansion of the Esc character. The escape literal can be embedded directly within the macro
- No need for two loops or : delimiter to parse the parameters. I switched to a single FOR loop that parses all 5 parameters. This also impacts the plot calls in the circle macro.

4) The flicker was easily eliminated by having plot write to a screen variable instead of stdout. Then when the entire frame is "plotted", I write the entire frame (screen) to stdout in one operation.

5) The program is terminated by pressing Ctrl-C. But I want the show cursor code to execute after the script is terminated. So I run the main loop in a new cmd.exe process so that the parent process can continue when the sub-process terminates. At the top of the program I dynamically generate a Yes and No file using the local language. Control is then transferred to the :init routine with input redirected to No so that the main routine does not terminate upon Ctrl-C. The main loop sub-process has input redirected to Yes so that it does terminate upon Ctrl-C.

6) I changed the circle pixel character from period (.) to asterisk (*) - I think it looks much better
I cannot thank you enough for everything here ESPECIALLY #4. This was my biggest obstacle because I could not figure a way to NOT %plot% to stdout.. To be honest I had not thought of this

Code: Select all

set plot=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-5" %%1 in ("^!args^!") do (%\n%
  set "screen=^!screen^!!esc![%%2;%%1H!esc![38;5;%%3m!esc![48;5;%%4m%%~5!esc![0m"%\n%
)) else set args=
This completely changes it all for me, and I cannot thank you enough for your help! This is fantastic!

Re: Pure Batch Colored Bouncing ball animation

Posted: 04 Apr 2018 19:53
by dbenham
Well, the flicker was not gone - it was just dramatically reduced. Any algorithm that uses CLS to erase the previous frame will always introduce flicker because there is a moment in time when the screen is black. The larger the window, the more pronounced the flicker becomes. The problem is exacerbated when screen capture is used to record a movie of the action, because an entire frame may be captured when the screen is black, and the movie is likely at a slower frame rate than the screen. So when you capture a black screen, it becomes more of a blink than a flicker.

So I decided to do even more to reduce, if not eliminate, the flicker. I decided to provide an option to abandon CLS and instead each ball would be drawn by first plotting the ball in black at the former position (erase the prior position), then the ball would immediately be plotted in color at the new position. Once one ball was done, then it would move on to the next ball...

The ball erase method leads to some display artifacts when the erasure of ball 2 overwrites the plot of ball 1 - which looks sort of like a shadow, but it is not at all realistic. Thankfully the "shadow" disappears as the balls separate.

Since each ball gets plotted twice per frame, it slows things down. I decided to spend time further optimizing performance.

First off, there is no need to compute the circle pixels each and every frame. Instead I pre-compute a list of coordinates for each ball at position 0 with color and character information and store the list as a variable. Then the plot routine need only read the x,y coordinates and add the new origin. No need to compute the circle points every time. This makes a huge difference in performance. I do this in two phases - first I calculate the coordinates of a generic circle for each of the 3 supported sizes. Then in a 2nd loop I add the color (and character, though it happens to be constant) for each ball. Each string represents a sprite that simply needs to be translated to the correct position.

One additional major performance improvement is to limit the number of times x and y coordinates need to be translated. If there are consecutive horizontal pixels on the same line, then there is no need to compute x and y for each pixel - we just need the coordinates for the 1st pixel, and then the color and character for the contiguous pixels can be tacked on. This also significantly reduces storage string sizes and speeds up performance.

I also added an option to choose between 4 bit (16 colors), 8 bit (256 colors), or 24 bit (16,777,216 colors). The original version used a single color for each ball. But I added a cool option to apply a color gradient that gives a crude 3-D effect, especially when using 24 bit colors.

Performance got good enough that restricting the program to a single ball results in a frame rate that is too fast. So I added an option to wait a minimum amount of time between frames.

My prior version collected all the strings for each frame into a single variable that gets printed to screen all at once. But the string is limited to <8191 characters. So I refined the code to detect when a string is approaching the max length, and then I switch to a new string. So for each frame an "array" of strings gets cleared, then the strings are built, and finally plotted.

The BALLS.BAT program only plots "circles" (really they look like oblong Christmas ornaments to me). But the plot macro is designed to support plotting of pretty much any sprite that contains lists of coordinates with color and character info.

I kind of got carried away, and decided to parametize a bunch of additional configuration values, and provide command line arguments to control the options. Here is the output of the built in help that describes all the options:

Code: Select all

BALLS.BAT  [/Option [Value]]...
  /B Balls  - Ball count     (default 5)
  /H Height - screen Height  (default 50)
  /W Width  - screen Width   (default 100)
  /T cSec   - min frame Time (default 0)
  /X Char   - piXel char     (default @)
  /C ColorBitCount - 4,8,24  (default 4)
  /G - color Gradient
  /S - Solid balls
  /E - Erase balls instead of CLS
  /P - Pause after each frame refresh
  /D - Debug mode
  /? - Help
Here is the actual code:

Code: Select all

@echo off
if "%~1"=="RUN" goto :run
if "%~1"=="LOOP" goto :loop
setlocal enableDelayedExpansion

:::
:::BALLS.BAT  [/Option [Value]]...
:::
:::  /B Balls  - Ball count     (default 5)
:::  /H Height - screen Height  (default 50)
:::  /W Width  - screen Width   (default 100)
:::  /T cSec   - min frame Time (default 0)
:::  /X Char   - piXel char     (default @)
:::  /C ColorBitCount - 4,8,24  (default 4)
:::  /G - color Gradient
:::  /S - Solid balls
:::  /E - Erase balls instead of CLS
:::  /P - Pause after each frame refresh
:::  /D - Debug mode
:::  /? - Help
:::

:: Define options
set "options= -?: -S: -E: -P: -D: -G: -H:50 -W:100 -B:5 -C:4 -X:"@" -T:0 "

:: Set default option values
for %%O in (%options%) do for /f "tokens=1* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:: Get options
:optionLoop
if not "%~1"=="" (
  set "arg=%~1"
  if "!arg:~0,1!" equ "/" set "arg=-!arg:~1!"
  for /f delims^=^ eol^= %%A in ("!arg!") do set "test=!options:*%%A:=! "
  if "!test!"=="!options! " (
      >&2 echo Error: Invalid option %~1. Use %~nx0 -? to get help.
      exit /b 1
  ) else if "!test:~0,1!"==" " (
      set "!arg!=1"
      if /i "!arg!" equ "-U" set "-z="
  ) else (
      set "!arg!=%~2"
      shift /1
  )
  shift /1
  goto :optionLoop
)

if defined -? (
  mode 50,25
  for /f "delims=: tokens=*" %%A in ('findstr "^:::" "%~f0"') do echo(%%A
  exit /b
)

:: Validate options and configure
set /a "ballCnt=4, ballCnt=%-B%-1" 2>nul
set /a "ht=50, ht=%-H%" 2>nul
set /a "wd=100, wd=%-W%" 2>nul
set /a "delay=0, delay=%-T%" 2>nul
set "validColorBits= 4 8 24 "
if "!validColorbits:%-C%=!" equ "!validColorBits!" set "-C=4"
if %ballCnt% lss 0 set "ballCnt=0"
if %ht% lss 20 set "ht=20"
if %wd% lss 20 set "wd=20"
if defined -X (set "-X=!-X:~0,1!") else set "-X=@"
if "!-X!"==" " set "-X=@"

if not defined -D mode %wd%,%ht%

:: ---- Define macros ----
(set LF=^
%= Empty line generates linefeed =%
)
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

for /F %%a in ('echo prompt $E^| cmd') do set "ESC=%%a"

:: plot  x  y  coordList  buffer
set plot=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-4" %%1 in ("^!args^!") do (%\n%
  for %%C in (^^!%%3^^!) do for /f "tokens=1-3" %%x in (%%C) do (%\n%
    set /a "x=%%x+%%1, y=%%y+%%2"%\n%
    set "screen%%4=^!screen%%4^!!esc![^!y^!;^!x^!H%%z"%\n%
  )%\n%
)) else set args=

if defined -E (  %= Erase each ball before plotting generates shadow artifacts on prior balls =%
  set "erase=!plot! ^!x%%b^! ^!y%%b^! ballEraser^!s%%b^! ^!si%%b^!"
  set "cls="
) else (         %= Using CLS to clear all balls generates flicker =%
  set "erase="
  set "cls=cls"
)

if %delay% gtr 0 (
  set "compute=1"
  set "t1=0"
  set "initDelay=if defined compute ("
  set openDelay=set compute=^)%\n%
    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"%\n%
    if ^^!tDiff^^! lss 0 set /a tDiff+=24*60*60*100%\n%
    if ^^!tDiff^^! geq !delay! (
  set "closeDelay=set compute=1&set t1=^!t2^!)"
) else (
  set "initDelay="
  set "openDelay="
  set "closeDelay="
)

if defined -P (set "pause=pause >nul <con") else set "pause="
:: ---- End macros ----

:: Build list of relative pixel coordinates for all possible circle sizes - 2,3,4
:: Each circleN is a string in the format '"x1 y1" "x2 y2" ... "xn yn"'
:: 0 is the center of the circle
:: Also build ballErasers by adding color/character string to circle coordinates
for /l %%s in (2 1 4) do (
  %== Build CircleN ==%
  set "circle%%s="
  for /l %%y in (-%%s,1,%%s) do for /l %%x in (-%%s,1,%%s) do (
    set /a "s=%%x*%%x+%%y*%%y-%%s*%%s, _3div2=%%s/2"
    if defined -S ( %== solid ball ==%
      if !s! leq 1 (set circle%%s=!circle%%s! "%%x %%y")
    ) else (        %== outline ball ==%
      if !s! geq -%%s if !s! leq !_3div2! (set circle%%s=!circle%%s! "%%x %%y")
    )
  )
  if defined -E (
    %== Build ballEraserN ==%
    set priorX=100
    set priorY=100
    set "ballEraser%%s="
    for %%C in (!circle%%s!) do for /f "tokens=1,2" %%x in ("%%~C") do (
      set /a "1/(100*(%%x-!priorX!-1)+%%y-!priorY!)" 2>nul && (
        %== New line ==%
        set "ballEraser%%s=!ballEraser%%s!" "%%~C ."
      ) || (
  		  %== Line continuation ==%
        set "ballEraser%%s=!ballEraser%%s!."
      )
      set /a "priorX=%%x, priorY=%%y"
    )
    for /f "tokens=1,2*" %%a in ("!ballEraser%%s:~2!") do set "ballEraser%%s=%%a %%b !esc![30m%%c""
  )
)

::  Build Yes and No files using local language
if not exist "%~f0.yes" (
  for /f "delims=(/ tokens=2,3" %%A in (
    'copy /-y nul "%~f0" ^<nul'
  ) do if not exist "%~f0.yes" (
    >"%~f0.yes" echo %%A
    >"%~f0.no"  echo %%B
  )
)

:: Initialize balls
set /a "screenCnt=size=0"
set "size2="
set "size3="
set "size4="
for /l %%B in (0,1,%ballCnt%) do (
  set /a "x%%B=!random! %% (wd-10) + 10"          %= x pos (col) =%
  set /a "y%%B=!random! %% (ht-10) + 10"          %= y pos (row) =%
  set /a "i%%B=(!random!%%3+1)*(1-!random!%%2*2)" %= x delta (speed & angle) =%
  set /a "j%%B=(!random!%%2+1)*(1-!random!%%2*2)" %= y delta (speed & angle) =%
  set /a "s%%B=!random! %% 3 + 2"                 %= size =%
  set /a "b%%B=ht - s%%B"                         %= screen bottom edge =%
  set /a "r%%B=wd - s%%B"                         %= screen right edge =%
  set /a "si%%B=screenCnt                         %= screen index =%

  %== Build each ball "sprite" by adding color and pixel character to each coordinate pair in circleN ==%  
  set priorX=100
  set priorY=100
  set "ball%%B="
  if defined -G ( %======= Color Gradient ======%
    if %-C% ==  4 (  %==  4 bit color ==%
      set /a "c%%B=31+!random!%%7"
      for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
        set /a "c=c%%B+60*^!(-%%y&1<<31)"
        set /a "1/(100*(%%x-!priorX!-1)+%%y-!priorY!)" 2>nul && (
          %== New line ==%
          set "ball%%B=!ball%%B!" "%%~C !esc![!c!m!-X!"
        ) || (
    		  %== Line continuation ==%
          set "ball%%B=!ball%%B!!-X!"
        )
        set /a "priorX=%%x, priorY=%%y"
      )
    )
    if %-C% ==  8 (  %==  8 bit color ==%
      set /a "c%%B=16+!random!%%6*36+!random!%%6"   %= compute random base color              =%
      set "xg=12/(s%%B*2+1), yg=2*12/(s%%B*2+1)"    %= adjust x and y gradient factor by size =%
      if defined -D echo c%%B=!c%%B!
      for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
        set /a "negX=^!^!(1<<31&%%x), absX=%%x*(1-2*negX), adj=((-%%y+4)*18/(s%%B*2+1)-absX*9/(s%%B*2+1))*6/17
        if !adj! gtr 5 set "adj=5"
				set /a "c=c%%B+adj*6"
        if defined -D echo x=%%x y=%%y negX=!negX! absX=!absX! adj=!adj! c=!c!
        set /a "1/(100*(%%x-!priorX!-1)+%%y-!priorY!)" 2>nul && (
          %== New line ==%
          set "ball%%B=!ball%%B!" "%%~C !esc![38;5;!c!m!-X!"
        ) || (
    		  %== Line continuation ==%
          set "ball%%B=!ball%%B!!esc![38;5;!c!m!-X!"
        )
        set /a "priorX=%%x, priorY=%%y"
      )
    )
    if %-C% == 24 (  %== 24 bit color ==%
      set /a "r=!random!%%60+76, g=!random!%%60+76, b=!random!%%60+76"  %= randomly select starting point for each base color    =%
      set /a "rp=^!(!random!%%3), gp=^!rp*(!random!%%2), bp=^!(rp|gp)"  %= randomly select 1 primary base color (never adjusted) =%
      set /a "rc%%B=r, rc%%B-=^!rp*r/(!random!%%3)" 2>nul   %= randomly adjust red base value by none, half or all unless red is primary =%
      set /a "gc%%B=g, gc%%B-=^!gp*g/(!random!%%3)" 2>nul               %= randomly adjust green base value the same way         =%
      set /a "bc%%B=b, bc%%B-=^!bp*b/(!random!%%3)" 2>nul               %= randomly adjust  blue base value the same way         =%
      set /a "xg=15*4/s%%B, yg=30*4/s%%B"                               %= adjust x and y gradient factor by size                =%
      if defined -D (      
        echo r=!r! g=!g! b=!b!
        echo rp=!rp! gp=!gp! bp=!bp!
        echo rc=!rc%%B! gc=!gc%%B! bc=!bc%%B!
        echo(
      )
      for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
        set /a "negX=^!^!(1<<31&%%x), absX=%%x*(1-2*negX), adj=-%%y*yg-absX*xg, r=rc%%B+adj, g=gc%%B+adj, b=bc%%B+adj"
        set /a "1/(100*(%%x-!priorX!-1)+%%y-!priorY!)" 2>nul && (
          %== New line ==%
          set "ball%%B=!ball%%B!" "%%~C !esc![38;2;!r!;!g!;!b!m!-X!"
        ) || (
    		  %== Line continuation ==%
          set "ball%%B=!ball%%B!!esc![38;2;!r!;!g!;!b!m!-X!"
        )
        set /a "priorX=%%x, priorY=%%y"
      )
    )
    set "ball%%B=!ball%%B:~2!""
  ) else (        %===== No Color Gradient =====%
    if %-C% ==  4 (  %==  4 bit color ==%
      set /a "c%%B=31+!random!%%7 + 60*(!random!%%2)"
    )
    if %-C% ==  8 (  %==  8 bit color ==%
      set /a "c%%B=!random!%%255+1"
      set "c%%B=38;5;!c%%B!"
    )
    if %-C% == 24 (  %== 24 bit color ==%
      set /a "r=!random!%%240+16, g=!random!%%240+16, b=!random!%%240+16"
      set "c%%B=38;2;!r!;!g!;!b!"
    )
    for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
      set /a "1/(100*(%%x-!priorX!-1)+%%y-!priorY!)" 2>nul && (
        %== New line ==%
        set "ball%%B=!ball%%B!" "%%~C !-X!"
      ) || (
  		  %== Line continuation ==%
        set "ball%%B=!ball%%B!!-X!"
      )
      set /a "priorX=%%x, priorY=%%y"
		)
    for /f "tokens=1,2*" %%a in ("!ball%%B:~2!") do set "ball%%B=%%a %%b !esc![!c%%B!m%%c""
  )
  if not defined size!s%%B! (
    set "screen0="
    %plot% !r%%B! !b%%B! ballEraser!s%%B! 0
    %plot% !r%%B! !b%%B! ball%%B 0
    call :StrLen screen0 size!s%%B!
  )
  set /a size+=size!s%%B!
  if !size! gtr 8175 set /a "screenCnt+=1, si%%B=screenCnt, size=size!s%%B!"
)

if defined -D (
  if defined -E for %%S in (2 3 4) do echo ballEraser%%S=!ballEraser%%S:%esc%=ESC!
  for /l %%B in (0 1 %ballCnt%) do echo ball%%B=!ball%%B:%esc%=ESC!
  set maxPixelLen
  set maxBallLen
  set size
  set screenCnt
  pause
  cls
)

:: Relaunch to run with input redirected to No
"%~f0" RUN <"%~f0.no"

:RUN
:: Hide the cursor
echo !esc![?25l

:: Run the main loop in new process with input redirected to Yes
cmd /c "%~f0" LOOP <"%~f0.yes"

:: Reset colors and show the cursor
echo !esc![0m!esc![?25h
cls

exit /b

:LOOP
setlocal enableDelayedExpansion
for /l %%# in () do (

  %initDelay% 

  for /l %%n in (0 1 %screenCnt%) do set "screen%%n="

  for /l %%b in (0,1,%ballCnt%) do (

    %erase%

    set /a "x%%b+=i%%b, y%%b+=j%%b"
    
    if !x%%b! geq !r%%b! set /a "x%%b=r%%b, i%%b*=-1"
    if !y%%b! geq !b%%b! set /a "y%%b=b%%b, j%%b*=-1"
    if !x%%b! leq !s%%b! set /a "x%%b=s%%b, i%%b*=-1"
    if !y%%b! leq !s%%b! set /a "y%%b=s%%b, j%%b*=-1"
    %plot% !x%%b! !y%%b! ball%%b !si%%b!
  )

  %openDelay%
  %cls%
  for /l %%n in (0 1 %screenCnt%) do echo !screen%%n!!esc!A
  %pause%
  %closeDelay%
)

:StrLen  string len                -- returns the length of a string
::                 -- string [in]  - variable name containing the string being measured for length
::                 -- len    [out] - variable to be used to return the string length
:: Many thanks to 'sowgtsoi', but also 'jeb' and 'amel27' dostips forum users helped making this short and efficient
:$created 20081122 :$changed 20101116 :$categories StringOperation
:$source http://www.dostips.com
(   SETLOCAL ENABLEDELAYEDEXPANSION
    set "str=A!%~1!"&rem keep the A up front to ensure we get the length and not the upper bound
                     rem it also avoids trouble in case of empty string
    set "len=0"
    for /L %%A in (12,-1,0) do (
        set /a "len|=1<<%%A"
        for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
    )
)
( ENDLOCAL & REM RETURN VALUES
    IF "%~2" NEQ "" SET /a %~2=%len%
)
EXIT /b
And here is an example of what the output looks like with the original CLS method with flicker.
BALLS.BAT /B 15 /S /G /W 200 /H 70 /C 24
Image

And here is the result when the /E option is added to use the erase mode instead of CLS.
BALLS.BAT /B 15 /S /G /W 200 /H 70 /C 24 /E
Image


Dave Benham

Re: Pure Batch Colored Bouncing ball animation

Posted: 04 Apr 2018 21:30
by IcarusLives
Oh my god, Dave. I am in awe. This is so totally amazing, I never thought this could be explored this much further; which excites me because it gives me a true understanding of how little I know and how much more I have to learn.

There are no words to express the respect and gratitude I have. I am literally jumping for joy over this animation! :mrgreen: :mrgreen: :mrgreen:

I love the idea of plotting the "last position" in black and avoiding cls. I have never considered such a thing!

Re: Pure Batch Colored Bouncing ball animation

Posted: 04 Apr 2018 22:08
by Squashman
Now someone make flying toasters!

Re: Pure Batch Colored Bouncing ball animation

Posted: 05 Apr 2018 10:58
by Compo
Dave, I have use the code above, (select all and copied to a new file named BALLS.BAT), and have tried it with both of the exact option sets you provided too, neither of them displays animations as above!

Here's the output from the first of your examples

Re: Pure Batch Colored Bouncing ball animation

Posted: 05 Apr 2018 11:52
by dbenham
I can't access that link from my office - lots of sites are blocked :-(

Compo - are you seeing a bunch of text? or are the colors limited to 16 (no smooth shading)?

If you are seeing text then either you are not running Win 10, or your console is configured for legacy mode. BALLS.BAT only runs on Windows 10 with the "legacy console" option switched OFF (within the console properties menu). Windows 10 added support for VT-100 (ANSI) escape sequences.

If you are getting moving images, but the colors are limited, then your Win 10 console does not support 24 bit color. That is the situation I have at work, where our Win 10 updates are out of synch with the world at large. The 24 bit color support was first introduced in September 2016 with Windows 10 Insider Build 14931. I should think most Win 10 installations would have 24 bit color support by now.


Dave Benham

Re: Pure Batch Colored Bouncing ball animation

Posted: 05 Apr 2018 12:11
by aGerman
The window in Compo's video doesn't look like Win10 Dave. Imho it's rather Win7-style.

Steffen

Re: Pure Batch Colored Bouncing ball animation

Posted: 05 Apr 2018 15:54
by IcarusLives
I was wondering if there is a way to detect collision between balls? Or possibly any object that is in it's path?

Lets just create a hypothetical

This code says, every 100 frames show an collide-able object (this could be another ball)

Code: Select all

set /a "frame+=1"
set /a "doAction=frame %% 100"

if !doAction! equ 0 (
	throw a random object into screen that the balls can literally bounce off of
)
But I do not simply want to store coordinates to be remembered as "collision spots". I would almost prefer it to recognize any character on the screen that is NOT a space (" ") and see it as something it can collide with, and then bounce off of.

I'm actually now not sure which of these two would be a faster method?

.....Am I even being clear enough about what I'm trying to say? :oops:

Re: Pure Batch Colored Bouncing ball animation

Posted: 06 Apr 2018 13:00
by dbenham
What you are asking absolutely makes sense - in fact, I was working on an alternate plotting strategy that can support your idea. However, bouncing off properly can be a bit tricky, because you have to figure out if you are changing X direction, Y direction, or both. And if both objects are moving, then it wont be very realistic if one object moves its full frame distance, and then the other bounces off that end location. In reality both objects should probably meet somewhere in the middle. For any type of realism, you also need to worry about angle of deflection and momentum transfer.

So the idea is to plot more like I did with SNAKE.BAT, where you have an array of strings with fixed length that represents the pixels of a horizontal line. The array index represents the Y position, and the position within the string represents the X position. Plotting a pixel (or contiguous horizontal line of pixels) involves replacing pixels in the middle of the line with the new pixel values.

SNAKE is monochrome, so it simply uses one character for each pixel.

But we want color, so each pixel is represented by Esc[{ColorValue}m{Pixel}, where {ColorValue} is one or more semicolon delimited numbers, depending on whether we are using 4, 8, or 24 bit color, and {Pixel} is the character used for that pixel. The important thing is each {ColorValue} must have a constant width. Thankfully, the VT100 escape sequence engine treats 009 the same as 9. So each number in the {ColorValue} must be left zero padded to a constant width (either width 2 or 3, depending on your color palette).

It is easy to modify the plot routine to use substring operations to set pixels in the middle of the string.

At the start of the program, CLS is followed by ESC7 to establish the home (top left corner) of the screen.

At the start of each frame, each row is initialized to a set of off pixels. Then each sprite is plotted, and when the entire frame has been built, The entire array is ECHOed to the screen. ESC8 is issued at the front of the first row to return to the home position before printing the frame, and the last line has ESCA appended to move up 1 line so the screen does not scroll up when the last ECHO newline is issued.

It should be fairly trivial to modify the plot routine some more to detect when you are overwriting already plotted pixels, so you can implement your collision detection. It might be useful to know what you are colliding with. This could be accomplished by extending the width of each pixel to store the numeric ID of the object each pixel represents. You can prefix each color with bogus color attribute(s) that represent the ID, and then be sure to reset color attributes before applying the true color. Something like ESC[{ID}mESC[0mESC[{ColorValue}m{Pixel}. The VT100 engine will simply ignore out of range numeric values. Just make sure the ID is left zero padded to a constant width of 2 or 3. You might be able to eliminate one of the ESC[ pairs - I'm not sure what the rules are.
If needed, you could use 2 or more numbers to help identify each object. Just be sure to delimit the numbers with semicolons.

Performance suffers as string lengths increase. So it is probably best to stick with 4 bit color, and limit the width of the screen. That will prevent the length of a row string from getting too long.

In my BALLS2.BAT below, I use 20 bytes for each 24 bit color pixel. So 24 bit color can go up to screen width of 400, though the font will need to be small to fit on the screen, and performance will likely be poor. Obviously the width must be reduced if IDs are added to the pixel definition.

The options for BALLS2.BAT are identical to my earlier BALLS.BAT, except the /E option has been eliminated.

Code: Select all

@echo off
if "%~1"=="RUN" goto :run
if "%~1"=="LOOP" goto :loop
setlocal enableDelayedExpansion

:::
:::BALLS2.BAT  [/Option [Value]]...
:::
:::  /B Balls  - Ball count     (default 5)
:::  /H Height - screen Height  (default 50)
:::  /W Width  - screen Width   (default 100)
:::  /T cSec   - min frame Time (default 0)
:::  /X Char   - piXel char     (default @)
:::  /C ColorBitCount - 4,8,24  (default 4)
:::  /G - color Gradient
:::  /S - Solid balls
:::  /P - Pause after each frame refresh
:::  /D - Debug mode
:::  /? - Help
:::

:: Define options
set "options= -?: -S: -P: -D: -G: -H:50 -W:100 -B:5 -C:4 -X:"@" -T:0 "

:: Set default option values
for %%O in (%options%) do for /f "tokens=1* delims=:" %%A in ("%%O") do set "%%A=%%~B"

:: Get options
:optionLoop
if not "%~1"=="" (
  set "arg=%~1"
  if "!arg:~0,1!" equ "/" set "arg=-!arg:~1!"
  for /f delims^=^ eol^= %%A in ("!arg!") do set "test=!options:*%%A:=! "
  if "!test!"=="!options! " (
      >&2 echo Error: Invalid option %~1. Use %~nx0 -? to get help.
      exit /b 1
  ) else if "!test:~0,1!"==" " (
      set "!arg!=1"
      if /i "!arg!" equ "-U" set "-z="
  ) else (
      set "!arg!=%~2"
      shift /1
  )
  shift /1
  goto :optionLoop
)

if defined -? (
  if not defined -D mode 50,25
  for /f "delims=: tokens=*" %%A in ('findstr "^:::" "%~f0"') do echo(%%A
  exit /b
)

:: Validate options and configure
set /a "ballCnt=4, ballCnt=%-B%-1" 2>nul
set /a "ht=50, ht=%-H%" 2>nul
set /a "wd=100, wd=%-W%" 2>nul
set /a "delay=0, delay=%-T%" 2>nul
set "validColorBits= 4 8 24 "
if "!validColorbits:%-C%=!" equ "!validColorBits!" set "-C=4"
if %ballCnt% lss 0 set "ballCnt=0"
if defined -X (set "-X=!-X:~0,1!") else set "-X=@"
if "!-X!"==" " set "-X=@"
if %ht% lss 20 set "ht=20"
if %wd% lss 20 set "wd=20"
if %ht% gtr 400 set "ht=400"
if %wd% gtr 400 set "wd=400"
for /F %%a in ('echo prompt $E^| cmd') do set "ESC=%%a"
if defined -D set "ESC=E"
if %-C%==4 (
  set "pixelLen=6"
  set "blankPixel=!esc![00m "
)
if %-C%==8 (
  set "pixelLen=12"
  set "blankPixel=!esc![38,5,000m "
)
if %-C%==24 (
  set "pixelLen=20"
  set "blankPixel=!esc![38,2,000;000;000m "
)

if not defined -D mode %wd%,%ht%
set /a wd-=1, ht-=1

set "blankLine="
for /l %%x in (0 1 !wd!) do set "blankLine=!blankLine!!blankPixel!"
if defined -D set blankLine

:: ---- Define macros ----
(set LF=^
%= Empty line generates linefeed =%
)
set ^"\n=^^^%LF%%LF%^%LF%%LF%^^"

:: plot  x  y  coordList
set plot=for %%# in (1 2) do if %%#==2 ( for /f "tokens=1-3" %%1 in ("^!args^!") do (%\n%
  for %%C in (^^!%%3^^!) do for /f "tokens=1-4" %%a in (%%C) do (%\n%
    set /a "x=%%a*pixelLen+%%1*pixelLen, y=%%b+%%2, z=x+%%d"%\n%
    for /f "tokens=1-3" %%x in ("^!x^! ^!y^! ^!z^!") do set "ln%%y=^!ln%%y:~0,%%x^!%%c^!ln%%y:~%%z^!"%\n%
  )%\n%
)) else set args=
if defined -D set plot

if %delay% gtr 0 (
  set "compute=1"
  set "t1=0"
  set "initDelay=if defined compute ("
  set openDelay=set compute=^)%\n%
    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"%\n%
    if ^^!tDiff^^! lss 0 set /a tDiff+=24*60*60*100%\n%
    if ^^!tDiff^^! geq !delay! (
  set "closeDelay=set compute=1&set t1=^!t2^!)"
) else (
  set "initDelay="
  set "openDelay="
  set "closeDelay="
)

if defined -P (set "pause=pause >nul <con") else set "pause="
:: ---- End macros ----

:: Build list of relative pixel coordinates for all possible circle sizes - 2,3,4
:: Each circleN is a string in the format '"x1 y1" "x2 y2" ... "xn yn"'
:: 0 is the center of the circle
for /l %%s in (2 1 4) do (
  %== Build CircleN ==%
  set "circle%%s="
  for /l %%y in (-%%s,1,%%s) do for /l %%x in (-%%s,1,%%s) do (
    set /a "s=%%x*%%x+%%y*%%y-%%s*%%s, _3div2=%%s/2"
    if defined -S ( %== solid ball ==%
      if !s! leq 1 (set circle%%s=!circle%%s! "%%x %%y")
    ) else (        %== outline ball ==%
      if !s! geq -%%s if !s! leq !_3div2! (set circle%%s=!circle%%s! "%%x %%y")
    )
  )
)

::  Build Yes and No files using local language
if not exist "%~f0.yes" (
  for /f "delims=(/ tokens=2,3" %%A in (
    'copy /-y nul "%~f0" ^<nul'
  ) do if not exist "%~f0.yes" (
    >"%~f0.yes" echo %%A
    >"%~f0.no"  echo %%B
  )
)

:: Initialize balls
for /l %%B in (0,1,%ballCnt%) do 2>nul (
  set /a "x%%B=!random! %% (wd-10) + 10"          %= x pos (col) =%
  set /a "y%%B=!random! %% (ht-10) + 10"          %= y pos (row) =%
  set /a "i%%B=(!random!%%3+1)*(1-!random!%%2*2)" %= x delta (speed & angle) =%
  set /a "j%%B=(!random!%%2+1)*(1-!random!%%2*2)" %= y delta (speed & angle) =%
  set /a "s%%B=!random! %% 3 + 2"                 %= size =%
  set /a "t%%B=s%%B + 1"                          %= screen top edge =%
  set /a "l%%B=s%%B + 1"                          %= screen left edge =%
  set /a "b%%B=ht - s%%B"                         %= screen bottom edge =%
  set /a "r%%B=wd - s%%B"                         %= screen right edge =%

  %== Build each ball "sprite" by adding color and pixel character to each coordinate pair in circleN ==%  
  set /a "priorX=100, priorY=100, len=0"
  set "ball%%B="
  if defined -G ( %======= Color Gradient ======%
    if %-C% ==  4 (  %==  4 bit color ==%
      set /a "c%%B=31+!random!%%7"
      for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
        set /a "c=c%%B+60*^!(-%%y&1<<31)+100000"
        set /a "1/(100*(%%x-priorX-1)+%%y-priorY)" && (
          %== New line ==%
          set "ball%%B=!ball%%B! !len!" "%%~C !esc![!c:~-2!m!-X!"
          set /a len=pixelLen
        ) || (
    		  %== Line continuation ==%
          set "ball%%B=!ball%%B!!esc![!c:~-2!m!-X!"
          set /a len+=pixelLen
        )
        set /a "priorX=%%x, priorY=%%y"
      )
    )
    if %-C% ==  8 (  %==  8 bit color ==%
      set /a "c%%B=16+!random!%%6*36+!random!%%6"   %= compute random base color              =%
      set "xg=12/(s%%B*2+1), yg=2*12/(s%%B*2+1)"    %= adjust x and y gradient factor by size =%
      if defined -D echo c%%B=!c%%B!
      for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
        set /a "negX=^!^!(1<<31&%%x), absX=%%x*(1-2*negX), adj=((-%%y+4)*18/(s%%B*2+1)-absX*9/(s%%B*2+1))*6/17
        if !adj! gtr 5 set "adj=5"
				set /a "c=c%%B+adj*6+100000"
        if defined -D echo x=%%x y=%%y negX=!negX! absX=!absX! adj=!adj! c=!c:~-cLen!
        set /a "1/(100*(%%x-priorX-1)+%%y-priorY)" && (
          %== New line ==%
          set "ball%%B=!ball%%B! !len!" "%%~C !esc![38;5;!c:~-3!m!-X!"
          set /a len=pixelLen
        ) || (
    		  %== Line continuation ==%
          set "ball%%B=!ball%%B!!esc![38;5;!c:~-3!m!-X!"
          set /a len+=pixelLen
        )
        set /a "priorX=%%x, priorY=%%y"
      )
    )
    if %-C% == 24 (  %== 24 bit color ==%
      set /a "r=!random!%%60+76, g=!random!%%60+76, b=!random!%%60+76"   %= randomly select starting point for each base color    =%
      set /a "rp=^!(!random!%%3), gp=^!rp*(!random!%%2), bp=^!(rp|gp)"   %= randomly select 1 primary base color (never adjusted) =%
      set /a "rc%%B=r, rc%%B-=^!rp*r/(!random!%%3)"  %= randomly adjust red base value by none, half or all unless red is primary =%
      set /a "gc%%B=g, gc%%B-=^!gp*g/(!random!%%3)"                      %= randomly adjust green base value the same way         =%
      set /a "bc%%B=b, bc%%B-=^!bp*b/(!random!%%3)"                      %= randomly adjust  blue base value the same way         =%
      set /a "xg=15*4/s%%B, yg=30*4/s%%B"                                %= adjust x and y gradient factor by size                =%
      if defined -D (      
        echo r=!r! g=!g! b=!b!
        echo rp=!rp! gp=!gp! bp=!bp!
        echo rc=!rc%%B! gc=!gc%%B! bc=!bc%%B!
        echo(
      )
      set /a "pr=pg=pb=0"  %== Clear prior values ==%
      for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
        set /a "negX=^!^!(1<<31&%%x), absX=%%x*(1-2*negX), adj=-%%y*yg-absX*xg+100000, r=rc%%B+adj, g=gc%%B+adj, b=bc%%B+adj"
        set /a "1/((1<<31&r-100000)|(1<<31&g-100000)|(1<<31&b-100000)), r=pr,g=pg,b=pb" || set /a "pr=r,pg=g,pb=b"  %== Use prior values if negative value detected ==%
        set /a "1/(100*(%%x-priorX-1)+%%y-priorY)" && (
          %== New line ==%
          set "ball%%B=!ball%%B! !len!" "%%~C !esc![38;2;!r:~-3!;!g:~-3!;!b:~-3!m!-X!"
          set /a len=pixelLen
        ) || (
    		  %== Line continuation ==%
          set "ball%%B=!ball%%B!!esc![38;2;!r:~-3!;!g:~-3!;!b:~-3!m!-X!"
          set /a len+=pixelLen
        )
        set /a "priorX=%%x, priorY=%%y"
      )
    )
  ) else (        %===== No Color Gradient =====%
    if %-C% ==  4 (  %==  4 bit color ==%
      set /a "c%%B=31+!random!%%7 + 60*(!random!%%2)+100000"
      set "c=!esc![!c%%B:~-2!m"
    )
    if %-C% ==  8 (  %==  8 bit color ==%
      set /a "c%%B=!random!%%255+1+100000"
      set "c=!esc![38;5;!c%%B:~-3!m"
    )
    if %-C% == 24 (  %== 24 bit color ==%
      set /a "r=!random!%%240+16+100000, g=!random!%%240+16+100000, b=!random!%%240+16+100000"
      set "c=!esc![38;2;!r:~-3!;!g:~-3!;!b:~-3!m"
    )
    for %%S in (!s%%B!) do for %%C in (!circle%%S!) do for /f "tokens=1,2" %%x in ("%%~C") do (
      set /a "1/(100*(%%x-priorX-1)+%%y-priorY)" && (
        %== New line ==%
        set "ball%%B=!ball%%B! !len!" "%%~C !c!!-X!"
        set /a len=pixelLen
      ) || (
  		  %== Line continuation ==%
        set "ball%%B=!ball%%B!!c!!-X!"
        set /a len+=pixelLen
      )
      set /a "priorX=%%x, priorY=%%y"
		)
  )
  set "ball%%B=!ball%%B:~4! !len!""
)

if defined -D (
  for /l %%B in (0 1 %ballCnt%) do echo ball%%B=!ball%%B:%esc%=ESC!
  pause
  cls
)

:: Relaunch to run with input redirected to No
"%~f0" RUN <"%~f0.no"

:RUN
:: Hide the cursor
echo !esc![?25l

:: Run the main loop in new process with input redirected to Yes
cmd /c "%~f0" LOOP <"%~f0.yes"

:: Reset colors and show the cursor
echo !esc![0m!esc![?25h
cls

exit /b

:LOOP
setlocal enableDelayedExpansion
for /l %%# in () do (

  %initDelay% 

  for /l %%n in (0 1 %ht%) do set "ln%%n=!blankLine!"
  set "ln%ht%=!ln%ht%!!esc!A"

  for /l %%b in (0,1,%ballCnt%) do (

    set /a "x%%b+=i%%b, y%%b+=j%%b"
    
    if !x%%b! geq !r%%b! set /a "x%%b=r%%b, i%%b*=-1"
    if !y%%b! geq !b%%b! set /a "y%%b=b%%b, j%%b*=-1"
    if !x%%b! leq !s%%b! set /a "x%%b=s%%b, i%%b*=-1"
    if !y%%b! leq !s%%b! set /a "y%%b=s%%b, j%%b*=-1"
    %plot% !x%%b! !y%%b! ball%%b
  )

  %openDelay%
  echo !esc!8!ln0!
  for /l %%n in (1 1 %ht%) do echo !ln%%n!
  %pause%
  %closeDelay%
)
Notice how there is no flicker, and no wonky erase shadow.
BALLS2.BAT /S /G /C 24 /B 10 /W 70 /H 35
Image


Dave Benham