<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[jpsteffe.com]]></title><description><![CDATA[Software Developer, Automation Advocate, and DIY Enthusiast]]></description><link>https://blog.jpsteffe.com/</link><image><url>http://blog.jpsteffe.com/favicon.png</url><title>jpsteffe.com</title><link>https://blog.jpsteffe.com/</link></image><generator>Ghost 4.4</generator><lastBuildDate>Tue, 28 Oct 2025 05:33:44 GMT</lastBuildDate><atom:link href="https://blog.jpsteffe.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Spaceship: An Asteroids Clone]]></title><description><![CDATA["Spaceship" aka Asteroids was one of the first projects that I ever made that worked on what some would call a "real" computer. Up until then, pretty much all of my experience with programming was on my TI-83+.]]></description><link>https://blog.jpsteffe.com/spaceship-an-asteroids-clone/</link><guid isPermaLink="false">5eae20645c78607c3f6105a8</guid><dc:creator><![CDATA[Josh Steffensmeier]]></dc:creator><pubDate>Sun, 17 May 2020 15:17:00 GMT</pubDate><content:encoded><![CDATA[<p>&quot;Spaceship&quot; aka Asteroids was one of the first projects that I ever made that ran on a &quot;real&quot; computer. Up until then, pretty much all of my experience with programming was on my <a href="http://blog.jpsteffe.com/how-i-got-started/">TI-83+</a>. I had forgotten it even existed until I stumbled onto an old zip file in the depths of my Google Drive trash folder. </p><p>It started because I wanted to learn a new language that would run on my home computer, and because I was a high school student of course I wanted to start making some games. After looking into good languages for a beginner to learn I finally settled on Python. I ended up making some simple games like &quot;Guess the Number&quot; and other beginner projects, but eventually I wanted to make something more serious. The only problem was that I had no idea what to do. After searching the internet for &quot;python&quot; and &quot;game&quot; of course I discovered PyGame. And I have to say that I was blown away, some of the examples that I saw on their website were pretty impressive. I had found my library. Next I grabbed some friends and we were off and running.</p><p>I started by just playing around with the library learning how to draw rectangles and move things around the screen. I think I built a simple platformer first, but then I started on Spaceship. It wasn&apos;t called Spaceship at the time though. It was just a generic object avoidance game until someone pointed out that it looked an awful lot like Asteroids. After we realized that we had inadvertently created an Asteroids clone we re-skinned the game with a space theme and Spaceship was born.</p><p>Now, lets take a look at the game itself:</p><figure class="kg-card kg-image-card"><img src="http://blog.jpsteffe.com/content/images/2020/05/spaceship_home.png" class="kg-image" alt loading="lazy"></figure><p>When I first tried to run it I was actually surprised that it worked at all to be honest. Granted I&apos;m lucky that PyPI hasn&apos;t shut down its Python 2.7 packages at the time of this writing but still...it works! </p><p>It doesn&apos;t look the most polished, but that matches the retro vibe that we were going for. The Play button functions as expected and so does Quit, but Options and High Scores just exit the game rather than doing anything useful. </p><figure class="kg-card kg-image-card"><img src="http://blog.jpsteffe.com/content/images/2020/05/spaceship_gameplay.png" class="kg-image" alt loading="lazy"></figure><p>Once in the game it pretty much works as expected. You can use the arrow keys or WASD to move your ship around and your mouse to turn your ship. Clicking will shoot a bomb that blows up the asteroids if you can&apos;t avoid them. The escape key will bring up a pause menu with the options to resume, go to the main menu, or quit the game.</p><p>I remember getting farther than this when we originally worked on the game, but that work must have been lost to time. I remember implementing the High Scores and a re-spawn feature. I also made the window re-sizable and even enabled full screen. There was also a credits screen Easter egg that triggered when you clicked the dot in the &quot;i&quot; of Spaceship. We must have been close to implementing that one because I found the credits image in the zip file but I couldn&apos;t find any code that would trigger it. </p><p>Speaking of the zip file, I was actually excited when I opened it up and saw two different versions of the source code in it. I thought maybe I would get to see multiple versions of the application because of my basic attempt at source control. Unfortunately, the only difference between the files was the addition of a few newlines.</p><p>Now lets get into the fun part...the code.</p><p>Oh boy the code, where do I even start? It makes me cringe even looking at it. The first thing that I should mention is that none of it is PEP-8 or any other consistent coding standard. The second thing to note is that there are no comments anywhere. Despite these two factors, the code is still simple enough to be able to follow along somewhat coherently.</p><p>Lets start with the top:</p><pre><code class="language-Python">import pygame, sys, random, math
from pygame.locals import *

pygame.init()
mainClock=pygame.time.Clock()

width=600
height=600
window=pygame.display.set_mode((width, height))
pygame.display.set_caption(&apos;Game_Test&apos;)

black=(0,0,0)
white=(255,255,255)
green=(0,255,0)
red=(255,0,0)

menu=pygame.image.load(&apos;mainmenu.png&apos;).convert()
pausebg=pygame.image.load(&apos;pausebg.png&apos;).convert()
font=pygame.font.SysFont(None,36)
font2=pygame.font.SysFont(None,20)</code></pre><p>Here we have some imports and global variable definitions. I might refactor some of this stuff later but for now lets just run the linter and do some minor cleanup.</p><pre><code class="language-Python"># Python imports
import math
import random
import sys

# Library imports
import pygame
from pygame.locals import *
from pygame.color import THECOLORS

# Set constants
WIDTH = 600
HEIGHT = 600

# Initialize pygame
pygame.init()

# Setup the pygame window
mainClock = pygame.time.Clock()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption(&apos;Spaceship&apos;)

# Setup pygame constants
FONT_LARGE = pygame.font.SysFont(None, 36)
FONT_SMALL = pygame.font.SysFont(None, 20)

# Load game assets
menu = pygame.image.load(&apos;mainmenu.png&apos;).convert()
pausebg = pygame.image.load(&apos;pausebg.png&apos;).convert()

background = pygame.image.load(&apos;stars.gif&apos;).convert()
background = pygame.transform.scale(background, (WIDTH, HEIGHT))

bombpic = pygame.image.load(&apos;bomb.jpg&apos;).convert()
bombpic = pygame.transform.scale(bombpic, (20, 20))
bombpic.set_colorkey(THECOLORS[&apos;white&apos;])

playerpic = pygame.image.load(&apos;spaceship.png&apos;).convert()
playerpic = pygame.transform.scale(playerpic, (30, 30))
playerpic.set_colorkey(THECOLORS[&apos;white&apos;])

rockpic = pygame.image.load(&apos;rock.png&apos;).convert()
rockpic.set_colorkey(THECOLORS[&apos;white&apos;])

explosion = pygame.image.load(&apos;explosion.jpg&apos;)
explosion = pygame.transform.scale(explosion, (60, 60))
explosion.set_colorkey(THECOLORS[&apos;white&apos;])</code></pre><p>Much better, I pulled all of the instances where an image is loaded to the top. We don&apos;t need to load the same image every time we start a new game. This has the added benefit that we will know immediately if an asset is missing. There&apos;s still room for improvement here but lets move on to the next section for now.</p><p>The main menu is where things start to get a little interesting. </p><p>First off, the entire function is an infinite loop. Every frame it draws the background image, sets up the clickable areas, processes the events for those areas, and updates the screen. That is already a lot of responsibility for one function but I think the most interesting thing is what happens when you click play. Clicking play calls the <code>game()</code> function which has an infinite loop to handle the game events. Going a step further, pressing pause from game calls the <code>pause()</code> function which also contains <em>another infinite loop!</em> At this point we are three infinite loops deep and hopefully are wondering if we can do better.</p><p>Ideally we would only have one infinite loop that handles all of the drawing and event handling no matter which part of the program that we are in. So lets start with that:</p><pre><code class="language-Python">def main():
    while True:
    	pass  # Draw something here
    	for event in pygame.event.get():
        	pass  # Handle events here</code></pre><p>That was easy! It sure would be nice if this function did anything other than just loop though.</p><pre><code class="language-Python">class State:
    __metaclass__ = ABCMeta

    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def handle_event(self, event):
        pass  # Return next state or self to stay on current state
        

def main():
    state = MainMenuState()
    while True:
        state.draw()
        for event in pygame.event.get():
            state = state.handle_event(event)</code></pre><p>That&apos;s better, we now have a small state machine that will draw to the screen with the <code>draw()</code> method and return the next state based on input events with the <code>handle_event()</code> method. Now it&apos;s just a matter of defining the states and their transitions. MainMenuState seems like it would be a good place to start, since that will be the initial state on startup.</p><pre><code class="language-Python">def exit_game():
    # Cleanup resources and quit the game
    sys.exit()


class MainMenuState(State):
    def __init__(self):
        self.play_rect = pygame.Rect(window.get_rect().centerx - 55,
                                     window.get_rect().centery - 90,
                                     125, 55)
        self.options_rect = pygame.Rect(window.get_rect().centerx - 75,
                                        window.get_rect().centery - 20,
                                        167, 55)
        self.highscores_rect = pygame.Rect(window.get_rect().centerx - 110,
                                           window.get_rect().centery + 45,
                                           250, 55)
        self.quit_rect = pygame.Rect(window.get_rect().centerx - 45,
                                     window.get_rect().centery + 105,
                                     105, 45)</code></pre><p>Before we start on the meat of the interface we need to define a few things that will make our lives easier later on. The <code>exit_game()</code> global function is a place where we can clean up any resources that need to be cleaned up before calling <code>sys.exit()</code> to end the program. In the class itself we will also want to define some rectangles, these will be our clickable areas later on.</p><pre><code class="language-Python">def draw(self):
    # Draw the background image to erase the previous screen
    window.blit(menu, (0, 0))

    # If the mouse is in a menu option rectangle then draw a line around it
    if self.play_rect.collidepoint(pygame.mouse.get_pos()):
        pygame.draw.rect(window, THECOLORS[&apos;red&apos;], self.play_rect, 2)

    if self.options_rect.collidepoint(pygame.mouse.get_pos()):
        pygame.draw.rect(window, THECOLORS[&apos;red&apos;], self.options_rect, 2)

    if self.highscores_rect.collidepoint(pygame.mouse.get_pos()):
        pygame.draw.rect(window, THECOLORS[&apos;red&apos;], self.highscores_rect, 2)

    if self.quit_rect.collidepoint(pygame.mouse.get_pos()):
        pygame.draw.rect(window, THECOLORS[&apos;red&apos;], self.quit_rect, 2)

    pygame.display.update()</code></pre><p><code>MainMenuState.draw()</code> is the first place that the game really takes shape. The first thing that we do in this function is to draw the <code>mainmenu.png</code> image over the entire screen, this will make more sense later on. Next we detect if the mouse pointer is within any of the rectangles that we defined before. If it is, then we will draw a border around that rectangle. This is also why we draw the main menu image every frame. In PyGame there isn&apos;t a way to undraw something from the screen, so we want to reset the screen in between frames so that the rectangles reset when the mouse is not over them. The last step is to tell PyGame that we are done drawing to the screen so that it can flush everything to our display. &#xA0;PyGame also has an option to only update parts of the screen by passing <code>pygame.display.update()</code> a list of &#xA0;&quot;dirty&quot; rectangles (the ones that have changed since the last update). If we wanted to do that, this would be the place to do so.</p><p>Finally we made it to the event handler! This is the most interesting part of the main menu. </p><pre><code class="language-Python">def handle_event(self, event):
    # Handle window close events
    if event.type == QUIT:
        exit_game()

    # Pressing escape also closes the game
    if event.type == KEYDOWN:
        if event.key == K_ESCAPE:
            exit_game()

    if event.type == MOUSEBUTTONUP:
        if self.play_rect.collidepoint(event.pos):
            return GameState()

        if self.options_rect.collidepoint(event.pos):
            return self

        if self.highscores_rect.collidepoint(event.pos):
            return self

        if self.quit_rect.collidepoint(event.pos):
            exit_game()
    return self</code></pre><p>The first two parts check if the user has exited out of the game with the &apos;X&apos; or by pressing the &apos;Escape&apos; key. The next part checks to see if the user has clicked inside any of the rectangles that we defined before. If they have, then it will return the next state that the game should transition to. Returning <code>self</code> keeps the current state and therefore doesn&apos;t transition the interface. It&apos;s important to remember that the outer loop is expecting a state every frame and so the fall through case of this function should be to return <code>self</code> as well. If it returns <code>None</code> there will be an error because <code>None</code> doesn&apos;t have a <code>draw()</code> function. So far the only new state that is returned is the GameState so lets look at that next.</p><p> The <code>game()</code> function is an awful mess that isn&apos;t really worth looking at as a whole. We can do better with GameState by starting with consolidating all of the drawing and event handling. But like with the MainMenuState we first need to do some setup.</p><pre><code>def __init__(self):
    self.score = 0
    self.rockcounter = 0
    self.newrock = 40
    self.rocknumber = 5
    self.player = pygame.Rect(window.get_rect().centerx,
                              window.get_rect().centery,
                              30, 30)
    self.speed = 6

    self.rocks = []
    for i in range(self.rocknumber):
        rocksize = random.randint(20, 40)
        rockx = random.randint(-rocksize, WIDTH)
        rocky = random.randint(-rocksize, 0)
        self.rocks.append({&apos;rect&apos;: pygame.Rect(rockx, rocky, rocksize, rocksize),
                           &apos;xspeed&apos;: math.cos(math.atan2(self.player.centery - rocky,
                                                         math.fabs(rockx - self.player.centerx))),
                           &apos;yspeed&apos;: math.sin(math.atan2(self.player.centery - rocky,
                                                         math.fabs(rockx - self.player.centerx))),
                           &apos;pic&apos;: pygame.transform.scale(rockpic, (rocksize, rocksize))})

    self.moveleft = False
    self.moveright = False
    self.moveup = False
    self.movedown = False
    self.bombs = []</code></pre><p>This is pretty ugly but we can come back to it once we have a better idea of what is needed by the other functions. Next we are going to consolidate all of the display functionality into <code>draw()</code>.</p><pre><code class="language-Python">def draw(self):
    # Draw the background
    window.fill(THECOLORS[&apos;black&apos;])
    window.blit(background, (0, 0))</code></pre><p>Again the first thing that we want to do is set the background to all black and then draw the background image. We need to draw the background as black first because the image isn&apos;t the right size to fill up the screen. Next we will move and rotate the player image.</p><pre><code class="language-Python">if moveleft and player.left &gt; speed:
    player.left -= speed
if moveup and player.top &gt; speed:
    player.top -= speed
if movedown and player.bottom &lt; HEIGHT - speed:
    player.bottom += speed
if moveright and player.right &lt; WIDTH - speed:
    player.right += speed
            
# Rotate the player
mousex, mousey = pygame.mouse.get_pos()
playerpicrotated = pygame.transform.rotate(playerpic, -math.degrees(
    math.atan2(self.player.centery - mousey, self.player.centerx - mousex)) + 45)
window.blit(playerpicrotated, self.player)</code></pre><p>In the old code above, we checked to see if adding or subtracting the speed would result in the player leaving the screen. If it would, we wouldn&apos;t move the player in that direction so that they would always be visible. We can make this better by forcing the player rectangle within the edges of the screen with the PyGame function <code>clamp</code>. This makes it so that the player can actually make it the last few pixels and touch the edge. It also removes some of the complicated edge cases that we were checking. In code that looks like this:</p><pre><code class="language-Python"># Move the player
if self.moveleft:
    self.player.left -= self.speed
if self.moveup:
    self.player.top -= self.speed
if self.movedown:
    self.player.bottom += self.speed
if self.moveright:
    self.player.right += self.speed
self.player.clamp_ip(window.get_rect())</code></pre><p>There is also some semi-complicated math that is used to rotate the player image to face the mouse pointer. To understand the math we need to have a quick refresher in trigonometry. one of the basic trig functions is the tangent function. It is defined as \(\tan(\theta) = \frac{y}{x}\) where \(\theta\) is the angle that we are looking for. Taking the arctangent of both sides leaves us with an equation for \(\theta\). </p><p>\[\arctan(\tan(\theta)) = \arctan(\frac{y}{x})\]<br>\[\theta = \arctan(\frac{y}{x})\]</p><p>However there is still a problem with this formula. The range (output) of the arctangent function is limited between \(\frac{\pi}{2}\) to \(-\frac{\pi}{2}\). this is because \(\frac{-y}{x}\) is equal to \(\frac{y}{-x})\). Python solves this problem for us by providing the <code>atan2</code> function in the math library, this function takes two arguments, y and x, and by looking at the sign of each argument it can disambiguate the above problem and tell us the correct angle in the range \(-\pi\) to \(\pi\). </p><p>The only thing left to do is to pick x and y so that they form an angle from the player to the mouse. We can do that by subtracting the x and y coordinates of the mouse from the x and y coordinates of the player but we need to be careful to make sure that the signs of each subtraction match what <code>atan2</code> is expecting so that we get the correct rotation in the end. If the mouse is above the player we want the result to be positive, and if the mouse is to the left of the player we want the result to be negative. Because in the PyGame coordinate system the origin is in the top left and increases down and to the right we have to be a little creative the get the answers we expect. </p><p>Subtracting the mouse y value from the player y value gives the correct answer because the mouse y value will be smaller when it is above the player and larger when it is below the player. The opposite is true for the x coordinates so we have to flip the order we subtract them.</p><p>After translating all of the rotation logic into code we get this:</p><pre><code class="language-Python"># Rotate the player
mousex, mousey = pygame.mouse.get_pos()

# Subtract player x from mouse x to match atan2 expected inputs
player_angle_x = mousex - self.player.centerx
player_angle_y = self.player.centery - mousey

# Subtract 135 to offset the angle of the spaceship in the image
rotation = math.degrees(math.atan2(player_angle_y, player_angle_x)) - 135

player_rotated = pygame.transform.rotate(playerpic, rotation)
window.blit(player_rotated, self.player)</code></pre><p>Next let&apos;s look at the bombs that are currently on the screen.</p><pre><code class="language-Python"># move the bombs
for i in self.bombs:
    # If still on the screen then draw the next frame
    if i[&apos;rect&apos;].bottom &gt; -25 and i[&apos;rect&apos;].top &lt; HEIGHT + 25 and i[&apos;rect&apos;].left &lt; WIDTH + 25 and i[&apos;rect&apos;].right &gt; -25:
        i[&apos;rect&apos;].centerx += i[&apos;xspeed&apos;] * self.speed
        i[&apos;rect&apos;].centery += i[&apos;yspeed&apos;] * self.speed

    # If bomb left the screen then remove it from the list
    if i[&apos;rect&apos;].bottom &lt;= -25 or i[&apos;rect&apos;].top &gt;= HEIGHT + 25 or i[&apos;rect&apos;].left &gt;= WIDTH + 25 or i[&apos;rect&apos;].right &lt;= -25:
        self.bombs.remove(i)

    window.blit(i[&apos;pic&apos;], i[&apos;rect&apos;])</code></pre><p>Rather than remove items from the list while we are iterating over it, a better idea is to filter the list ahead of time and then iterate over the smaller list.</p><pre><code class="language-Python"># Move the bombs
self.bombs = [bomb for bomb in self.bombs if bomb[&apos;rect&apos;].colliderect(window.get_rect())]
for bomb in self.bombs:
    bomb[&apos;rect&apos;].centerx += bomb[&apos;xspeed&apos;] * self.speed
    bomb[&apos;rect&apos;].centery += bomb[&apos;yspeed&apos;] * self.speed
    window.blit(bomb[&apos;pic&apos;], bomb[&apos;rect&apos;])</code></pre><p>We also want to check if the bomb has hit any of the rocks on screen but before we do that let&apos;s define a new function to spawn a new rock into the game and clean up the <code>__init__</code> function to use it.</p><pre><code class="language-Python">def __init__(self):
    self.score = 0

    self.player = pygame.Rect(window.get_rect().centerx, window.get_rect().centery, 30, 30)
    self.speed = 6
    self.moveleft = False
    self.moveright = False
    self.moveup = False
    self.movedown = False

    self.bombs = []

    self.rocks = []
    self.num_rocks = 5
    for i in range(self.num_rocks):
        self.rocks.append(self.spawn_rock())

def spawn_rock(self):
    rocksize = random.randint(20, 40)
    rockx = random.randint(-rocksize, WIDTH+rocksize)
    rocky = random.randint(-rocksize, 0)
    return {&apos;rect&apos;: pygame.Rect(rockx, rocky, rocksize, rocksize),
            &apos;xspeed&apos;: math.cos(math.atan2(self.player.centery - rocky, math.fabs(rockx - self.player.centerx))),
            &apos;yspeed&apos;: math.sin(math.atan2(self.player.centery - rocky, math.fabs(rockx - self.player.centerx))),
            &apos;pic&apos;: pygame.transform.scale(rockpic, (rocksize, rocksize))}</code></pre><p>The <code>spawn_rock</code> method will be useful when we start talking about rocks and <code>__init__</code> is starting to look much better now that all of that logic has been delegated elsewhere. Now we can go back to the bombs and check if they have hit any of the rocks.</p><pre><code class="language-Python">for bomb in self.bombs:
    if bomb[&apos;exploded&apos;]:
        bomb[&apos;explosion_counter&apos;] -= 1
        window.blit(explosion, bomb[&apos;rect&apos;])
    else:
        bomb[&apos;rect&apos;].centerx += bomb[&apos;xspeed&apos;] * self.speed
        bomb[&apos;rect&apos;].centery += bomb[&apos;yspeed&apos;] * self.speed
        window.blit(bomb[&apos;pic&apos;], bomb[&apos;rect&apos;])

        # Remove a rock from the list if it was hit and start the explosion counter
        rock_index = bomb[&apos;rect&apos;].collidelist([r[&apos;rect&apos;] for r in self.rocks])
        if rock_index != -1:
            self.rocks.pop(rock_index)
            self.rocks.append(self.spawn_rock())
            bomb[&apos;exploded&apos;] = True</code></pre><p>If the current bomb has exploded we draw the explosion graphic instead of the bomb graphic and decrement the frame counter. This allows the explosion to stay on screen longer so that we can actually see it. Otherwise, we check if the current bomb hit any of the rocks and remove the rock that was hit and replace it in the list with <code>spawn_rock</code>. One last important piece of code to remember is to clean up the explosions after their frame counter reaches 0.</p><pre><code class="language-Python">self.bombs = [bomb for bomb in self.bombs
              if bomb[&apos;rect&apos;].colliderect(window.get_rect()) and bomb[&apos;explosion_counter&apos;] &gt; 0]</code></pre><p>Finally we are on to the rocks themselves, we need to start with removing the rocks that are off the screen similar to how we did with the bombs. </p><pre><code class="language-Python">self.rocks = [rock for rock in self.rocks if rock[&apos;rect&apos;].colliderect(window.get_rect())]</code></pre><p>But if we ran the game now we might notice that there is a bug in the above statement. Because rocks get spawned off screen they will get deleted as soon as they are created. We can fix this by adding a new spawn rectangle that we will use to create rocks in, and if they collide with this rectangle then we will also consider them &quot;on the screen&quot;. We can also take this opportunity to improve the <code>spawn_rock</code> function so that it actually aims rocks at the player. </p><p>We will use <code>spawn_rect</code> to determine the initial location of the rock and then find the x and y distances to the player. Dividing by the hypotenuse of that triangle gives us numbers between 0 and 1 to use as the x and y speed of the rock. A number between 0 and 1 is good because we will be multiplying these numbers by the speed scaling factor and we don&apos;t want them to go too fast that we can&apos;t react. It also has the nice benefit that when truncated to an integer it doesn&apos;t always look like the rock was aimed directly at the player because of rounding errors.</p><pre><code class="language-Python">def __init__(self):
	self.spawn_rect = pygame.Rect(0, -40, WIDTH, 40)
    ...
    
def spawn_rock(self):
    rocksize = random.randint(20, 40)
    rockx = random.randint(self.spawn_rect.left, self.spawn_rect.right)
    rocky = random.randint(self.spawn_rect.top, self.spawn_rect.bottom)
    x_dist = self.player.centerx - rockx
    y_dist = self.player.centery - rocky
    total_dist = math.sqrt((x_dist**2) + (y_dist**2))
    return {&apos;rect&apos;: pygame.Rect(rockx, rocky, rocksize, rocksize),
            &apos;xspeed&apos;: x_dist/total_dist,
            &apos;yspeed&apos;: y_dist/total_dist,
            &apos;pic&apos;: pygame.transform.scale(rockpic, (rocksize, rocksize))}
            
def draw(self):
	...
    self.rocks = [rock for rock in self.rocks
                      if rock[&apos;rect&apos;].collidelist([window.get_rect(), self.spawn_rect]) != -1]</code></pre><p>After fixing this, we need to replace all of the rocks that we had previously removed and increment the score for each one. </p><pre><code class="language-Python">for i in range(self.num_rocks - len(self.rocks)):
    self.rocks.append(self.spawn_rock())
    self.score += 1</code></pre><p>And the last step is easy, just iterate through the list of rocks and update their positions on the screen.</p><pre><code class="language-Python">for rock in self.rocks:
    rock[&apos;rect&apos;].centerx += rock[&apos;xspeed&apos;] * self.speed
    rock[&apos;rect&apos;].centery += rock[&apos;yspeed&apos;] * self.speed

    window.blit(rock[&apos;pic&apos;], rock[&apos;rect&apos;])</code></pre><p>One of the last things that <code>draw</code> needs to do is to check if the player has collided with any rocks. If they have, then we will set the <code>game_over</code> variable and post an event for the <code>handle_event</code> method to read and advance to the GameOverState. The very last thing for draw to do is to draw the score to the screen and update the difficulty every 50 points. </p><pre><code class="language-Python"># Check for player collision
if self.player.collidelist([rock[&apos;rect&apos;] for rock in self.rocks]) != -1:
    self.game_over = True
    pygame.event.post(pygame.event.Event(USEREVENT, {}))

# Every 50 points increase the number of rocks by 5
if self.score % 50 == 0:
    self.num_rocks += 5
    self.score += 1

# Draw the score
window.blit(FONT_SMALL.render(str(self.score), True, THECOLORS[&apos;red&apos;], THECOLORS[&apos;black&apos;]), (350, 25))

pygame.display.update()
mainClock.tick(40)</code></pre><p>The GameState event handler is a lot of what you have seen before, if a known key is pressed then it sets some instance variables so that the <code>draw</code> method knows what to do. If <code>game_over</code> is set then it just returns a new GameOverState, and if the mouse is clicked it adds a new bomb to the bomb array. The only interesting part of it is that when then pause button is pressed it will return a new PauseState object instantiated with the current GameState as the argument. This is so that when the PauseState is exited, it can return the state passed to it instead of creating an entirely new game. </p><p>I was slightly concerned that this might cause a memory leak. But because GameState doesn&apos;t maintain a reference to the PauseState, the only reference to the PauseState is in the main loop. Once the PauseState is unreferenced by the main loop it will get garbage collected by Python and we will be back to only having one state in memory.</p><p>I will spare you the details of how GameOverState and PauseState work since they are very similar to the rest. If you are interested you can check out the project on <a href="https://github.com/jpsteffe/spaceship">GitHub</a> and look at the code there, the specific branch with all of these changes can be found <a href="https://github.com/jpsteffe/spaceship/tree/feature/state_machine_refactor">here</a>. Overall I&apos;m happy with the new state machine design. I think it will be easy to extend in the future if more states are desired, like for the high scores and credits. If you are curious as to what the state diagram currently looks like here it is:</p><figure class="kg-card kg-image-card"><img src="http://blog.jpsteffe.com/content/images/2020/05/spacehip_state_diagram.PNG" class="kg-image" alt loading="lazy"></figure><p>There are definitely still things that I would change such as finishing all of the features, upgrading to Python 3.x, and making it so that everything follows a consistent coding standard, but I will save that for another time. For now I&apos;m just having fun playing Spaceship.</p>]]></content:encoded></item><item><title><![CDATA[How I Got Started with Programming]]></title><description><![CDATA[I was going through my desk the other day when I came across something interesting...my old TI-83+ from middle school through college. I remembered that there were some old programs on there that I had written throughout the years, and I thought it might be fun to look at some of them.]]></description><link>https://blog.jpsteffe.com/how-i-got-started/</link><guid isPermaLink="false">5eae1fd35c78607c3f6105a2</guid><dc:creator><![CDATA[Josh Steffensmeier]]></dc:creator><pubDate>Tue, 05 May 2020 02:36:25 GMT</pubDate><content:encoded><![CDATA[<p>I was going through my desk the other day when I came across something interesting...my old TI-83+ from middle school through college. I remembered that there were some old programs on there that I had written throughout the years, and I thought it might be fun to look at some of them. After a quick trip to the garage to grab some batteries she was up and running just like I remembered.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="http://blog.jpsteffe.com/content/images/2020/05/TI-83--1.jpg" class="kg-image" alt loading="lazy"><figcaption>Quadratic Formula</figcaption></figure><p>Texas Instruments (TI) began support for their programming language TI-Basic with the release of the TI-81 calculator released in 1990, and have continued to support the language in every calculator model since. While most students have probably discovered the PRGM button on their calculator, and perhaps dabbled with it, few have discovered the computational power available to them, and fewer still have been able to utilize it to its full potential. I won&apos;t claim to be in the last group, but I did manage to write some useful functions with it that saved me a lot of time on tests.</p><p>Using TI-Basic was actually my first foray into the programming world. It started in middle school where I was told to copy down the instructions for a basic quadratic formula solver into the program memory by my algebra teacher. Unfortunately I didn&apos;t touch that feature again until the end of my sophomore year geometry class in high school, and didn&apos;t seriously utilize it until my senior year. Little did I know at the time that that little taste of programming would go on to become the foundation of my career.</p><p>Lets get to looking at one of the old programs that I wrote shall we? It only seems fitting to start with the one that started it all - QUAD. While I said before that QUAD started life as a copy and paste quadratic formula solver from middle school, it was later improved as I learned about complex numbers and determinants. Without further ado...QUAD:</p><figure class="kg-card kg-code-card"><pre><code>ClrHome
Prompt A,B,C
(B&#xB2;-4*A*C)&#x2192;D
If D&lt;0
Then
Output(4,1,B
Output(4,3,&quot;+-i&#x221A;(&quot;
Output(4,9,-D
Output(5,4,2*A
Else
-((B+&#x221A;(D))/(2A))&#x2192;E
-((B-&#x221A;(D))/(2A))&#x2192;F
Disp &quot;Solutions&quot;,E,F
Output(5,1,&quot;X=&quot;
Output(6,1,&quot;X=&quot;</code></pre><figcaption>Quadratic Formula Solver</figcaption></figure><p>Breaking this down, the code it looks pretty straightforward. </p><p>First clear the screen and prompt for input to three variables A, B, and C.</p><pre><code>ClrHome
Prompt A,B,C</code></pre><p>Next calculate the determinant of the quadratic formula and assign it to a new variable D (&#x2192; is the assignment operator in TI-Basic). For those like myself that haven&apos;t taken high school math in quite a while, here is the quadratic formula:</p><p>\[ x_1, x_2 = \frac{-b \pm \sqrt{b^2 + 4ac}}{2a}\]</p><p>And in this context the &quot;determinant&quot; is just a fancy way of saying the &quot;stuff inside of the square root &quot;.</p><pre><code>(B&#xB2;-4*A*C)&#x2192;D</code></pre><p>Then check if the determinant is less than zero. If it is it will crash the square root function of the calculator and we don&apos;t want to do that It also means that the solution will be a pair of complex numbers and so we should output those accordingly.</p><pre><code>If D&lt;0
Then
Output(4,1,B
Output(4,3,&quot;+-i&#x221A;(&quot;
Output(4,9,-D
Output(5,4,2*A</code></pre><p>Otherwise we should use the square root function of the calculator to come up with the two solutions to the equation.</p><pre><code>Else
-((B+&#x221A;(D))/(2A))&#x2192;E
-((B-&#x221A;(D))/(2A))&#x2192;F
Disp &quot;Solutions&quot;,E,F
Output(5,1,&quot;X=&quot;
Output(6,1,&quot;X=&quot;</code></pre><p>The first thing that struck me when I read this was that none of the output statements had closing parenthesis on them. Obviously the program still works, so after some research I discovered that this is a common space saving optimization because TI-Basic doesn&apos;t require closing parenthesis at the end of a line. Big plus one to my past self for recognizing that, but unfortunately I didn&apos;t realize that I could have gone further and removed the end of line quotation marks as well. </p><p>The next thing I noticed was a distinct lack of indentation. I don&apos;t remember indentation being an option, but at the same time I probably just didn&apos;t realize that it was important. This is probably a good time to mention that none of my schools up until Iowa State offered any programming classes, and so I was entirely self taught. But I hope that any course that I would have taken would have emphasized good code cleanliness and indentation.</p><p>It is definitely on the simpler side, but it does exactly what it needs to do. If I were to write it again today I would probably make the code cleanliness changes that I mentioned before and try to add some logic so that it would output a simplified radical when the determinant is an integer. Currently, it will just return large floating point numbers that are harder to read than \(\sqrt{3}\). Overall I think the code isn&apos;t that bad for a first program, and I&apos;m happy that it still works after all these years.</p><p>As I was researching this post, I found an old <a href="http://tibasicdev.wikidot.com/home">website</a> that I remember using back when I was first learning how to write TI-Basic. I can tell that its the same site because it hasn&apos;t changed since I first discovered it over a decade ago. And the forums are surprisingly still active! Its somehow comforting to know that as much as the world has changed, that there is still a small community helping new programmers like I was to learn the ropes the same way that I did all those years ago.</p>]]></content:encoded></item></channel></rss>