Python Arcade Keeping Score and Postgres

Introduction

This is part 9 of a multi-part series of articles teaching how to build a video game of the 2D kind. In this part 9, we increase the fun factor by learning how to use Python Arcade for Keeping Score and Postgres for saving that score and writing and reading Sprites on the screen. In this part, we learn how to add score keeping and saving. In the article following this one, where we delve into giving the player the ability to change game difficulty on the fly, we’ll make game difficulty affect points scored. In the articles following this part, we’ll learn to and add a system for tracking and changing game difficulty, which influences many parts of the game, including speed of enemy movement and enemy intelligence. Depending on demand, we may also add online multiplayer capability and build a screen editor.

Prerequisites

  • The full source code for this project, including images and sound files are available here for download.

  • Python Arcade docs. We use the Arcade framework for most of the functionality in this Python Arcade video game.

  • IMPORTANT: study part 1 through part 8 where we learned to create and set up a game screen window with Postgres, how to set up Sprites with images and group the Sprites into Arcade Sprite Lists, how to acknowledge and address when the user presses a key, how to move the Player’s Sprite through game play based on those keypresses, how to move enemies (walruses) with some randomness and intelligence, how to enable the player to fire multiple bullets (snowballs) using the space bar, snowball movement, collisions between Sprite Lists and Sprites, and sound effects.

In this part 9, before we add keeping score to our game and even storing that score in PostgreSQL! Because the full source code of the game is available for you to download, study, and run, we’ll copy over only the snippets necessary from the full game code and comment them heavily here, so you can learn in the most easy manner possible without a direct brain interface.

Score keeping in main game class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Main application class showing only up to where we added score-keeping
class GameClass(arcade.Window):
    def __init__(self):
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
        # Center the location of our game screen
        self.set_location(int((MONITOR_RES_WIDTH - SCREEN_WIDTH)/2), int((MONITOR_RES_HEIGHT - SCREEN_HEIGHT)/2))
        # Set the working folder to the
        # same folder this python file is in.
        try:
            file_path = sys._MEIPASS
        except Exception:
            #file_path = os.path.dirname(os.path.abspath(__file__))
            file_path = os.path.abspath(".")
        os.chdir(file_path)
        self.frame_count = 0
        self.game_over = False
        # Set up the player score, sprite, and lives.
        self.score = 0

Now we will look at our function for starting a new game and add score keeping to that function:

Game score initialization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Set up the game and initialize the variables.
def start_new_game(self):
    self.frame_count = 0
    self.game_over = False
    # Set up all Sprite lists.
    self.list_all_sprites = arcade.SpriteList()
    self.list_enemies = arcade.SpriteList()
    self.list_snowballs = arcade.SpriteList()
    self.list_player_lives = arcade.SpriteList()
    self.list_blocks = arcade.SpriteList()
    # Set up title screen sprite image.
    self.title = PlayerSprite("./resources/images/title.png", SCALE * 2.5)
    # Set up the player score as a property of the player Sprite.
    self.score = 0

Next, we will look at how to draw the score indicator on the screen so the player can see.

Draw game score on screen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Render the game screen.
def on_draw(self):
    # This command must occur before we start drawing
    arcade.start_render()
    # Draw all game Sprites here.
    self.list_all_sprites.draw()
    # Draw title box in the middle of the screen.
    if self.player_sprite.respawning:
        self.title.center_x = SCREEN_WIDTH/2
        self.title.center_y = SCREEN_HEIGHT/2
        self.title.alpha = 255
        self.title.draw()
    # Remove title box from game screen once
    # the Player has finished respawn.
    else:
        self.title.alpha = 0
        self.title.draw()
    # Put stats, etc on the screen.
    output = f"Throw snowball: press space"
    arcade.draw_text(output, 10, SCREEN_HEIGHT-25, arcade.color.BLACK, 14)
    # Notice the parameters used below with the
    # arcade draw_text function.
    # "output" may be obvious.
    # the next two params represent x and y positions.
    # So score is placed near the bottom of the screen.
    output = f"Score: {self.score}"
    arcade.draw_text(output, 10, 40, arcade.color.BLACK, 14)

Finally, we’ll add to the Player’s score when they hit an enemy with a bullet (snowball):

Score increase from hitting enemy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Move all Sprites on screen:
def on_update(self, x_delta):
    self.frame_count += 1
    # Calculate the frames per second and call the variable "fps".
    fps = x_delta * 3600
    # Set up enemy speed variable for use in this function.
    enemy_speedy = 2 + (self.player_sprite.difficulty/12)
    if not self.game_over:
        self.list_all_sprites.update()
        # Iterate through all bullets
        # currently on screen.
        for snowball in self.list_snowballs:
            enemies = arcade.check_for_collision_with_list(snowball, self.list_enemies)
            # Cycle through all (if any) enemies hit by current snowball.
            for enemy in enemies:
                arcade.play_sound(self.sound_powerup)
                # Give points to the Player for hitting
                # enemy based on difficulty level, which
                # we'll play with in the next lesson part.
                self.score += int(100 * self.player_sprite.difficulty)

OK now that we’ve studied all the parts in the game where score keeping is managed, we will move on to using PostgreSQL to store and retrive those scores!

Python save game

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# First we'll use a SELECT SQL statement
# to check if a saved screen already exists
# for the current user. So yes, one save
# per user.
s = ""
s += "SELECT"
s += " id"
s += ", i_score_high"
s += " FROM tbl_games"
s += " WHERE id_user = " + id_user
db_cursor.execute(s)
db_row = db_cursor.fetchone()
# Check to find if a row was returned.
# If not, set i_rows variable to zero value.
if db_row == None:
    i_rows = 0
else:
    # A row was returned, yay.
    # Get id and high score from that row, so that
    # later we can update the row with user high score
    # if the new score is higher.
    i_rows = 1
    id = db_row[0]
    i_score_high = db_row[1]

Now that we have queried the database to (a) find out if there are any matching rows for this user; and (b) if so, grabbed the id of that row and the high score, we can proceed to either add a row or update the existing row, depending on those results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
if i_rows == 0:
    # If no rows were returned, this means
    #   we want to add a new record via the INSERT INTO
    #   SQL command.
    s = ""
    s += "INSERT INTO tbl_games ("
    s += " id_screen"
    s += ", id_user"
    s += ", i_hit_points"
    s += ", i_points"
    s += ", i_lives"
    s += ", i_score_high"
    s += ") VALUES ("
    s += id_screen
    s += "," + id_user
    s += "," + i_hit_points
    s += "," + i_points
    s += "," + i_lives
    s += "," + i_score_high
    s += ")"
    db_cursor.execute(s)
else:
    # If a recordset row was returned earlier, we use
    # the id column of that returned row in our WHERE
    # clause so we overwrite that row in the database
    # with a new high score.
    # First check to make sure the new score is higher
    # than the one stored in the database.
    if self.score > i_score_high:
        s = ""
        s += "UPDATE tbl_games SET"
        s += " id_screen = " + id_screen
        s += ", i_hit_points = " + i_hit_points
        s += ", i_points = " + i_points
        s += ", i_lives = " + i_lives
        s += ", i_score_high = " + i_score_high
        s += " WHERE id = " + id
        db_cursor.execute(s)

Another way to handle scores would be to add every score to a table called tbl_scores and allow queries to produce either all scores for a user or highest scores per user. Here we chose to begin with the easy approach and give you ideas for improvement, so you can practice your Python and Postgres skills as well as differentiate your game!

Python load game

Now that we have some score data in Postgres, we can easily retrive it using the query below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
s = ""
s += "SELECT TOP 1"
s += " id_screen"
s += ", i_hit_points"
s += ", i_points"
s += ", i_lives"
s += ", i_score_high"
s += " FROM tbl_games"
s += " WHERE id_user = " + id_user
# No "order by" needed here because we
# are only tracking highest score per user.
db_cursor.execute(s)
db_row = db_cursor.fetchone()
# Retrieve data from the row
id_screen = db_row[0]
i_hit_points = db_row[1]
i_points = db_row[2]
i_lives = db_row[3]
i_score_high = db_row[4]

Conclusion

In this part 9 of the multi-part series of articles teaching how to code a video game in Python using Arcade, we learned how to use Python Arcade for score keeping and Postgres for saving player score and reading Sprites to the screen. In the article following this one, we delve into giving the player the ability to change game difficulty as they play and we’ll make game difficulty affect points scored by the player. We also learned how to retrieve and save high score to PostgreSQL. In the articles that come next, we’ll add changing of game difficulty, which influences many areas of the game, including enemy movement. Depending on if there is enough demand, we will also add an online multiplayer mode and build a screen editor.

Pilot the ObjectRocket Platform Free!

Try Fully-Managed CockroachDB, Elasticsearch, MongoDB, PostgreSQL (Beta) or Redis.

Get Started

Keep in the know!

Subscribe to our emails and we’ll let you know what’s going on at ObjectRocket. We hate spam and make it easy to unsubscribe.