GUI in Python and Cockroach

Have a Database Problem? Speak with an Expert for Free
Get Started >>

Introduction

Welcome to the beginning of a multi-article tutorial on building a 2D arcade-style video game. In this game, the player is represented by a monkey in a zombie-infested world. Zombies chase the player. The user can move the monkey around the screen with either the arrow keys (cursor control keys) and/or pressing “A” for left, “D” for right, “W” for up, and “S” for down. The user can press the space bar to throw a brain. When the brain collides with a zombie (enemy), the player gets awarded points (modified by difficulty level) and the zombie head gets larger. When the player collides with a zombie, the player is “eaten”, loses a life, and respawns in the center of the screen. The user can press the “-” key to lower game difficulty and the “=” key to increase game difficulty.

In this part we learn to use to create a GUI in Python with Cockroach. The game GUI will be a foundation of the game, where all the activity occurs, including player movement, enemy movement, bullets (“brains” in this game), score display, game difficulty, etc. We use Python’s “Arcade” framework for its graphics-related functions. In this application, we used Cockroach to store game object data, in this case lego-looking blocks.

We chose Arcade for its efficiency and the dedication of the author to making continual improvements to the library. You can see how recent the latest changes have occurred by checking out their github here. Arcade is based on the Pyglet library and shares much of its functionality and syntax.

In future parts of this multi-article tutorial, we will learn to:

  • Create Sprites and Sprite lists comprised of Sprites for manipulating all the gui objects, including the player, blocks, bullets, and enemies.
  • Keyboard, waiting for and using keypresses by the user, so we can then move the player around the screen.
  • Enemy movement. We’ll use random() to decide whether an enemy will move randomly or move with brains (pun intended). If the “random coin toss” decides to move the enemy with intelligent intent, the movement direction will be set to be toward the player Sprite.
  • Bullets. We will create routines for initializing and moving “bullets”, which in this case are brains.
  • Collision. It’s important that we detect collisions between the different Sprites and reacting to those collisions. This includes the player hitting a lego block, enemies hitting a lego block, bullets hitting enemies, and enemies colliding with the player.
  • Sound. In this article, we will add a song playing in the background as well as various other sounds, including when the player is hit by an enemy or enemies are hit with bullets (brains).
  • Scoring. We’ll keep track of points, based on the difficulty level and display it at the bottom of the GUI.
  • Speed and difficulty. We’ll add key press detection for the player to adjust game speed during play. The difficulty integer will also increase/decrease points earned for shooting enemies as well as the intelligence of those enemies, i.e., how likely they are to move intelligently vs. in a more erratic manner.

Prerequisites

First, you may wish to study the Arcade framework because many of the functions in this application will depend on it.

We’ve made the full source code and all resources (images and sounds) available for download here.

Next, install the following frameworks for import into your project, using PIP. Then you can include them in your project using Python’s IMPORT command.

1
2
3
4
5
6
7
import arcade
import math
import random
import sys
import os
import pyautogui
from datetime import datetime, timedelta

Create GUI with Python

First we’ll merely look at how to set up a GUI using Arcade’s open_window().

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
39
40
41
42
43
44
45
46
# Create and populate constants
SCREEN_TITLE = "Zombie Feeder"
# Beginning difficulty level, which can be
# changed via the "-" and "+(=)" buttons.
DIFFICULTY = 4
# How many enemies?
ENEMY_COUNT_INITIAL = 8
ENEMY_SPEED = 2 + (DIFFICULTY/12)
# Set the following to true for
# harder game play where enraged
# enemies are immune to obstacles.
ENEMY_ENRAGED_IMMUNE_TO_BLOCKS = False
# Allow enemies to "teleport"
# from any edge of the screen
# to opposite edge.
# Set to False for easier, where
# the enemies will "bounce" from
# the screen edge.
ENEMY_SCREEN_EDGE_TRAVERSE = True
# Beginning size of all the Sprites
# on the screen.
SCALE = 0.25
PLAYER_STARTING_LIVES = 4
PLAYER_IMMUNE_TO_BLOCKS = True
MONITOR_RES_WIDTH, MONITOR_RES_HEIGHT = pyautogui.size()
SCREEN_WIDTH = 1920
SCREEN_HEIGHT = 1080
# Make sure SCREEN_WIDTH is not bigger than monitor width.
if SCREEN_WIDTH > MONITOR_RES_WIDTH:
    SCREEN_WIDTH = MONITOR_RES_WIDTH
# Make sure SCREEN_HEIGHT is not bigger than monitor width.
if SCREEN_HEIGHT > MONITOR_RES_HEIGHT:
    SCREEN_HEIGHT = MONITOR_RES_HEIGHT
# Number of obstacles are based on the screen width.
BLOCKS_NUMBER = int(SCREEN_WIDTH/20)
# Set up screen edges.
SPACE_OFFSCREEN = 1
LIMIT_LEFT = -SPACE_OFFSCREEN
LIMIT_RIGHT = SCREEN_WIDTH + SPACE_OFFSCREEN
LIMIT_BOTTOM = -SPACE_OFFSCREEN
LIMIT_TOP = SCREEN_HEIGHT + SPACE_OFFSCREEN
# Draw screens from database.
# If set to False, screens will
# be drawn using the random function
# to place obstacles.
SCREEN_FROM_DATABASE = False

Analysis of the code above:

  • Constants. Naming constants with uppercase is the standard way to name constants. Most Python interpreters will be case sensitive with functions and variables. Using constants allows us to more efficiently – when keeping our settings in code instead of a settings file or database – keep those settings we are most likely to NOT change during program execution but may want to change to tweak how the game works – all in one place in your code, typically at the top. Another benefit is we set up this application so that we only need to change a certain setting one time and the rest of the program will use that setting. For example, as we build the application, there will be different places where we use the constant named “DIFFICULTY”. If we instead used “4” in all those spots, any time we want to change the starting difficulty, we would have to make changes in many areas of our code instead of just the one near the top we see as “DIFFICULTY = 4”. Finally, this allows us to give descriptive names to the variables that control various game settings instead of numbers alone, which can be ambiguous in comparison.

  • SCALE. This controls the size of every Sprite we’ll later create in relation to the GUI height and width. So here a smaller number means smaller objects. We used PNG files for all our Sprites with transparent backgrounds and built them larger than they need to be for the game screen at 1200 x 900, so as to future proof the game for higher potential resolutions.

  • pyautogui.size(). This allows us to find out the resolution of the current monitor being used. As you can see, we received two values, width and height, from the function.

  • BLOCKS_NUMBER. This function matters when set SCREEN_FROM_DATABASE to False, so the random lego block generator knows how many obstacles to create. To determine that number, we divide screen width by 20. Decrease that number to get a higher density of lego blocks on the screen. You can also experiment with turning off collision detection for either the Player with lego blocks or the Enemies with lego blocks to change the flavor of the game some. As you see here, we set PLAYER_IMMUNE_TO_BLOCKS = True.

Next we’ll take the first and most important step in setting up a GUI and supplying it with the parameters you see below:

1
2
3
4
5
# Create a GUI and set resolution and window caption.
arcade.open_window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
# Set the GUI background
# See a list of color constants here http://arcade.academy/arcade.color.html.
arcade.set_background_color(arcade.color.BLACK)

Now to create a table in Cockroach for our screen data within the database, by opening a connection to Cockroach, querying a table, getting the position of our lego blocks from the records returned, and draw those objects to the screen as Sprites.

We’ll create two tables for storing lego block positions:

Create tables for storing GUI elements

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CREATE TABLE public.tbl_gui (
    id serial NOT NULL,
    id_session int4 NULL DEFAULT 0,
    id_user_created int4 NULL DEFAULT 0,
    t_gui_title VARCHAR(255) NULL,
    t_gui_msg VARCHAR(255) NULL,
    t_gui_notes VARCHAR(255) NULL,
    i_points_bonus int4 NOT NULL DEFAULT 0,
    i_gui_order int4 NULL DEFAULT 0,
    i_gui_width int4 NOT NULL DEFAULT 0,
    i_gui_height int4 NOT NULL DEFAULT 0,
    t_gui_bkg_color VARCHAR(24) NULL,
    d_gui_created DATE NULL DEFAULT now(),
    CONSTRAINT tbl_gui_pkey PRIMARY KEY (id)
);

Analysis

  • id serial: This is an auto-incrementing unique index for the table. VERY necessary for the foreign key in the next table we will build!
  • id_session: This column is for when a screen builder adds a new screen to the database and we want to most quickly and easily retrieve the new row id based on session, “id_user_created”, and “t_gui_title”.
  • id_user_created: In case we want to keep track of the user who created a given screen, allow only the creator to edit that screen they saved, and/or give them credit for what they built.
  • t_gui_title: The caption, which will appear in the title bar for the new window.
  • t_gui_message: A message we may choose to display when the user gets to this map or screen.
  • t_gui_notes: The screen maker may want to place a comment here.
  • i_points_bonus: Points the player is awarded for completing the screen.
  • i_gui_order: When we have more screens than just the current one for testing, we can track the order the screens are played.
  • i_gui_width and i_gui_height: The GUI resolution.
  • t_gui_bkg_color: Game GUI background color.
  • d_gui_created: Date the screen was built.

Now for the sub-table that we would link to the above CockroachDB table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE TABLE public.tbl_gui_objects (
    id serial NOT NULL,
    id_session int4 NULL DEFAULT 0,
    id_gui int4 NULL DEFAULT 0,
    b_collision_possible bool NULL DEFAULT FALSE,
    b_collision_destroys_it bool NULL DEFAULT FALSE,
    n_collision_awards NUMERIC NOT NULL DEFAULT 0,
    n_collision_damage NUMERIC NOT NULL DEFAULT 0,
    t_obj_name VARCHAR(128) NULL,
    t_obj_type VARCHAR(128) NULL,
    t_obj_image_URL VARCHAR(255) NULL,
    t_obj_sound_URL VARCHAR(255) NULL,
    t_obj_sound_movement_URL VARCHAR(256) NULL,
    t_obj_sound_collision_URL VARCHAR(256) NULL,
    i_obj_order int4 NOT NULL DEFAULT 1,
    i_obj_x int4 NULL DEFAULT 0,
    i_obj_y int4 NULL DEFAULT 0,
    i_obj_speed int4 NULL DEFAULT 100,
    CONSTRAINT tbl_gui_objects_pkey PRIMARY KEY (id)
);

Analysis

  • id and id_session: These have the same function as the corresponding columns in tbl_gui above.
  • id_gui: Via foreign keys we will tie this table to tbl_gui. This field points to the unique id field in tbl_gui. This will be a one to many relationship, where one row in tbl_gui points to a potential of many rows in this table (tbl_gui_objects).
  • b_collision_possible: Boolean column we used to determine if the player can collide with the object. This is another value we will ignore because in this version of the game, all lego blocks activate collision.
  • b_collision_destroys_it: Does the object disappear if hit by player?
  • n_collision_awards: How many hit points does the player receive when they collide with this object?
  • n_collision_damage: How many hit points damage, if any, does the player receive when they collide with this object?
  • t_obj_name: Name – may not be used.
  • t_obj_type: What kind of object? For now this means shape and we’ll use either “circle” or “rectangle”.
  • t_obj_image_URL: Image for the object.
  • t_obj_sound_movement_URL: Sound the object makes if/when moving. In this version of the game, the objects – lego blocks – do not move. But it doesn’t hurt to plan some for the future!
  • t_obj_sound_collision_URL: Sound for player to hear when they collide with the object.
  • i_obj_x and i_obj_y: Placement in x/y coordinates of the object.
  • i_obj_speed: How fast the object moves.

We’ll assume you added a certain amount of lego block location data to this table so you can now write a query to retrieve object data and draw that data as Sprites.

Query Cockroach for GUI

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
# -------------------------
# Database objects creation
# -------------------------
import psycopg2
t_dbname = "db name"
t_name_user = "db user name"
t_sslmode = "auto"
t_sslrootcert = 'certifs/ca.crt'
t_sslkey = 'certifs/client.maxroach.key'
t_sslcert = 'certifs/client.maxroach.crt'
t_host = "localhost"
t_port = "26251"
crdb_conn = psycopg2.connect(database=t_dbname, user=t_name_user, sslmode=t_sslmode, sslrootcert=t_sslrootcert, sslkey=t_sslkey, sslcert=t_sslcert, host=t_host, port=t_port)
crdb_cursor = crdb_conn.cursor()

# First step is to get a screen ID and title from tbl_gui from FIRST screen
s = ""
s += "SELECT TOP 1"
s += " id"
s += ", t_gui_title"
s += ", i_gui_width"
s += ", i_gui_height"
s += ", t_gui_bkg_color"
s += " FROM tbl_gui"
s += " ORDER BY i_gui_order"
try:
    crdb_cursor.execute(s)
    id_gui = crdb_cursor.fetch("id")
    t_gui_title = crdb_cursor.fetch("t_gui_title")
    i_gui_width = crdb_cursor.fetch("i_gui_width")
    i_gui_height = crdb_cursor.fetch("i_gui_height")
    t_gui_bkg_color = crdb_cursor.fetch("t_gui_bkg_color")
except psycopg2.Error as e:
    t_msg_to_user = "Database error: " + e + "/n SQL: " + s
    return render_template("error.html", t_msg_to_user = t_msg_to_user)
crdb_cursor.close

Analysis of the above Python script: Get screen object data from CockroachDB, including screen id, screen window title, dimensions, and background color. We used “SELECT TOP 1 id…” to be sure to retrieve only the first record determined with the “ORDER BY” at the end, which sorts by i_gui_order ascending.

Now we will query tbl_gui_objects to retrieve all the screen objects associated with the current game screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
s = ""
s += "SELECT"
s += " id"
s += ", t_obj_name"
s += ", t_obj_image_URL"
s += ", t_obj_sound_URL"
s += ", t_obj_sound_movement_URL"
s += ", t_obj_sound_collision_URL"
s += ", i_obj_x"
s += ", i_obj_y"
s += " FROM tbl_gui_objects"
s += " WHERE"
s += " ("
s += " id_gui = " + id_gui
s += " AND"
s += " t_obj_type = 'lego block'"
s += " )"
s += " ORDER BY i_obj_order"
try:
    crdb_cursor.execute(s)
except psycopg2.Error as e:
    t_msg_to_user = "Database error: " + e + "/n SQL: " + s
    return render_template("error.html", t_msg_to_user = t_msg_to_user)

The SQL above returns all object data from Cockroach for the game screen defined by the contents of the id_gui variable. Now we will loop through that recordset to draw game Sprites!

Sprites from records

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
arcade.start_render()

# Iterate through each row in the cursor recordset we opened above,
#   create a Sprite for each row, and draw that Sprite to the game
#   GUI. Note: We are only retrieving data we'll be using now
#   in this part 1 of the multi-article series.
for each rs_row in crdb_cursor:
    id_gui = rs_row[0]
    t_obj_image_URL = rs_row[2]
    i_obj_x = rs_row[6]
    i_obj_y = rs_row[7]
    # Create Sprite.
    # Draw the current lego block.
    sprite_block = arcade.Sprite("./resources/images/block.png", SCALE)
    sprite_block.center_x = i_x
    sprite_block.center_y = i_y
    sprite_block.draw
crdb_cursor.close
# Display the results of the accumulated draw commands above.
arcade.finish_render()
# Keep the GUI open until the user clicks close.
arcade.run()

NOTE: In that Python script above, we drew each Sprite immediately to the window. Since we want these objects to be detectable for collisions and other group-handling features, we will later (in subsequent articles of this series) add those Sprites to a Sprite List so we can utilize group-oriented functions like “Detect all collisions between any lego block and the player.”

Conclusion

This was the beginning of a multi-article tutorial on building a top-down 1980’s type game. In this part we learned to create a GUI with Python and Cockroach. The GUI will be a primary foundation of the game, where all the play happens, including player movement, enemy Sprite moving and brains, bullet firing, scoring, game speed/difficulty, music and sounds, etc. We used Cockroach to store GUI maps.

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.