Positron8 - Star Raiders (ish) game using a small 18F device and a 128x64 LCD

Started by top204, Sep 27, 2024, 01:26 PM

Previous topic - Next topic

top204

On youtube, I happened across a video of a favourite game of mine on the Atari 800 computer back in the early 1980s named 'Star Raiders', and it stirred my nostalgia so much that I had a bit of time on my hands and started writing a game similar to it, but using a PIC18F chip running at 16MHz and a 128x64 KS0108 graphic LCD, instead of the lovely giant 'tank of a machine' that was the Atari 800. :-)

It simulates the perspective 3D stars movement and the alien ship movement so it gets smaller and larger while randomly moving on the display, and the ship's viewer with the fire mechanism, and the rear view of the stars. I doubt if I will complete it, but it was fun writing the 3D perspective of the stars so they also move in relationship with the direction buttons. It has been something I have wanted to write since the 1980s when I first came across computers, but never had the time or the incentive.

Below is a movie of the game code running in a simulator, and even in that it was running too fast, so I had to slow it down with differing tick event timings for each item, so on a real device it operates very smoothly and no flicker at all on the display, and only using a 16MHz PIC18F26K40 device and using only 4K of flash memory and 246 bytes of RAM, at the moment. It just goes to show what the compiler is capable of producing, using only the high level language itself, and no tweeks to the code using any assembler. The code also uses signed 16-bit variables (SWord), for all the item movements, including the stars in 3 arrays for X, Y and Z coordinates, so it shows how efficient they are as well in the Positron8 compiler.

I know it is a pointless task, but it was fun to write the code so far, and it triggered some good memories of first seeing the wonderful Atari 800 in action as a young-un. I wish I had kept my lovely Atari 800 machine, but I had to sell it after my injury:



JonW

That is cool!  Nice one.  Remember that Elite game that shocked the world back in the days, expert use of math and small code space. 

JonW

I know its not the same but here is a windows emulator for the Atari. Emulator

JonW

Les, How do you create and manipulate the sprites on the screen, buffer-wise, Moving the sprites and memory efficiency

I am just curious how you manipulate the screen from a retro game perspective?

top204

Because there are no complex sprites or a large amount of moving images, the program uses the compiler's standard KS0108 commands, except for the LCD_DrawLine procedure, and does not need any double buffering. The jerking and intermittent blocking in the movie display above is caused by the simulator and the screenshot grabber. On a real device, the movements are perfectly smooth, and the device is only runnning at 16MHz!

The unfinished 3D stars game template code listing is shown below:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate a 3D starfield on a 128x64 KS0108 graphic LCD.
' The starfield moves direction with button presses for left, right, up, and down
'
' Written by Les Johnson for the Positron8 BASIC compiler.
' https://sites.google.com/view/rosetta-tech/home
'
    Include "Device_Setups.inc"                                             ' Load the setups for the program
'
' Create constants for the starfield
'
    $define cMax_Stars   15                                                 ' The amount of stars in the starfield to display
    $define cMax_Stars_MinusOne $eval (cMax_Stars - 1)                      ' Maximum number of stars in the starfield minus 1
    $define cMax_Stars_Z 254                                                ' Macimum depth to prevent stars from being too far
    $define cMin_Stars_Z 40                                                 ' Minimum depth to prevent stars from being too close when they fist appear
'
' Create some constants for the alien ship
'
    $define cAlien1_Width   8
    $define cAlien1_Height  8
    $define cAlien1_Width_Minus1   $eval (cAlien1_Width - 1)
    $define cAlien1_Height_Minus1  $eval (cAlien1_Height - 1)
    $define cAlien1_Width_Div2     $eval (cAlien1_Width / 2)
    $define cAlien1_Height_Div2    $eval (cAlien1_Height / 2)
'
' Create some constants for the ship's blaster weapon
'
    $define cShip_Blast_Width  4
    $define cShip_Blast_Height 8
'
' Create some constants for the ship's viewer
'
    $define cViewer_Height 10
    $define cViewer_Width  10
'
' Create a flash memory table for the alien's smallest ship
'
    Dim Alien1_Ship3_Data As Flash8 = {0, 0, 0, 1, 1, 0, 0, 0,
                                       0, 1, 1, 1, 1, 1, 1, 0,
                                       1, 1, 1, 1, 1, 1, 1, 1,
                                       1, 1, 1, 1, 1, 1, 1, 1,
                                       1, 1, 1, 1, 1, 1, 1, 1,
                                       1, 1, 1, 0, 0, 1, 1, 1,
                                       1, 1, 0, 0, 0, 0, 1, 1,
                                       0, 0, 0, 0, 0, 0, 0, 0}
'
' Create a flash memory table for the alien's medium size ship
'
    Dim Alien1_Ship2_Data As Flash8 = {0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 1, 1, 0, 0, 0,
                                       0, 0, 1, 1, 1, 1, 0, 0,
                                       0, 1, 1, 1, 1, 1, 1, 0,
                                       0, 1, 1, 1, 1, 1, 1, 0,
                                       0, 1, 1, 0, 0, 1, 1, 0,
                                       0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 0, 0, 0, 0, 0}
'
' Create a flash memory table for the alien's larger ship
'
    Dim Alien1_Ship1_Data As Flash8 = {0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 1, 1, 0, 0, 0,
                                       0, 0, 1, 1, 1, 1, 0, 0,
                                       0, 0, 1, 0, 1, 1, 0, 0,
                                       0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 0, 0, 0, 0, 0,
                                       0, 0, 0, 0, 0, 0, 0, 0}
'
' Create a flash memory table for the ship's blaster weapon
'
    Dim Ship_Blast_Data As Flash8 = {0, 1, 1, 0,
                                     0, 1, 1, 0,
                                     0, 1, 1, 0,
                                     0, 1, 1, 0,
                                     0, 1, 1, 0,
                                     0, 1, 1, 0,
                                     0, 1, 1, 0,
                                     0, 1, 1, 0}
'
' Create a flash memory table for the ship's viewer
'
    Dim Viewer_Data As Flash8 = {0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
                                 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
                                 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
                                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
                                 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
                                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
                                 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
                                 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}
'
' Create arrays to hold the stars X positions, Y positions, and Z positions
'
    Dim Global_wStars_X[cMax_Stars] As SWord
    Dim Global_wStars_Y[cMax_Stars] As SWord
    Dim Global_wStars_Z[cMax_Stars] As Byte 'Word
    Dim Global_Viewer_Move_X        As SWord = 0
    Dim Global_Viewer_Move_Y        As SWord = 0
    Dim Global_Stars_Speed          As SByte = 1
'
' Create alien ship position and movement variables
'
    Dim Global_wAlien1_Move_X    As SWord = 1                                ' Initial movement
    Dim Global_wAlien1_Move_Y    As SWord = 1
    Dim Global_bAlien1_Move_Z    As Byte = 3                                 ' Alters the size of the alien1 ship (1 is largest ship, 2 is medium ship, 3 is smallest ship)
    Dim Global_wAlien1_Xpos      As SWord = -4 '0
    Dim Global_wAlien1_Ypos      As SWord = -4 '0
    Dim Global_tAlien1_Destroyed As Bit = False
    Dim Global_Alien1_Ship_Hit   As Bit = False
'
' Create ship blast variables
'
    Dim Global_wBlast_Xpos  As SWord = -2
    Dim Global_wBlast_Ypos  As SWord = 67
    Dim Global_tFire_Blaster As Bit = False

    Dim Global_tView        As Bit
    Dim Global_bStar_Tick   As Byte
    Dim Global_bAlien1_Tick As Byte
    Dim Global_bBlast_Tick  As Byte

    Symbol cStar_Tick_Max       = 100
    Symbol cAlien1_Tick_Max     = 200
    Symbol cBlast_Tick_Max      = 30
    Symbol cViewer_Tick_Max     = (cStar_Tick_Max - 1)

'------------------------------------------------------------------------------------------
' The main program starts here
'
Main:
    Setup()                                                                 ' Setup the program
    Do
        Process_Buttons()                                                   ' Process the buttons

        If Global_tView = True Then                                         ' Is the ship's view forward?
            '
            ' Move the ship's blaster weapon
            '
            If Global_bBlast_Tick >= cBlast_Tick_Max Then                   ' Yes. So is it time to move the ship's blaster image?
                If Global_tFire_Blaster = True Then                         ' Yes. So is the blaster image ready for moving?
                    Move_Blaster()                                          ' Yes. So move the blaster image
                EndIf
                Global_bBlast_Tick = 0                                      ' Reset its tick counter
            EndIf
        Else
            Global_tFire_Blaster = False
        EndIf
        '
        ' Move the starfield
        '
        If Global_bStar_Tick >= cStar_Tick_Max Then                         ' Is it time to move the starfield?
            Update_Stars(Global_tView)                                      ' Yes. So move the stars in the direction depending on the value of Global_tView
            Global_bStar_Tick = 0                                           ' Reset its tick counter
        EndIf

        If Global_tView = True Then                                         ' Is the view forward?
            '
            ' Move the alien craft
            '
            If Global_bAlien1_Tick >= cAlien1_Tick_Max Then                 ' Yes. Is it time to move the alien ship
                If Global_Alien1_Ship_Hit = False Then
                    Alien1_Move_Ship()                                      ' Yes. So move the alien ship
                EndIf
                Global_bAlien1_Tick = 0                                     ' Reset its tick counter
            EndIf
            Update_Vwer_Movement()                                          ' Alter the alien's direction based upon the viewer's direction

            If Global_bStar_Tick = 0 Then                                   ' Is it time to update the ship's viewer on the LCD
                Draw_Ship_Viewer()                                          ' Yes. So draw the ship's viewer graphic
            EndIf
        EndIf

        Inc Global_bStar_Tick
        Inc Global_bAlien1_Tick
        Inc Global_bBlast_Tick
    Loop

'------------------------------------------------------------------------------------
' Draw the alien ship from its pixel data table
' Input     : Global_wAlien1_Xpos holds the alien's X position
'           : Global_wAlien1_Ypos holds the alien's Y position
' Output    : None
' Notes     : None
'
Proc Alien1_Draw_Ship()
Global Dim SR_bIndex   As Byte Access Shared
Global Dim SR_wAddress As Word Access Shared
Global Dim SR_bTemp    As Byte Access Shared
Global Dim SR_bBit_X   As Byte Access Shared
Global Dim SR_bBit_Y   As Byte Access Shared
    Dim bScreen_X As Byte
    Dim bScreen_Y As Byte
    Symbol cHght_X_Wdth = ((cAlien1_Height * cAlien1_Width) - 1)
'
' Change the size of the alien ship
'
    If Global_bAlien1_Move_Z = 1 Then                                           ' Is the alien ship's random Z value set to 1?
        SR_wAddress = AddressOf(Alien1_Ship1_Data)                              ' Yes. So use the smallest image for the alien ship
    ElseIf Global_bAlien1_Move_Z = 2 Then                                       ' Is the alien ship's random Z value set to 2?
        SR_wAddress = AddressOf(Alien1_Ship2_Data)                              ' Yes. So use the medium image for the alien ship
    Else                                                                        ' Otherwise... The alien ship's random Z value is set to 0 or 3
        SR_wAddress = AddressOf(Alien1_Ship3_Data)                              ' So.... Use the largest image for the alien ship
    EndIf
'
' Convert to screen coordinates
'
    bScreen_X = Global_wAlien1_Xpos + cScreen_Width_Div2
    bScreen_Y = Global_wAlien1_Ypos + cScreen_Height_Div2
    'HRsoutLn SDec Global_wAlien1_Xpos, ",", SDec Global_wAlien1_Ypos
'
' Draw the alien ship from the flash memory table
'
    SR_bIndex = 0
    Repeat
        SR_bTemp = SR_bIndex // cAlien1_Width
        SR_bBit_X = bScreen_X + SR_bTemp

        SR_bTemp = SR_bIndex / cAlien1_Height
        SR_bBit_Y = bScreen_Y + SR_bTemp

        If SR_bBit_X >= 0 Then
            If SR_bBit_X < cScreen_Width Then
                If SR_bBit_Y >= 0 Then
                    If SR_bBit_Y < cScreen_Height Then
                        SR_bTemp.0 = cPtr8(SR_wAddress++)
                        If SR_bTemp.0 = 1 Then                                  ' Is a pixel to be set?
                            LCD_Inline_Set_Pixel(SR_bBit_X, SR_bBit_Y)          ' Yes. So set a pixel to draw the alien ship
                        EndIf
                    EndIf
                EndIf
            EndIf
        EndIf

        Inc SR_bIndex
    Until SR_bIndex > cHght_X_Wdth
EndProc

'------------------------------------------------------------------------------------
' Move the alien ship smoothly
' Input     :
' Output    : None
' Notes     : None
'
Proc Alien1_Move_Ship()
Global Dim SR_bTemp As Byte Access Shared

    If Global_Alien1_Ship_Hit = True Then                                   ' Has the alien ship been hit by the blaster?
        'If Alien1_Destroy_Ship() = True Then                               ' Yes. So create the blast particles, and have they finished moving out?
            Global_Alien1_Ship_Hit = False                                  ' Yes. So set the Global_Alien1_Ship_Hit flash to false
        'EndIf
    Else
        '
        ' Generate pseudo random movements of the alien ship
        '
        SR_bTemp = RandValue8(20)                                           ' \
        If SR_bTemp = 0 Then                                                ' / Random chance to change direction
            Global_wAlien1_Move_X = RandValue8(3) - 1                       ' Random value: -1, 0, or 1
            Global_wAlien1_Move_Y = RandValue8(3) - 1                       ' Random value: -1, 0, or 1
            Global_bAlien1_Move_Z = RandValue8(3)                           ' Random value from 0 to 3
        EndIf

        Alien1_Erase_Ship()                                                 ' Erase the previous position of the alien ship
        '
        ' Update the alien ship X and Y position with current movement
        '
        Global_wAlien1_Xpos = Global_wAlien1_Xpos + Global_wAlien1_Move_X
        Global_wAlien1_Ypos = Global_wAlien1_Ypos + Global_wAlien1_Move_Y
        '
        ' Ensure the ship wraps around the screen edges
        '
        If Global_wAlien1_Xpos < -cScreen_Width_Div2 Then
            Global_wAlien1_Xpos = cScreen_Width_Div2
        ElseIf Global_wAlien1_Xpos > cScreen_Width_Div2 Then
            Global_wAlien1_Xpos = -cScreen_Width_Div2
        EndIf
        If Global_wAlien1_Ypos < -cScreen_Height_Div2 Then
            Global_wAlien1_Ypos = cScreen_Height_Div2
        ElseIf Global_wAlien1_Ypos > cScreen_Height_Div2 Then
            Global_wAlien1_Ypos = -cScreen_Height_Div2
        EndIf

        Alien1_Draw_Ship()                                                  ' Draw the alien ship at its new position
    EndIf
EndProc

'------------------------------------------------------------------------------------
' Erase the alien ship
' Input     : None
' Output    : None
' Notes     : None
'
Proc Alien1_Erase_Ship()
Global Dim SR_bIndex As Byte Access Shared
Global Dim SR_bTemp  As Byte Access Shared
Global Dim SR_bBit_X As Byte Access Shared
Global Dim SR_bBit_Y As Byte Access Shared
    Dim bScreen_X As Byte
    Dim bScreen_Y As Byte

    Symbol cHght_X_Wdth = ((cAlien1_Height * cAlien1_Width) - 1)
'
' Convert to screen coordinates (2D)
'
    bScreen_X = Global_wAlien1_Xpos + cScreen_Width_Div2
    bScreen_Y = Global_wAlien1_Ypos + cScreen_Height_Div2
'
' Erase the alien ship by clearing pixels
'
    For SR_bIndex = cHght_X_Wdth DownTo 0
        SR_bTemp = SR_bIndex // cAlien1_Width
        SR_bBit_X = bScreen_X + SR_bTemp

        SR_bTemp = SR_bIndex / cAlien1_Width
        SR_bBit_Y = bScreen_Y + SR_bTemp
        If SR_bBit_X >= 0 Then
            If SR_bBit_X < cScreen_Width Then
                If SR_bBit_Y >= 0 Then
                    If SR_bBit_Y < cScreen_Height Then
                        LCD_Inline_Clear_Pixel(SR_bBit_X, SR_bBit_Y)            ' Clear a pixel to erase the alien ship
                    EndIf
                EndIf
            EndIf
        EndIf
    Next
EndProc

'------------------------------------------------------------------------------------
' Update viewer movement and make the alien ship move in the opposite direction
' Input     : Global_Viewer_Move_X and Global_Viewer_Move_Y represent the viewer's movement direction, based on button input
' Output    : None
' Notes     : None
'
Proc Update_Vwer_Movement()
    If Global_Viewer_Move_X = 1 Then
        Global_wAlien1_Move_X = -1                                              ' Move the alien ship in the opposite direction
    ElseIf Global_Viewer_Move_X = -1 Then
        Global_wAlien1_Move_X = 1
    EndIf

    If Global_Viewer_Move_Y = 1 Then
        Global_wAlien1_Move_Y = -1
    ElseIf Global_Viewer_Move_Y = -1 Then
        Global_wAlien1_Move_Y = 1
    EndIf
EndProc

'------------------------------------------------------------------------------------
' Draw the ship's screen viewer
' Input     : None
' Output    : None
' Notes     : None
'
Proc Draw_Ship_Viewer()
Global Dim SR_wAddress As Word Access Shared
Global Dim SR_bBit_X   As Byte Access Shared
Global Dim SR_bBit_Y   As Byte Access Shared

    Symbol Left_Pos = cScreen_Width_Div2 - 5
    Symbol Up_Pos   = cScreen_Height_Div2 - 5

    SR_wAddress = AddressOf(Viewer_Data)
'
' Draw the ship's screen viewer based on its pixel data
'
    For SR_bBit_Y = Up_Pos To Up_Pos + 9
        For SR_bBit_X = Left_Pos To Left_Pos + 9
            If cPtr8(SR_wAddress++) = 1 Then                        ' Is the pixel to be set?
                LCD_Inline_Set_Pixel(SR_bBit_X, SR_bBit_Y)          ' Yes. So set a pixel to draw the viewer
            EndIf
        Next
    Next
EndProc

'------------------------------------------------------------------------------------
' Move the ship's blaster
' Input     : Global variable Global_wBlast_Xpos holds the blast's X position
'           : Global Variable Global_wBlast_Ypos holds the blast's Y position
' Output    : None
' Notes     : None
'
Proc Move_Blaster()
    Dim wBlast_XPosMin As SByte
    Dim wBlast_XPosMax As SByte
    Dim wBlast_YPos As SByte '= Global_wBlast_Ypos                   ' Make a copy of the blaster's top pixel Y position

    Dim wAlien1_XPosMid As SByte
    Dim wAlien1_YPos As SByte

    Erase_Ship_Blaster()                                            ' Erase the previous position of the blast ship

    Dec Global_wBlast_Ypos
    If Global_wBlast_Ypos > -4 Then
        Draw_Ship_Blaster()                                         ' Draw the ship's blast
    Else
        Global_wBlast_Ypos = 67                                     ' Reset the blaster's initial Y position
        Global_tFire_Blaster = False
    EndIf
    wBlast_XPosMin = Global_wBlast_Xpos - 4
    wBlast_XPosMax = Global_wBlast_Xpos + 4
    wAlien1_XPosMid = Global_wAlien1_Xpos' - 2

    'HRSOutLn "AX=", SDec wAlien1_XPosMid, ". AY=", SDec Global_wAlien1_Ypos
    'HRSOutLn "BX=", SDec wBlast_XPosMin, ". BY=", SDec Global_wBlast_Ypos
'
' Check if the blaster has hit the alien ship
'
    Select Global_bAlien1_Move_Z                                    ' \ Is the alien ship medium or large sized?
        Case >=2                                                    ' /
            Select Global_wBlast_Ypos                               ' \ Yes. So is the blaster's Y position in the hit Y position of the alien1 ship?
                Case <4 '-4 To 4                                    ' /
                    Select wAlien1_XPosMid                          ' \ Yes. So is the blaster's X position within the width of the alien1 ship
                        Case wBlast_XPosMin To wBlast_XPosMax       ' /
                            HRSOutLn "***** Ship Hit"
                            Erase_Ship_Blaster()
                            Global_Alien1_Ship_Hit = True
                            Global_wBlast_Ypos = 67                 ' Reset the blaster's initial Y position
                            Global_tFire_Blaster = False
                    EndSelect
            EndSelect
    EndSelect
EndProc

'------------------------------------------------------------------------------------
' Draw the ship's blaster from its pixel data table
' Input     : Global variable Global_wBlast_Xpos holds the blast's X position
'           : Global Variable Global_wBlast_Ypos holds the blast's Y position
' Output    : None
' Notes     : None
'
Proc Draw_Ship_Blaster()
Global Dim SR_bIndex   As Byte Access Shared
Global Dim SR_wAddress As Word Access Shared
Global Dim SR_bTemp    As Byte Access Shared
Global Dim SR_bBit_X   As Byte Access Shared
Global Dim SR_bBit_Y   As Byte Access Shared
    Dim bScreen_X As Byte
    Dim bScreen_Y As Byte

    Symbol cHght_X_Wdth = ((cShip_Blast_Height * cShip_Blast_Width) - 1)

    SR_wAddress = AddressOf(Ship_Blast_Data)
'
' Convert to screen coordinates (2D)
'
    bScreen_X = Global_wBlast_Xpos + cScreen_Width_Div2
    bScreen_Y = Global_wBlast_Ypos + cScreen_Height_Div2
'
' Draw the ship's blast based on its pixel data
'
    SR_bIndex = 0
    Repeat
        SR_bTemp = SR_bIndex // cShip_Blast_Width
        SR_bBit_X = bScreen_X + SR_bTemp

        SR_bTemp = SR_bIndex / cShip_Blast_Height
        SR_bBit_Y = bScreen_Y + SR_bTemp

        If SR_bBit_X >= 0 Then
            If SR_bBit_X < cScreen_Width Then
                If SR_bBit_Y >= 0 Then
                    If SR_bBit_Y < cScreen_Height Then
                        SR_bTemp = cPtr8(SR_wAddress++)
                        If SR_bTemp = 1 Then
                            LCD_Inline_Set_Pixel(SR_bBit_X, SR_bBit_Y)              ' Set a pixel to draw the blast image
                        EndIf
                    EndIf
                EndIf
            EndIf
        EndIf
        Inc SR_bIndex
    Until SR_bIndex > cHght_X_Wdth
EndProc

'------------------------------------------------------------------------------------
' Erase the ship's blast
' Input     : Global variable Global_wBlast_Xpos holds the blast's X position
'           : Global Variable Global_wBlast_Ypos holds the blast's Y position
' Output    : None
' Notes     : None
'
Proc Erase_Ship_Blaster()
Global Dim SR_bIndex As Byte Access Shared
Global Dim SR_bTemp  As Byte Access Shared
Global Dim SR_bBit_X As Byte Access Shared
Global Dim SR_bBit_Y As Byte Access Shared
    Dim bScreen_X As Byte
    Dim bScreen_Y As Byte

    Symbol cHght_X_Wdth = ((cShip_Blast_Height * cShip_Blast_Width) - 1)
'
' Convert to screen coordinates
'
    bScreen_X = Global_wBlast_Xpos + cScreen_Width_Div2
    bScreen_Y = Global_wBlast_Ypos + cScreen_Height_Div2
'
' Erase the ship's blast by clearing pixels
'
    For SR_bIndex = cHght_X_Wdth DownTo 0
        SR_bTemp = SR_bIndex // cShip_Blast_Width
        SR_bBit_X = bScreen_X + SR_bTemp

        SR_bTemp = SR_bIndex / cShip_Blast_Width
        SR_bBit_Y = bScreen_Y + SR_bTemp
        If SR_bBit_X >= 0 Then
            If SR_bBit_X < cScreen_Width Then
                If SR_bBit_Y >= 0 Then
                    If SR_bBit_Y < cScreen_Height Then
                        LCD_Inline_Clear_Pixel(SR_bBit_X, SR_bBit_Y)                ' Clear a pixel to erase the alien ship
                    EndIf
                EndIf
            EndIf
        EndIf
    Next
EndProc

'------------------------------------------------------------------------------------------
' Move the pixels in a 3D effect towards the viewer or away from the viewer
' Moved in a direction by button presses for left, right, up, and down
' Input     : pView alters the view of the starfield, either coming towards or moving away
'             True is moving towards, and False is moving away
'           : Global variable: Global_Viewer_Move_X holds the X direction of the stars (altered by the buttons)
'           : Global variable: Global_Viewer_Move_Y holds the Y direction of the stars (altered by the buttons)
' Output    : None
' Notes     : None
'
Proc Update_Stars(pView As Bit)
Global Dim SR_bIndex As Byte Access Shared
    Dim wTemp_X      As SWord
    Dim wTemp_Y      As SWord
    Dim wTemp_Z      As Word
    Dim wProjected_X As Word
    Dim wProjected_Y As Word
    Dim wScreen_X    As wProjected_X
    Dim wScreen_Y    As wProjected_Y
    Dim wPrev_X      As Word
    Dim wPrev_Y      As Word
    Dim wPos_El_X    As SWord
    Dim wPos_El_Y    As SWord
    Dim wPos_El_Z    As SWord
    Dim wTemp        As wPos_El_X

    For SR_bIndex = cMax_Stars_MinusOne DownTo 0
        wTemp_X = Global_wStars_X[SR_bIndex]
        wTemp_Y = Global_wStars_Y[SR_bIndex]
        wTemp_Z = Global_wStars_Z[SR_bIndex]
        '
        ' Calculate the previous position for clearing a star's pixel
        '
        wTemp = wTemp_Z + 1
        wPrev_X = (wTemp_X * 64) / wTemp
        wPrev_X = wPrev_X + cScreen_Width_Div2

        wPrev_Y = (wTemp_Y * 64) / wTemp
        wPrev_Y = wPrev_Y + cScreen_Height_Div2
        LCD_Clear_Pixel(wPrev_X, wPrev_Y)                                               ' Erase a star pixel only if it is within screen boundaries
        '
        ' Move stars based on the direction (forward or backward)
        '
        If pView = True Then                                                            ' Is the view: Stars forward?
            wTemp_Z = wTemp_Z - Global_Stars_Speed                                      ' Yes. So move stars forward (reduce Z)
            If wTemp_Z <= cMin_Stars_Z Then                                             ' Reset when reaching the viewer
                Global_wStars_X[SR_bIndex] = RandValue(cScreen_Width) - cScreen_Width_Div2
                Global_wStars_Y[SR_bIndex] = RandValue(cScreen_Height) - cScreen_Height_Div2
                Global_wStars_Z[SR_bIndex] = cMax_Stars_Z
            Else
                Global_wStars_Z[SR_bIndex] = wTemp_Z
            EndIf
            '
            ' Update a star's position based on movement
            '
            Global_wStars_X[SR_bIndex] = Global_wStars_X[SR_bIndex] + Global_Viewer_Move_X
            Global_wStars_Y[SR_bIndex] = Global_wStars_Y[SR_bIndex] + Global_Viewer_Move_Y
        Else                                                                            ' Otherwise... The stars are moving away
            wTemp_Z = wTemp_Z + Global_Stars_Speed                                      ' So. Move stars backward (increase Z)
            If wTemp_Z >= cMax_Stars_Z Then                                             ' Reset when too far
                Global_wStars_X[SR_bIndex] = RandValue(cScreen_Width) - cScreen_Width_Div2
                Global_wStars_Y[SR_bIndex] = RandValue(cScreen_Height) - cScreen_Height_Div2
                Global_wStars_Z[SR_bIndex] = cMin_Stars_Z
            Else
                Global_wStars_Z[SR_bIndex] = wTemp_Z
            EndIf
            '
            ' Update a star's position based on movement
            '
            Global_wStars_X[SR_bIndex] = Global_wStars_X[SR_bIndex] - Global_Viewer_Move_X
            Global_wStars_Y[SR_bIndex] = Global_wStars_Y[SR_bIndex] - Global_Viewer_Move_Y
        EndIf

        wPos_El_X = Global_wStars_X[SR_bIndex]                                         ' Read and store a copy of the Global_wStars_X element
        wPos_El_Y = Global_wStars_Y[SR_bIndex]                                         ' Read and store a copy of the Global_wStars_Y element
        '
        ' Wrap around screen edges to prevent stars from disappearing
        '
        If wPos_El_X < -cScreen_Width_Div2 Then
            Global_wStars_X[SR_bIndex] = cScreen_Width_Div2
        EndIf
        If wPos_El_X > cScreen_Width_Div2 Then
            Global_wStars_X[SR_bIndex] = -cScreen_Width_Div2
        EndIf
        If wPos_El_Y < -cScreen_Height_Div2 Then
            Global_wStars_Y[SR_bIndex] = cScreen_Height_Div2
        EndIf
        If wPos_El_Y > cScreen_Height_Div2 Then
            Global_wStars_Y[SR_bIndex] = -cScreen_Height_Div2
        EndIf
        '
        ' Project new star positions based on updated depth
        '
        wPos_El_Z = Global_wStars_Z[SR_bIndex] + 1                                     ' Read and store a copy of the Global_wStars_Z element and increment it
        wProjected_X = (Global_wStars_X[SR_bIndex] * 64) / wPos_El_Z
        wProjected_Y = (Global_wStars_Y[SR_bIndex] * 64) / wPos_El_Z
        '
        ' Convert to screen coordinates
        '
        wScreen_X = wProjected_X + cScreen_Width_Div2
        wScreen_Y = wProjected_Y + cScreen_Height_Div2
        LCD_Set_Pixel(wScreen_X, wScreen_Y)                                             ' Draw a star pixel only if it is within screen boundaries
    Next
EndProc

'------------------------------------------------------------------------------------------
' Initialise the starfield pixel positions
' Input     : None
' Output    : None
' Notes     : Spreads star pixels randomly across the screen but not clustered
'
Proc Init_StarField()
Global Dim SR_bIndex As Byte Access Shared
    Symbol cZ_Bounds = ((cMax_Stars_Z - cMin_Stars_Z) + 1)

    For SR_bIndex = cMax_Stars_MinusOne DownTo 0
        Global_wStars_X[SR_bIndex] = RandValue8(cScreen_Width) - (cScreen_Width_Div2 / 2)   ' From -64 to +64
        Global_wStars_Y[SR_bIndex] = RandValue8(cScreen_Height ) - (cScreen_Height_Div2 / 2)' From -32 to +32
        Global_wStars_Z[SR_bIndex] = RandValue8(cZ_Bounds) + cMin_Stars_Z                   ' Avoid very low Z values to prevent clipping
    Next
EndProc

'------------------------------------------------------------------------------------------
' Setup the program
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    LCD_Init()                                                              ' Initialise the LCD
    Init_StarField()                                                        ' Initialise the star positions
    Global_tView = True                                                     ' Default to a forward view
    Global_bStar_Tick = 0
    Global_bAlien1_Tick = 0
    Global_bBlast_Tick = 0

    PinMode(Button_Left_Pin, Input_PullUp)                                  ' Button to move view left
    PinMode(Button_Right_Pin, Input_PullUp)                                 ' Button to move view right
    PinMode(Button_Up_Pin, Input_PullUp)                                    ' Button to move view up
    PinMode(Button_Down_Pin, Input_PullUp)                                  ' Button to move view down
    PinMode(Button_Rev_Pin, Input_PullUp)                                   ' Button to reverse the view
    PinMode(Button_SpeedInc_Pin, Input_PullUp)                              ' Button to increment the starfield speed
    PinMode(Button_SpeedDec_Pin, Input_PullUp)                              ' Button to decrement the starfield speed
    PinMode(Button_Fire_Pin, Input_PullUp)                                  ' Button to fire a blast
EndProc

'------------------------------------------------------------------------------------------
' Handle the button presses to move stars
' Input     : None
' Output    : Variable Global_Viewer_Move_Y holds the Y value for the stars
'           : Variable Global_Viewer_Move_X holds the X value for the stars
' Notes     : Also seeds the Random number generator with an incrementing value when a button is pressed
'
Proc Process_Buttons()
Static Dim tSeededUD         As Bit = False
Static Dim tSeededLR         As Bit = False
Static Dim tRev_Btn_Pressed  As Bit = False
Static Dim tSpdU_Btn_Pressed As Bit = False
Static Dim tSpdD_Btn_Pressed As Bit = False

    Global_wSeedValue = Global_wSeedValue + 24                                  ' Increment the Random seed value
    If Global_wSeedValue < 1000 Then                                            ' Is the seed value less than 1000?
        Global_wSeedValue = 1000                                                ' Yes. So make it 1000, so the Random generator does not see too low a value
    EndIf

    If Button_Up_Pin = 0 Then                                                   ' Is the Up button pressed?
        Global_Viewer_Move_Y = 1
        If tSeededUD = False Then                                               ' Has the Random value been seeded by an Up or Down button press?
            Seed Global_wSeedValue                                              ' No. So seed the Random value
            tSeededUD = True                                                    ' Indicate a seed has happened, so it will not seed again while the button is pressed
        EndIf

    ElseIf Button_Down_Pin = 0 Then                                             ' Is the Down button pressed?
        Global_Viewer_Move_Y = -1
        If tSeededUD = False Then                                               ' Has the Random value been seeded by an Up or Down button press?
            Seed Global_wSeedValue                                              ' No. So seed the Random value
            tSeededUD = True                                                    ' Indicate a seed has happened, so it will not seed again while the button is pressed
        EndIf

    Else                                                                        ' Otherwise. The Up or Down buttons are not pressed...
        Global_Viewer_Move_Y = 0                                                ' So... Load the Global_Viewer_Move_Y variable with 0
        tSeededUD = False                                                       ' Indicate a seed has not happened, so it will seed again when an Up or Down button is pressed
    EndIf

    If Button_Left_Pin = 0 Then                                                 ' Is the Left button pressed?
        Global_Viewer_Move_X = 1
        If tSeededLR = False Then                                               ' Has the Random value been seeded by a Left or Right button press?
            Seed Global_wSeedValue                                              ' No. So seed the Random value
            tSeededLR = True                                                    ' Indicate a seed has happened, so it will not seed again while the button is pressed
        EndIf

    ElseIf Button_Right_Pin = 0 Then                                            ' Is the Right button pressed?
        Global_Viewer_Move_X = -1
        If tSeededLR = False Then                                               ' Has the Random value been seeded by a Left or Right button press?
            Seed Global_wSeedValue                                              ' No. So seed the Random value
            tSeededLR = True                                                    ' Indicate a seed has happened, so it will not seed again while the button is pressed
        EndIf

    Else                                                                        ' Otherwise. The Left or Right buttons are not pressed...
        Global_Viewer_Move_X = 0                                                ' So... Load the Global_Viewer_Move_X variable with 0
        tSeededLR = False                                                       ' Indicate a seed has not happened, so it will seed again when a Left or Right button is pressed
    EndIf
'
' Check the reverse view pin
'
    If Button_Rev_Pin = 0 Then                                                  ' Is the Reverse View button pressed?
        If tRev_Btn_Pressed = False Then                                        ' Yes. So is the button pressed flag false?
            Global_tView = ~Global_tView                                        ' Yes. So alternate the view of the starfield
            tRev_Btn_Pressed = True                                             ' Indicate the button has been pressed, so it will not reverse again, until released and pressed again
            LCD_Clear()                                                         ' Clear the LCD's display
            Seed Global_wSeedValue                                              ' Seed the Random value
            Init_StarField()                                                    ' Re-initialise the starfield
        EndIf
    Else                                                                        ' Otherwise... The Reverse View button is not pressed
        tRev_Btn_Pressed = False                                                ' So set the tRev_Btn_Pressed flag to false, so it will detact another button press
    EndIf
'
' Check the stars increase and decrease of speed pins
'
    If Button_SpeedInc_Pin = 0 Then                                             ' Is the Increment speed button pressed?
        If tSpdU_Btn_Pressed = False Then                                       ' Yes. So is the button pressed flag false?
           tSpdU_Btn_Pressed = True                                             ' Indicate the button has been pressed, so it will not trigger again, until released and pressed again
            Inc Global_Stars_Speed
            If Global_Stars_Speed > 5 Then Global_Stars_Speed = 5
            'HRSOutLn Dec Global_Stars_Speed
        EndIf
    Else                                                                        ' Otherwise... The Speed Increse button is not pressed
        tSpdU_Btn_Pressed = False                                               ' So set the tSpdU_Btn_Pressed flag to false, so it will detact another button press
    EndIf

    If Button_SpeedDec_Pin = 0 Then                                             ' Is the Increment speed button pressed?
        If tSpdD_Btn_Pressed = False Then                                       ' Yes. So is the button pressed flag false?
           tSpdD_Btn_Pressed = True                                             ' Indicate the button has been pressed, so it will not trigger again, until released and pressed again
            Dec Global_Stars_Speed
            If Global_Stars_Speed.SByte <= 0 Then Global_Stars_Speed = 0
            'HRSOutLn Dec Global_Stars_Speed
        EndIf
    Else                                                                        ' Otherwise... The Speed Increase button is not pressed
        tSpdD_Btn_Pressed = False                                               ' So set the tSpdU_Btn_Pressed flag to false, so it will detact another button press
    EndIf
'
' Check the fire blaster pin
'
    If Button_Fire_Pin = 0 Then
        If Global_tFire_Blaster = False Then                                    ' Yes. So is the button pressed flag false?
           Global_tFire_Blaster = True                                          ' Yes. So indicate the button has been pressed, so it will not trigger again
        EndIf
    EndIf
EndProc

And below, is the code listing for the "Device_Setups.inc "file it uses. I created this file to remove some of the commonly used bits and pieces from the main code listing, and de-clutter it a bit:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Setups for the 3D star simulation
'
Declare Warnings = Off
    Device = 18F46Q10                                       ' Tell the compiler what device to compile for
    Declare Xtal = 16                                       ' Tell the compiler what frequency the device is operating at (in MHz)
    Declare Create_Coff = True
    Declare Float_Display_Type = Fast                       ' Use the compiler's faster floating point display routine

    Declare Auto_Heap_Arrays = On                           ' Make all arrays "Heap" types, so they always get placed after standard variables
    Declare Auto_Heap_Strings = On                          ' Make all Strings "Heap" types, so they always get placed after standard variables
    Declare Auto_Variable_Bank_Cross = On                   ' Make sure all multi-byte variables remain within a single RAM bank
'
' Setup the KS0108 Graphic LCD
'
    Declare LCD_DTPort = PORTD
    Declare LCD_RSPin = PORTC.1
    Declare LCD_ENPin = PORTE.0
    Declare LCD_RWPin = PORTC.0
    Declare LCD_CS1Pin = PORTE.1
    Declare LCD_CS2Pin = PORTE.2
    Declare LCD_Type = KS0108
    Declare Internal_Font = On
    Declare Font_Addr = 0
'
' Setup USART1
'
    Declare Hserial_Baud = 9600
    Declare HRSOut1_Pin = PORTC.6

$define True 1
$define False 0
'
' Setup LCD screen constants
'
    $define cScreen_Width  128
    $define cScreen_Height 64
    $define cScreen_Width_Div2  $eval (cScreen_Width / 2)
    $define cScreen_Height_Div2 $eval (cScreen_Height / 2)
'
' Button definitions
'
$define Button_Fire_Pin  PORTA.7                            ' Button to fire a blast
$define Button_Left_Pin  PORTA.0                            ' Button to move view left
$define Button_Right_Pin PORTA.1                            ' Button to move view right
$define Button_Up_Pin    PORTA.2                            ' Button to move view up
$define Button_Down_Pin  PORTA.3                            ' Button to move view down
$define Button_Rev_Pin   PORTA.4                            ' Button to reverse the view
$define Button_SpeedInc_Pin PORTA.5                         ' Button to increment the starfield speed
$define Button_SpeedDec_Pin PORTA.6                         ' Button to decrement the starfield speed

    Dim Global_wSeedValue As Word = 0
    Dim Global_wPixel_Xpos As SWord
    Dim Global_wPixel_Ypos As SWord
    Dim Global_bPixel_Ypos As Global_wPixel_Ypos.SByte0

'------------------------------------------------------------------------------------------------------------
' Draw or erase a line using Bresenham's algorithm
' Input     : pXStart holds the start X position
'           : pYStart holds the start Y position
'           : pXEnd holds the end X position
'           : pYEnd holds the end Y position
'           : pCol holds the colour of the line (1 or 0 for a monochrome LCD)
' Output    : None
' Notes     : None
'
Proc LCD_DrawLine(pXStart As SWord, pYStart As SWord, pXEnd As SWord, pYEnd As SWord, pCol As Bit)
    Dim wDX    As SWord
    Dim wDY    As SWord
    Dim wSX    As SWord
    Dim wSY    As SWord
    Dim wErr   As SWord
    Dim wErrX2 As SWord

    wDX = Abs(pXEnd - pXStart)
    wDY = Abs(pYEnd - pYStart)

    wSX = -1
    If pXStart < pXEnd Then
        wSX = 1
    EndIf

    wSY = -1
    If pYStart < pYEnd Then
        wSY = 1
    EndIf

    wErr = wDX - wDY
    Do
        LCD_Draw_Pixel(pXStart, pYStart, pCol)
        If pXStart = pXEnd Then
            If pYStart = pYEnd Then
                Break
            EndIf
        EndIf
        wErrX2 = wErr * 2
        If wErrX2 > -wDY Then
            wErr = wErr - wDY
            pXStart = pXStart + wSX
        EndIf
        If wErrX2 < wDX Then
            wErr = wErr + wDX
            pYStart = pYStart + wSY
        EndIf
    Loop
EndProc

'------------------------------------------------------------------------------------------
' Initialise the LCD
' Input     : None
' Output    : None
' Notes     : None
'
Proc LCD_Init()
    LCD_Clear()
EndProc

'------------------------------------------------------------------------------------------------------------
' Clear the LCD's display
' Input     : None
' Output    : None
' Notes     : None
'
Proc LCD_Clear()
    Cls
EndProc

'------------------------------------------------------------------------------------------------------------
' Draw or erase a pixel on the LCD
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
'           : pCol holds the colour of the pixel (1 or 0 for a monochrome LCD)
' Output    : None
' Notes     : None
'
Proc LCD_Draw_Pixel(pXpos As SWord, pYpos As SByte, pCol As Bit)
    If pXpos >= 0 Then
        If pXpos < cScreen_Width Then
            If pYpos >= 0 Then
                If pYpos < cScreen_Height Then
                    If pCol = True Then
                        Plot pYpos, pXpos
                    Else
                        UnPlot pYpos, pXpos
                    EndIf
                EndIf
            EndIf
        EndIf
    EndIf
EndProc

'------------------------------------------------------------------------------------------------------------
' Draw a pixel on the LCD
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
' Output    : None
' Notes     : None
'
Proc LCD_Set_Pixel(pXpos As Global_wPixel_Xpos, pYpos As Global_bPixel_Ypos)
    If pXpos >= 0 Then
        If pXpos < cScreen_Width Then
            If pYpos >= 0 Then
                If pYpos < cScreen_Height Then
                    Plot pYpos, pXpos
                EndIf
            EndIf
        EndIf
    EndIf
EndProc

'------------------------------------------------------------------------------------------------------------
' Erase a pixel on the LCD
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
' Output    : None
' Notes     : None
'
Proc LCD_Clear_Pixel(pXpos As Global_wPixel_Xpos, pYpos As Global_bPixel_Ypos)
    If pXpos >= 0 Then
        If pXpos < cScreen_Width Then
            If pYpos >= 0 Then
                If pYpos < cScreen_Height Then
                    UnPlot pYpos, pXpos
                EndIf
            EndIf
        EndIf
    EndIf
EndProc

'------------------------------------------------------------------------------------------------------------
' Draw a pixel on the LCD inline
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
' Output    : None
' Notes     : None
'
$define LCD_Inline_Set_Pixel(pXpos, pYpos) Plot pYpos, pXpos

'------------------------------------------------------------------------------------------------------------
' Erase a pixel on the LCD inline
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
' Output    : None
' Notes     : None
'
$define LCD_Inline_Clear_Pixel(pXpos, pYpos) UnPlot pYpos, pXpos

'------------------------------------------------------------------------------------------
' Erase a pixel on the LCD inline
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
' Output    : None
' Notes     : None
'
$define Clear_Star(pXpos, pYpos) LCD_Clear_Pixel(pXpos, pYpos)

'------------------------------------------------------------------------------------------
' Draw a pixel on the LCD inline
' Input     : pXpos holds the X position of the pixel
'           : pYpos holds the Y position of the pixel
' Output    : None
' Notes     : None
'
$define Draw_Star(pXpos, pYpos) LCD_Set_Pixel(pXpos, pYpos)

'------------------------------------------------------------------------------------------
' Create an 8-bit random value, with a limit parameter
' Input     : pLimit holds the 8-bit limit of the random value
' Output    : Returns the random 8-bit value
' Notes     : Never returns a 0
'
Proc RandValue(pLimit As Byte), WREG
    Result = Random
    Result = Result // pLimit
    'Result = Result + 1
EndProc
$define RandValue8(pLimit) RandValue(pLimit)

'------------------------------------------------------------------------------------------
' Create a 16-bit random value, with a limit parameter
' Input     : pLimit holds the 8-bit limit of the random value
' Output    : Returns the random 8-bit value
' Notes     : Never returns a 0
'
Proc RandValue16(pLimit As Word), Word
    Result = Random
    Result = Result // pLimit
    'Result = Result + 1
EndProc
'------------------------------------------------------------------------------------------------------------
' Create a 16-bit random value
' Input     : None
' Output    : Returns the random 16-bit value
' Notes     : Never returns a 0
'
Proc Rand(), Word
    Result = Random
    Result = Result // 32767
EndProc

JonW

Nice, ill take a good look over it. I am writing a new SSD1306 driver in C to test my C skills and started writing directly to the screen but moved to a RAM buffer so I can add random small sprites from ROM and quickly move them about and overwrite them, creating animated icons like batteries and Wifi signal strength meters.  Its alot more work than I thought mapping the page memory to the screen and then adding the usual drawbox, lines (Using bresenhams line algorothm), circle, add pixel, clr pixel, add sprite etc.  I have had to build fairly complex test routines to debug the sprite movement/placement.  I've been using an online tool for the sprite or backdrop bitmap called Piskel and then porting it to MS Paint to make it monochrome and then passing it to the LCD Assistant to build the ROM table.  I still need to add text yet!  It must take an age writing large comples functions for the compiler.. then debugging them: i feel your pain :-)

top204

Some of the compiler's functions are extremely complex, and when I look at them now, I think: "How the heck did I do that?". Fortunately, I got into the habit of always adding as many neat comments as possible, so I can go back to code years later and study it, and find out why I wrote the code, by reading the comments line by line. :-)

A while back, I wrote a set of procecures for an SSD1309 display that used 1K of RAM in the microcontroller as a display buffer (very, very similar to an SSD1306 display). I called the procedures Shad_Plot, Shad_Pixel, Shad_Line, Shad_Circle etc (short for 'Shadow'), for the procedures that interfaced with the microcontroller's 1K screen buffer and not the display directly, so the normal routines could also be used to interface directly with the LCD itself as well. Then created a loop that read the buffer RAM and wrote it to the display as fast as the device could possibly do it.

As you have found out, the SSD130x displays now call a line of bytes that make up the horizontal 8-pixels, "a page" instead of the original term "a line". It must make them sound more complex, when they are, pretty much, the same as the original graphic LCDs where a mask has to be made to extract a pixel position from a byte in the display's screen RAM, and rotates and Anding and Oring. :-) 

I still remember doing that back in, around, 2001 when I first got hold of a Samsung KS0108 128x64 display and had to work out how to interface to it, and how to get pixels from bytes etc... There were no other source codes I could look at, so I had to use the display's datasheet, and write the code in assembler. It took a couple of weeks, but it worked nicely, and once I had worked out how, it was one of those: "That was obvious" moments, that we all get once we understand a method. :-)     

JonW

Sounds extremely similar!  I have been looking at the U8g2 libraries, and man-o-man, they are BIG; no wonder drawing a box takes up the majority of Flash in a small Arduino. There are some cool meters on youtube, created using these libraries, but the device is practically full with a simple screen and an ADC/POT.     

I also have a single page ram buffer so you can quickly map a page for a fast border or header update.  It's quite humbling to work on these libraries as the math is pretty complex, especially when bounded by a tiny 8-bit pic.  Keeping track of the X,Y, and Z in the page memory is weird as it's vertically mapped in the display to the horizontal rom format.  Also being mindful to keep the routines fast and efficient.  Now I have the flexible sprite mapping working, complex-shaped animated sprites take up much less Flash than they would if you tried to create them as I can map the exact size, saving Flash.  I have also added a RAM export so you can create a screen, then export it via USB/Uart and pop it back in memory or Flash tables.

I admire you for doing all that work on the compiler alone.

charliecoutas

Nice one Les. ".. and it triggered some good memories.." rings quite a few bells with me. Going back in time is a very emotive thing - as I get older I really value those things that trigger early, usually happy memories.

Charlie