Game Python Postgres Moving Monsters

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

Introduction

In this tutorial, we will continue creating a game with Python and Postgres with moving monsters as part 4 of a multi-part series of lessons where the final result will be a simple 2D graphical top-down view game like PacMan. We will use Python’s “Arcade” framework for the gaming-related features. We used PostgreSQL to read and write screen data. In part 1 we created two tables for that. In this lesson we will add: placing enemy Sprites on the screen and moving them using the random function. In the next lesson, we’ll learn how to address collisions in the game.

Prerequisites

See part 1 through 3, where we learned how to draw a screen, create tables in Postgres for storing and reading screen data and screen objects as Sprites, and reading the arrow keys to control player movement: Create game with Python and Postgres Part 1. For this lesson we will be using much of what we learned in part 1 through 3.

Random game enemy movement

Our goal is to create two enemies that move around the screen mostly randomly. First, we’ll use the random() function to get a random direction out of eight directions, which includes left, right, up, down, and the diagonal directions. then – in a future article – we’ll add a different random check – after the left/right/etc calculation – to possibly nudge the enemy toward the player. Later, if we choose to give the game variable difficulty level, that would influence the probability of the enemy moving toward the player, as well as enemy speed of movement.

Python random example

1
2
3
import random
x = random.random()
print (x)

Run the above code and you will get the following output, which is a random floating number between 0.0000000 and 0.9999999:

1
0.3578102

Now, if you don’t want to go through the hassle of converting that to an integer in the range you want, you can use the randint function like so:

1
2
3
4
import random
for y in range (0,4):
    x = random.randint(1, 6)
    print (x + ", ")

Output:

1
3, 1, 4, 6

NOTE: The parameters in the roundint function are inclusive, meaning, as you can see from the output above, 1 and 6 are both included as potential output numbers.

Also of note: Numbers generated via the above random functions will produce “pseudo-random” numbers, as there will be patterns. Do NOT use these functions for security-related applications. For those, we recommend looking at the “secrets” library. We are using the random library because it is faster and we do not need FULL randomness here.

Finally, one way to influence how truly random the random functions above are is to use the random.seed function to “seed” the random functions with a value, so as to disrupt patterns. If random.seed is not used, the system time is used as a seed.

Now we will move into the graphics aspect of our tutorial. We will be repeating some of the lessons from previous articles in this series, for purposes of ease and continuity but with changes.

Sprites with Arcade

We’ll begin with creating a simple window and then adding one Sprite to that 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
# use Python's arcade framework
import arcade

# Set up screen dimensions variables
win_title = "Python Wack-Man Arcade Game"
win_width = 1280
win_height = 720

# Open/create the window. Set size and title
arcade.open_window(win_width, win_height, win_title)

# Set window bkg color
# See http://arcade.academy/arcade.color.html for list of colors
arcade.set_background_color(arcade.color.BLACK)

# Create a player sprite
arcade.PlayerSprite = arcade.Sprite("game-graphics/icon-player.png")

# Set variables for the middle of the window
midpoint_x = win_width / 2
midpoint_y = win_height / 2

# Set position of the Player Sprite

# Set up global variables to keep track of the Player Sprite's coordinates.
player_x = midpoint_x
Player_y = midpoint_y

# Change the coordinates of our player Sprite
arcade.PlayerSprite.set_position(player_x, player_y)

# Keep up the game window until the user taps close
arcade.run()

We encourage you to play with the above example until you feel confident you understand the various arcade functions outlined in it. You can change various parameters, add a for loop to move the player in a line or along a path defined by a sin function, for example. Once you feel comfortable with those functions, continue on with the lesson.

Get screen from PostgreSQL

To get screen and object positions and function from the database, we’ll use a database. Please refer to part 1 in this series for the CREATE TABLE we used for both PostgreSQL tables. Why two? Tbl_screens will have a row per screen, while tbl_screens_objects will store all objects (player, enemies, and obstacles) using the indexed id_screen column to point up to the id column in tbl_screens.

Postgres tables to save game screen data

Here is tbl_screens with one row of data to represent screen 1 of the game:

idt_titlet_colori_widthi_height
1Screen 01black1280720

The following diagram shows tbl_screens_objects, for storing the objects on any given screen, which all will be Sprites. This table relates via foreign key to the above table by way of the id and id_screen columns.

id_screent_obj_namet_obj_typet_obj_img_URLb_hit_possiblen_hit_awardsn_hit_damaget_hit_sound
1Playerplayerplayer.pngtrue00
1Wallobstacleblock.pngtrue00hit_block.mp3
1Treeobstaclebush-01.pngtrue00hit_bush.mp3
1Poweruppoweruppower-up.pngtrue100power-up.mp3
1Enemy01enemyenemy-01.pngtrue010explode.mp3
1Enemy02enemyenemy-02.pngtrue010explode.mp3

NOTE: For tbl_screens_objects, we are showing more columns than in previous lessons because we are doing more with the Sprites now. We also left out the “id” field because it is irrelevant to this lesson.

IMPORTANT: Here and in the database, in order to (a) reduce amount of typing; and (b) reduce size of the above diagram, for easier display, we changed “collision” to “hit”, changed “object” to “obj”, and changed “image” to “img”.

Query PostgreSQL for screen data

We covered this part in detail in part 2 of this series, so here, we will leave out the queries and go straight to adding movement to our player sprite. That said, ALL will be in the final source code, including those queries.

Random sprite movement

Now we will plan out the function we’ll use to handle movement of the on-screen Player enemy Sprites. As mentioned above, their movement will be “mostly” random. Let’s write some code.

[Eight directions of Sprite movement](https://gyazo.com/1b866b42b69e736c3fdb2c2b765bc8a1 “Eight directions of Sprite movement”)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def enemyMovement(i_which_Sprite, x, y):
    direction = random.randint(1, 8)
    if direction == 1:
        y += 1
    if direction == 2:
        x += 1
        y += 1
    if direction == 3:
        x +=1
    if direction == 4:
        x += 1
        y -= 1
    if direction == 5:
        y -= 1
    if direction == 6:
        y -= 1
        x -= 1
    if direction == 7:
        x -= 1
    if direction == 8:
        x -= 1

The above function, enemyMovement, accepts three parameters:

  • i_which_Sprite: Which Sprite to move.
  • x: What is the current Sprite x coordinate?
  • y: What is the current Sprite y coordinate?

In the previous part of this multi-article series, we learned how to reposition a Sprite, so we’ll go ahead and do that in the full source code below.

Full source code

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import arcade # game-related functions
import psycopg2 # database manipulation
import random # new to our game application for this lesson
from flask import Flask
from flask import render_template # for sending those error messages

# connect to PostgreSQL database
t_host = "PostgreSQL database host address"
t_port = "5432"
t_dbname = "database name"
t_user = "database user name"
t_pw = "password"
db_conn = psycopg2.connect(host=t_host, port=t_port, dbname=t_dbname, user=t_user, password=t_pw)
db_cursor = db_conn.cursor()

# initialize screen variables
#  to be global
id_screen = 0
t_title = ""
i_width = 0
i_height = 0
t_color = ""

@app.route("/main")

def getScreenFromDB():
    # Get a screen ID, color, width, height, and title from tbl_screens from FIRST screen
    s = ""
    s += "SELECT TOP 1"
    s += " id"
    s += ", t_title"
    s += ", t_color"
    s += ", i_width"
    s += ", i_height"
    s += " FROM tbl_screens"
    s += " ORDER BY i_order"
    try:
        db_cursor.execute(s)
        id_screen = db_cursor.fetch("id")
        t_title = db_cursor.fetch("t_title")
        i_width = db_cursor.fetch("i_width")
        i_height = db_cursor.fetch("i_height")
        t_color = db_cursor.fetch("t_color")
    except psycopg2.Error as e:
        t_message = "Database error: " + e + "/n SQL: " + s
        return render_template("error.html", t_message = t_message)
    db_cursor.close

def getObjectsFromDB():
    # Following are all the columns in our tbl_screens_objects
    # Out of all the columns you see below, we are only using a few
    # in THIS part of the multi-part series. We are leaving the extra
    # columns in so that you can (a) see where we are heading; and
    # (b) have less to change when you get to the next part of this series.
    s = ""
    s += "SELECT"
    s += " id"
    s += ", t_obj_name"
    s += ", t_obj_type"
    s += ", t_obj_img_URL"
    s += ", i_x"
    s += ", i_y"
    s += ", t_color_hit"
    s += ", t_properties"
    s += ", t_hit_sound"
    s += ", t_hit_action"
    s += ", b_hit_possible"
    s += ", b_hit_destroys_it"
    s += ", b_hit_awards"
    s += ", n_hit_awards"
    s += ", n_hit_damage"
    s += " FROM tbl_screens_objects"
    s += " WHERE ("
    s += " id_screen = " + id_screen
    s += ")"
    s += " ORDER BY i_order"
    try:
        db_cursor.execute(s)
        return db_cursor
    except psycopg2.Error as e:
        t_message = "Database error: " + e + "/n SQL: " + s
        return render_template("error.html", t_message = t_message)

class gameWindow(arcade.Window):
    def__init__(self, i_width, i_height, t_title):
    super().__init__(width, height, title, resizable=false)
    self.set_location(100, 100)

    arcade.set_background_color(arcade.color.t_color)

    self.player_x = 100
    self.player_y = 200
    self.player_speed = 250

    self.right = false
    self.left = false
    self.up = false
    self.down = false

    # initialize a sprite list to store objects via append method
    self.SpriteObjects = arcade.SpriteList()

    # iterate through each row in db_cursor
    i = 0
    for each db_row in db_cursor:
        id_screen = db_row.fetch("id")
        t_obj_name[i] = db_row.fetch("t_obj_name")
        t_obj_type[i] = db_row.fetch("t_obj_type")
        t_obj_img_URL = db_row.fetch("t_obj_img_URL")
        i_x = db_row.fetch("i_x")
        i_y = db_row.fetch("i_y")
        # draw each sprite in db_row to screen
        #  ignoring if player because we already created player as sprite1 above.
        #  This assumes we didn't filter out "player" by changing our SQL WHERE clause.
        if t_object_type == "obstacle":
            self.SpriteObject = arcade.Sprite(t_object_image_URL, i_x, i_y)
            # add current obstacle or enemy (sprite) to growing list of Sprites (SpriteList)
            self.SpriteObjects.append(self.SpriteObject)
        elif t_object_type == "enemy":
            self.SpriteObstacle = arcade.Sprite(t_object_image_URL, i_x, i_y)
            # add current obstacle or enemy (sprite) to growing list of Sprites (SpriteList)
            self.SpriteObjects.append(self.SpriteObject)
        elif t_object_type == "Player":
            self.SpritePlayer = arcade.Sprite(t_object_image_URL, i_x, i_y)
        i += 1

def enemyMovement():
    for currentSprite in self.SpriteObjects:
        x = currentSprite.position[0]
        y = currentSprite.position[1]
        direction = random.randint(1, 8)
        if direction == 1:
            y += 1
        if direction == 2:
            x += 1
            y += 1
        if direction == 3:
            x +=1
        if direction == 4:
            x += 1
            y -= 1
        if direction == 5:
            y -= 1
        if direction == 6:
            y -= 1
            x -= 1
        if direction == 7:
            x -= 1
        if direction == 8:
            x -= 1
            y += 1
        currentSprite.set_position(x, y)

def on_draw(self):
    self.SpritePlayer.draw()
    self.SpriteObjects.draw()

def on_update(self, delta_time):
    # delta_time is last time same function was run;
    #   used to scale movement to processing speed
    if self.right = true:
        self.player_x += self.player_speed * delta_time
    if self.left = true:
        self.player_x -= self.player_speed * delta_time
    if self.up = true:
        self.player_y += self.player_speed * delta_time
    if self.down = true:
        self.player_y -= self.player_speed * delta_time

    self.SpritePlayer.set_position(self.player_x, self.player_y)
    # call function to move all enemy Sprites
    enemyMovement()
    self.SpriteObjects.update()

def on_key_press(self, symbol, modifiers):
    # every time a key is pressed, this function is called
    if symbol == arcade.key.right:
        self.right = true
    if symbol == arcade.key.left:
        self.left = true
    if symbol == arcade.key.up:
        self.up = true
    if symbol == arcade.key.down:
        self.down = true

def on_key_release(self, symbol, modifiers):
    # every time a key is released, this function is called
    if symbol == arcade.key.right:
        self.right = false
    if symbol == arcade.key.left:
        self.left = false
    if symbol == arcade.key.up:
        self.up = false
    if symbol == arcade.key.down:
        self.down = false

Def main():
    getScreenFromDB()
    db_cursor = getObjectsFromDB()
    gameWindow(i_width, i_height, t_title)
    enemyMovement()
    arcade.run()

Conclusion

In this tutorial, we continued creating a 2D game with Python and Postgres with moving enemies as part 4 of a multi-part series of lessons where the final result will be a simple graphical top-down view game like PacMan. We will use Python’s “Arcade” framework for the gaming-related features. We used Postgres to read and write screen data. In part 1 we created tables for that. In this lesson we added: placing enemy Sprites on the screen and moving them using the random function. In the next lesson, we’ll learn how to handle collisions.

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.