'SNOWFALL.BAS
'============
'DESCRIPTION
'-----------
' A holiday screensaver for QBasic.
'AUTHOR
'------
' Dustinian Camburides
'PLATFORM
'--------
' Written in QB64.
' But designed to be QBasic/QuickBasic compatible.
' Although, I haven't tested QBasic / QuickBasic compatability yet.
'VERSION
'-------
'1.0, 2021-12-18: First working version.
'1.1, 2021-12-19: I was excited to keep working!
' Added page-flipping to reduce flicker!
' Added background snowflakes at a smaller speed and dimmer color!
' Set the formula to advance snowflakes to actually use the FALLSPEED constant.
' Also started the timer before calculating all the snowflakes to smooth out the animation.
'1.2, 2021-12-23: Updates with encouragement from the great folks at qb64.org/forum!
' Added a "settle" function to let "spiky" snow accumulation settle. (Thank you for the suggestion bplus!)
' Updated the delay so it doesn't break when it starts before midnight and ends after midnight. (Thank you for the suggestion Richard Frost!)
' Shifted to screen 9 to keep page-flipping, but at a higher resolution. (Thank you for the suggestion Richard Frost!)
'PLANNED ENHANCEMENTS
'--------------------
'Maybe next year I will add...
' - Actual, tested QBasic compatability (need to to a DosBox install and find a copy of QBasic).
' - A more complex data structure for snowflakes that can store both X and Y coordinates in a dynamic array of user-defined types... so it can support more than one snowflake per column... and mabe some drift back-and-forth in the X-axis.
'HOLIDAY MESSAGE
'---------------
'But for now, I'm happy that I have my first QB64 program that has animation. Happy Holidays!
'SUBS
'CONSTANTS
Const FALLSPEED
= 1 'Snow falls this many pixels per frame. Const COLUMNS
= 639 'The screen is 640 pixels across (0-639) in Screen 9. Const ROWS
= 349 'The screen is 350 pixels tall (0-349) in Screen 9. Const DELAY
= 0.04 'The number of seconds between snowflake recalculation / re-draw... QBasic can't detect less than 0.04 seconds... Const ODDS
= 0.7 'The % chance a snowflake will be added to a column that doesn't have a snowflake... anything over 1% will results in "waves" of snowflakes.
'VARIABLES
Dim intSnowflakes
(COLUMNS
) As Integer 'Array that holds the current Y-coordinate for a snowflake per column... this means there can only be one snowflake per column at any given time. Dim intBackgroundSnowflakes
(COLUMNS
) As Integer 'Same as above, but for the background. Dim intAccumulation
(COLUMNS
) As Integer 'Array that holds the current accumulated pixels of snow per column. Dim intBackgroundAccumulation
(COLUMNS
) As Integer ' Same as above, but for the background. Dim sngStart
As Single 'The timer at the start of the delay loop. Dim intBackgroundFrame
As Integer 'Whether the current frame will move the background snow... Dim intFullColumns
As Integer 'Used to track the number of columns that are full of snow...
'INITIALIZE VARIABLES
'For each column...
For intColumn
= 0 To COLUMNS
'Set all snowflakes to -1, indicating there is no snowflake in this column.
intSnowflakes(intColumn) = -1
intBackgroundSnowflakes(intColumn) = -1
'Set all accumulation to 0, indicating there is no accumulation in this column.
intAccumulation(intColumn) = 0
intBackgroundAccumulation(intColumn) = 0
intBackgroundFrame = 0
'INITIALIZE SCREEN
'Set the screen to mode 9 with an active page (where the cls, pset, and line commands occur) of 0 and a visible page (that the user sees) of 1.
'PROGRAM
'While no key has been pressed...
'Set the delay timer...
'Reset the number of full columns...
intFullColumns = 0
'Flip whether the background snow will move on or off...
intBackgroundFrame
= Not intBackgroundFrame
'For each column... calculate the changes to the snowfall...
For intColumn
= 0 To COLUMNS
'Settle the accumulation...
If intColumn
> 0 And intColumn
< COLUMNS
Then Call SettleAccumulation
(intAccumulation
(intColumn
- 1), intAccumulation
(intColumn
), intAccumulation
(intColumn
+ 1)) Call SettleAccumulation
(intBackgroundAccumulation
(intColumn
- 1), intBackgroundAccumulation
(intColumn
), intBackgroundAccumulation
(intColumn
+ 1)) 'If this is a background frame...
'Recalculate background snow...
Call CalculateSnowflakeColumn
(intBackgroundSnowflakes
(intColumn
), intBackgroundAccumulation
(intColumn
), ROWS
, FALLSPEED
, ODDS
) 'Ensure background accumulation keeps up with foreground accumulation to smooth out the accumulation...
If intAccumulation
(intColumn
) > intBackgroundAccumulation
(intColumn
) Then intBackgroundAccumulation
(intColumn
) = intAccumulation
(intColumn
) 'Draw the background snow first...
Call DrawSnowflakeColumn
(intBackgroundSnowflakes
(intColumn
), intBackgroundAccumulation
(intColumn
), intColumn
, ROWS
, 7) 'Recalculate the foreground snow...
Call CalculateSnowflakeColumn
(intSnowflakes
(intColumn
), intAccumulation
(intColumn
), ROWS
, FALLSPEED
, ODDS
) 'Draw the foreground snow next, on top of the background snow...
Call DrawSnowflakeColumn
(intSnowflakes
(intColumn
), intAccumulation
(intColumn
), intColumn
, ROWS
, 15) 'Track whether or not this column is full of snow (program will terminate when all columns are full)...
If intAccumulation
(intColumn
) = ROWS
Then intFullColumns
= intFullColumns
+ 1 'Copy the active page (where we just drew the snow) to the visible page...
'Clear the active page for the next frame...
'Wait for the delay to pass before starting over...
'If there is a snowflake in the column (i.e. any value > -1) then...
'If the snowflake has not fallen to the accumulation...
If (Snowflake
+ FallSpeed
) < (Rows
- Accumulation
) Then 'Advance the snowflake...
Snowflake = Snowflake + FallSpeed
'Eliminate the flake...
Snowflake = -1
'Add to the accumulation...
Accumulation = Accumulation + 1
'If accumulation hasn't filled up the column...
'Maybe add a flake...
Snowflake = 0
'If there is a snowflake in this column...
'Draw the snowflake...
PSet (Column
, Snowflake
), SnowColor
'If there is accumulation in this column...
'Draw the accumulation...
Line (Column
, Rows
)-(Column
, (Rows
- Accumulation
+ 1)), SnowColor
'If accumulation should settle left...
If ((Rnd * (Accumulation
- LeftAccumulation
)) > 2) Then 'Shift accumulation to the left
LeftAccumulation = LeftAccumulation + 1
Accumulation = Accumulation - 1
'If accumulation should settle right...
'Shift accumulation to the right
RightAccumulation = RightAccumulation + 1
Accumulation = Accumulation - 1