Break Into Program – TransAm

This is another of our occasional blog posts on programming retro games using techniques and languages that are familiar to kids. Retro games are often a good starting point, as they are usually fairly simple, yet with entertaining game play.

TransAm is a classic Sinclair Spectrum Game by Ultimate Play the Game, and takes the form of a road race across America. It’s a top-view game, and is one of my personal favourites.

Retro fact of the day: This game was originally developed to work on the 16K ZX Spectrum, so the code and graphics had to be less than 10K in size, as the screen RAM took up over 6K.

A video of the original game can be found here:

We looked at programming a version of this game in Scratch, and there is also a version for Python written using the PyGame library.

Link to Scratch here

The Python code is listed below, and can be cut and pasted into Python. If you are new to Python, we’ve written guides for installing Python and PyGame.

The graphics for this game can be downloaded here:

Graphics – TransAm

Unzip into the graphics directory (we put them in C:\Python\Dev\CoderDojo\Graphics).

Enjoy!

The Science Bit (Python Code)

# Import the libraries
#
import pygame
import random

# Initialise PyGame
# 
pygame.init()

# Some global variables
#
win_w = 800               # Window width
win_h = 600               # Window height
ticker = 0                # Ticker, increments every pass of the game loop

# Lookup table of scroll speeds (one for each rotation of the car)
# 
scroll_x = [ 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2, -2, -2, -2, -1 ]
scroll_y = [ -2, -2, -2, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2 ]

scroll_dir = 0;           # Scroll direction
scroll_speed = 1;         # Scroll speed multiplier

sprites = []              # List of sprites

# Create a screen surface to draw on, and a clock
#
screen = pygame.display.set_mode((win_w, win_h))
clock = pygame.time.Clock()

# Set the key repeat to 1ms delay, 1ms repeat
#
pygame.key.set_repeat(1,1)

# Load some images -------------------------------------------------------------

# Load the car rotation graphics into an array
#
image_car = [None] * 16
for i in range(16):
    image_car[i] = pygame.image.load("../Graphics/transam_%s.png" %(i)).convert()
    image_car[i].set_colorkey((0,0,0))

# Load the ground dot
#
image_dot = pygame.image.load("../Graphics/dot_4x4_yellow.png").convert()

# The Sprite class -------------------------------------------------------------

class Sprite:
    #
    # Some basic variables, just for this sprite
    #
    active = None   # Whether the sprite is active or off
    xp = None       # X position of the sprite
    yp = None       # Y position of the sprite
    image = None    # Sprite image
    type = None     # Sprite type (string)
    fn_move = None  # Function to move the sprite
    
    #
    # This is called when we initialise the Sprite class
    #
    def __init__(self):
        self.active = False

    #
    # A class function to move the sprite
    #
    def move(self):
        if(self.active and self.fn_move is not None):
            self.fn_move(self)
            return

    #
    # A class function to draw the sprite
    #
    def draw(self):
        if(self.active):
            screen.blit(self.image, (self.xp, self.yp))

# Set up a list of Sprites
#
max_sprites = 200                                   # Number of sprites we want to create
sprites = [Sprite() for i in range(max_sprites)]    # Create a list of Sprites

# Helper functions ------------------------------------------------------------

# Find index of next available free sprite slot in list
# Returns index in array of sprites, or -1 if no slots left
#
def find_next_available_sprite():
    for i in range(0, max_sprites):
        if sprites[i].active == False:
            return i
    return -1

# Movement functions ----------------------------------------------------------

# Move the player
#
def move_player(self):
    global scroll_dir, scroll_speed
    
    # Only read the keyboard every 4th frame
    # 
    if(ticker % 4 == 0):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and scroll_speed >= 1:
            self.rotate = (self.rotate - 1) % 16
        if keys[pygame.K_RIGHT] and scroll_speed >= 1:
            self.rotate = (self.rotate + 1) % 16
        if keys[pygame.K_UP] and scroll_speed < 8: scroll_speed += 1 if keys[pygame.K_DOWN] and scroll_speed > 0:
            scroll_speed -= 1
        self.image = image_car[self.rotate]
    scroll_dir = self.rotate
    scroll_speed -= 0.1
    if scroll_speed < 0:
        scroll_speed = 0

def move_ground(self):
    global scroll_x, scroll_y, scroll_dir, scroll_speed
    self.xp = (self.xp - scroll_x[scroll_dir] * scroll_speed) % win_w
    self.yp = (self.yp - scroll_y[scroll_dir] * scroll_speed) % win_h

# Stubbed functions ------------------------------------------------------------

# Called when the game initialises
#
def Initialise_Game():
    # Create the player sprite
    #
    sprites[max_sprites - 1].sprite_type = "player"   # This is the sprite type
    sprites[max_sprites - 1].xp = win_w//2            # Set X position to centre of screen
    sprites[max_sprites - 1].yp = win_h//2            # Set Y position to centre of screen
    sprites[max_sprites - 1].fn_move = move_player    # Set function to move sprite
    sprites[max_sprites - 1].active = True            # Set the sprite to be active
    sprites[max_sprites - 1].rotate = 0;

    # Create some ground dot sprites
    #
    for i in range(0, 50):
        sprites[i].sprite_type="bg"
        sprites[i].image = image_dot
        sprites[i].xp = random.randint(0, win_w)
        sprites[i].yp = random.randint(0, win_h)
        sprites[i].fn_move = move_ground
        sprites[i].active = True
        
    return

# Called when the level initialises
#
def Initialise_Level():
    return

# Called when the level ends
#
def End_Level():
    return

# Called when the game ends
#
def End_Game():
    return

# Main Game Loop ---------------------------------------------------------------

Initialise_Game()

# This outer loop keeps going until end_game is True
#
end_game = False
while not end_game:

    Initialise_Level()

    # This outer loop keeps going until end_level is True
    #
    end_level = False
    while not end_level:
        
        #
        # Pygame event handler
        #
        for event in pygame.event.get():
            # Check for window close
            if event.type == pygame.QUIT:
                end_level = True
                end_game = True

        # Move everything by calling the Sprite's move function
        #
        for i in range(0, max_sprites):
            sprites[i].move()
                
        # Clear the screen
        #
        screen.fill((0,0,0))

        # Draw the Sprites by calling their draw function
        #
        for i in range(0, max_sprites):
            sprites[i].draw()

        # Update the surface to the screen and lock at 60fps
        #
        pygame.display.flip()
        clock.tick(60)
        ticker += 1

    # Level over
    #
    End_Level()

# Game over!
#
End_Game()
pygame.quit()

Dean Belfield

Technical Consultant, Programmer and old-school 8-bit Games Developer.

You may also like...