A full-fledged game made by me in a regular windows console

Hello!



Today I will describe in detail how I made the game on the command line, and how good it turned out.



Where does the idea come from?



I was inspired by the idea of ​​doing something simple at first glance, but at the same time interesting in terms of development. The idea came to my mind to make a game in the console, it is interesting in terms of development, and it will be interesting to look at it even from the outside, such as this game.



Game engine



So, let's start with how the game is organized at the root, and what is its idea of ​​work.



First, I decided on how the game world would be displayed on the console. I realized that in order to display game objects, we need a list that stores other lists that contain symbols, which are subsequently displayed on the playing field in a loop for.



With this code:



for line_words in OUTPUT_IMAGE:
       for word in line_words:
           print(word, end="")
       print("\n", end="")


Here we draw all the characters from the list, and go to a new line to draw the next list of characters.



This is how the variable that stores the lists of symbols looks like:



image



Here we immediately get a decision on how to display objects in X and Y, we can now specify:



X - a symbol in the list

Y - a list that contains X

Thus, draw some symbol on the field ... We will use this when drawing game objects.



We can try to draw a "ball" on the field, substituting the letter "O" for X and Y.



To do this, write the following code:



import os
OUTPUT_IMAGE = [
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        ]

OUTPUT_IMAGE[4][6] = "O"
os.system("cls||clear")
for line_words in OUTPUT_IMAGE:
       for word in line_words:
           print(word, end="")
       print("\n", end="")



image



And so, we have drawn an object on our playing field. True, the X and Y coordinates are not classic. Firstly, we indicate first Y, then X, which is not quite according to the classics, and secondly, the Y coordinate should increase in order to raise the object, in our case, on the contrary, it should decrease.



Graph of X and Y in the game:



image



This feature will also have to be taken into account later when we do collisions of objects in the console.



Now we can try to move our object across the playing field, i.e. create movement.



We will need to clear the console in order to erase the old picture of the playing field.

We will do this with the command:



os.system("cls||clear")


Also, we need to override the variable OUTPUT_IMAGEin order to clear all objects previously drawn in the playing field.



We will also need to put all this in while True.



Let's add to the while Truefunction time.sleep(1)in order to limit the FPS.



And so, the code was drawn before our eyes:



from time import sleep
from os import system
OUTPUT_IMAGE = [
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
        ]

x = 0
y = 0
while True:
      sleep(1)
      system("cls||clear")
      OUTPUT_IMAGE[y][x] = "O"
      for line_words in OUTPUT_IMAGE:
             for word in line_words:
                 print(word, end="")
             print("\n", end="")
      y += 1
      x += 1
      OUTPUT_IMAGE = [
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            ]


image



Now we have the ability to distribute objects across the field.



True, these objects are too primitive, and we would have to learn how to draw complex objects like players, houses, food ...



In order to draw a complex object, we need to understand and figure out how to draw an object by specifying only once its X and Y.



For this we need a function that accepts a picture (symbols), X, Y;



Let's do this:



def SetImage(image: str, x: int, y: int):
    pass


Now we need to implement it. To do this, you need to decide how to draw an image that stretches along the X and Y axes, I came up with this:

draw an object by dividing it into symbols, and as soon as the "\ n" character is encountered, add the Y axis.



The Y axis, as we said, is incorrect, reversed, so we add to it to lower the object.



An example of an image that is drawn according to my principle:



image = " O\n'|'\n |"#


Now let's describe this in our function:



def SetImage(x: int, y: int, image: str):
  x_start = x
  x = x
  y = y
  for word in image:
      if word == "\n":
          x = x_start
          y += 1
      else:
          x += 1
          try:
            OUTPUT_IMAGE[y][x] = word
          except IndexError:
              break


Let's add try: except()in order to avoid errors if the object has X and Y too small or too large.



x_startThis is the X, from which we need to start drawing when increasing Y (with the "\ n"



character ) Now we can use our function, drop X and Y into it, and the picture that needs to be drawn:



the code
from time import sleep
from os import system
OUTPUT_IMAGE = [
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      ]

def SetImage(x: int, y: int, image: str):
  x_start = x
  x = x
  y = y
  for word in image:
      if word == "\n":
          x = x_start
          y += 1
      else:
          x += 1
          try:
            OUTPUT_IMAGE[y][x] = word
          except IndexError:
              break
while True:
      sleep(1)
      system("cls||clear")
      SetImage(x=3,y=4,image=" O\n'|'\n |")
      for line_words in OUTPUT_IMAGE:
             for word in line_words:
                 print(word, end="")
             print("\n", end="")
      OUTPUT_IMAGE = [
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            ]




And this is what we got:



image



just like the ball we drew, it can be moved along the X and Y axes.



the code
from time import sleep
from os import system
OUTPUT_IMAGE = [
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      ]
px = 0
py = 0
def SetImage(x: int, y: int, image: str):
  x_start = x
  x = x
  y = y
  for word in image:
      if word == "\n":
          x = x_start
          y += 1
      else:
          x += 1
          try:
            OUTPUT_IMAGE[y][x] = word
          except IndexError:
              break
while True:
      sleep(1)
      system("cls||clear")
      SetImage(x=px,y=py,image=" O\n'|'\n |")
      for line_words in OUTPUT_IMAGE:
             for word in line_words:
                 print(word, end="")
             print("\n", end="")
      px += 1
      py += 1
      OUTPUT_IMAGE = [
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            ]




image



And now, the player is already moving around the map.



Here we have already done a lot, there is already a player, there is already a map, and it would seem that it is already possible to make a game, but no. We need a function for calculating collisions of objects, because what kind of game is it without object interactions? So let's get started.



First, we need to make a function to get the latitude and height of an object in order to calculate its hitbox.



So, I decided to make the function according to the following logic:



X - the hitbox of the object in X width, this is the largest number of characters between the characters "\ n" in the picture

Y - the hitbox in Y is the number of characters "\ n" in the picture



By this logic it is not difficult make a function that takes a picture, counts all the characters between "\ n" for it, and selects the largest number of characters from this - the latitude is obtained.

And if you count the characters "\ n", as I already wrote, you get the height.



The function turned out like this:



def GetSizeObject(img: str):
    w = 0
    weights = []
    h = [word for word in img if word == "\n"]

    for word in img:
      if word == "\n":
          weights.append(w)
          w = 0
      else:
          w += 1
      try:
          return {"w": max(weights), "h":len(h)}
      except ValueError:
            return {"w": 0, "h":0}



Why is ValueError except here?
It is here to prevent an error when starting the game.



So let's draw our player, and calculate its width and length.



code with drawing and calculating latitude and height
from time import sleep
from os import system
OUTPUT_IMAGE = [
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      ]
px = 3
py = 3
def SetImage(x: int, y: int, image: str):
    global OUTPUT_IMAGE      
    x_start = x
    x = x
    y = y
    for word in image:
        if word == "\n":
            x = x_start
            y += 1
        else:
            x += 1
            try:
              OUTPUT_IMAGE[y][x] = word
            except IndexError:
                break

def GetSizeObject(img: str):
    w = 0
    weights = []
    h = [word for word in img if word == "\n"]
    h.append(1)

    for word in img:
        if word == "\n":
            weights.append(w)
            w = 0
        else:
            w += 1
    try:
        return {"w": max(weights), "h":len(h)}
    except ValueError:
        return {"w": 0, "h":0}

player_image = " O\n'|'\n |"
def draw():
      global OUTPUT_IMAGE
      sleep(1)
      system("cls||clear")
      for line_words in OUTPUT_IMAGE:
             for word in line_words:
                 print(word, end="")
             print("\n", end="")
      OUTPUT_IMAGE = [
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            ]
while True:
    SetImage(x=px,y=py,image=player_image)
    print(GetSizeObject(img=player_image))
    draw()





Hooray! we have a function for calculating latitude and height, now we have to make a function for calculating the hitbox and collisions of objects.



Let's remember that our coordinate system is not a classical one, so, alas, we cannot use the classical function, we will have to make our own. To do this, I drew 2 squares on the graph that collide, and from this picture you can come up with a condition by which the collision will be calculated.



For ease of understanding, I drew hitboxes, i.e. squares:



image



Logic in words




x β€” X

y β€” Y

h β€”

w β€”

x2 β€” X

y2 β€” Y

h2 β€”

w2 β€”



:





y y2 - h2 + h y - h y2 + h2 - h



y2 y - h + h2 y2 - h2 y + h - h2

2 ?
2 , - / .



Y



X, Y, y β€” x, h β€” w.



:



x x2 - w2 + w x - w x2 + w2 - w







x2 x - w + w2 x2 - w2 x + w - w2



X



Logic in code
, :



def IsClash(x: int, y: int, h: int, w: int,x2: int, y2: int, h2: int, w2: int):
    if (y >= y2 - h2 + h and y - h <= y2 + h2 - h) or (y2 >= y - h + h2 and y2 - h2 <= y + h - h2):
        if (x >= x2 - w2 + w and x - w <= x2 + w2 - w) or (x2 >= x - w + w2 and x2 - w2 <= x + w - w2):
            return True

    return False


True , False .



I additionally drew a cube on our playing field so that the player has someone to face.



And I tried how the collision calculation function works.



Here is a player touching a cube:



image



But no touching:



image



Contact code
/ :



from time import sleep
from os import system
OUTPUT_IMAGE = [
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
      ]

def SetImage(x: int, y: int, image: str):
    global OUTPUT_IMAGE      
    x_start = x
    x = x
    y = y
    for word in image:
        if word == "\n":
            x = x_start
            y += 1
        else:
            x += 1
            try:
              OUTPUT_IMAGE[y][x] = word
            except IndexError:
                break

def GetSizeObject(img: str):
    w = 0
    weights = []
    h = [word for word in img if word == "\n"]
    h.append(1)

    for word in img:
        if word == "\n":
            weights.append(w)
            w = 0
        else:
            w += 1
    try:
        return {"w": max(weights), "h":len(h)}
    except ValueError:
        return {"w": 0, "h":0}

def IsClash(x: int, y: int, h: int, w: int,x2: int, y2: int, h2: int, w2: int):
    if (y >= y2 - h2 + h and y - h <= y2 + h2 - h) or (y2 >= y - h + h2 and y2 - h2 <= y + h - h2):
        if (x >= x2 - w2 + w and x - w <= x2 + w2 - w) or (x2 >= x - w + w2 and x2 - w2 <= x + w - w2):
            return True

    return False

player_image = " O\n'|'\n |"
cube_image = "____\n|  |\n----"
cx = 5#
cy = 4  #          
px = 10  #
py = 3#
def draw():
      global OUTPUT_IMAGE
      sleep(1)
      system("cls||clear")
      for line_words in OUTPUT_IMAGE:
             for word in line_words:
                 print(word, end="")
             print("\n", end="")
      OUTPUT_IMAGE = [
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".",],
            ]
while True:
    SetImage(x=px,y=py,image=player_image)
    SetImage(x=cx,y=cy,image=cube_image)
    print("is clash: ",IsClash(
      x=px,
      x2=cx,
      y=py,
      y2=cy,
      h=GetSizeObject(img=player_image)["h"],
      h2=GetSizeObject(img=cube_image)["h"],
      w=GetSizeObject(img=player_image)["w"],
      w2=GetSizeObject(img=cube_image)["w"],
      ))
    draw()





Now we have all the starting functions for the game, in fact, I wrote my game based on them.



A game



The idea of ​​the game is as follows:



There is a player, food appears around him, which he is forced to collect so as not to die. The game also has functions: pick up food, put it in the inventory, eat it from the inventory, put an item from the inventory on the



floor.I started by making a game loop in 3 lines, it's simple While True:



from time import sleep
while True:
    sleep(0.1)


Then I considered it necessary to create a class in which all the functions of future objects will be stored. Therefore, I created a main.py file and a lib folder, into which I placed the lib.py file in which the game class was. THOSE. the game files looked like this:



+----game
|    + -- 
|    | -- main.py
|    \ --lib
|         +--lib.py -> class Game()
|         \
|
+---


In the future, I worked mainly with the Game () class, in main.py I just called it, created starting objects, started the game.



In the game class, I made a run () function that starts the game loop. Also made the draw_all () function, it erases all past objects, draws new ones, and prints to the playing field.



And this is how the class looked like:



from time import sleep


class Game():
    def __init__(self):
        self.OUTPUT_IMAGE = []  #   

    def draw_all(self):
        for line_words in self.OUTPUT_IMAGE:
            for word in line_words:
                print(word, end="")
            print("\n", end="")

    def run(self):
        while True:
            self.draw_all()
            sleep(0.1)



I added all the basic functions of the type set_image(), size_object(), is_clash(), and all those who are game engine, and which I have described above.



Made a new function create_object()and a variable self.OBJECTS, a function create_object()I use to create objects, it takes the parameters img, name, x, y, up, rigid, data.



img- object picture - object

namename (house, grass, inhabitant, food, etc.)

x- object X - object

yY

up- if this parameter is True, then the object is drawn above the player, otherwise the player overlaps it

rigid- hardness, the player cannot go through this object (not yet implemented)

data- personal data of the object, its personal characteristics



create_object ()
:



def CreateObject(self,x: int, y: int, img: str, name: str = None, up: bool = False, rigid: bool = False, data: dict = {}):
    size_object = self.GetSizeObject(img=img)
    self.OBJECTS.append(
        {"name": name,
         "x": x,
         "y": y,
         "up": up,
         "rigid": rigid,
         "h":size_object["h"],
         "w":size_object["w"],
         "id":uuid4().hex,
         "data":data,
         "img": img}
    )




At that time, I already added a player, a house, grass, and a villager.



And I decided to use the same parameter in the object up, use it in the object Home, i.e. so that the house covers the player. To do this, I made the CheckAll () function, a for loop went through all the objects, and drew them on the outgoing picture, i.e. use the SetImage (x: int, y: int, img: str) function, supplying the X and Y of the object, and a picture.



Thus, he drew objects that the player could close himself. In the same cycle, I declared a list up_of_payer_objects, and if the object had up = True, then I added it to the list without drawing it on the field. After that I drew the player himself, and only then I went through the for loop over the objects in up_of_payer_objects, drawing them, thus they were above the player.



def CheckAll(self):
    up_of_payer_objects = []
    for object_now in range(len(self.OBJECTS)):
        if object_now["up"]:
            up_of_payer_objects.append(object_now)
            continue
        self.SetImage(x=object_now["x"],y=object_now["y"],image=object_now["img"])


Then I started moving the player. For this, I created it as a separate object, which is not in the list self.OBJECTS, but which is stored in a variable self.PLAYER.



All its parameters according to the type X, Y, img, itp You can get it using keys, in other words it is a dictionary (dict). With such a player and objects it was already possible to work, move, calculate collisions. I started by moving.

I started creating motion by making the CheckKeysObjects () function, which is responsible for tracking keystrokes, and which I call in the CheckAll () function at the very beginning



def CheckAll(self):
    self.CheckKeysObjects()
    ....


To track keystrokes, I used the keyboard library , and 4 variables: And everything turned out to be simple, we track the keys, and if it is pressed , we make a variable . At the very beginning of the function, we declare all variables in , in order to reset all past results, otherwise the player will not stop.



self.WALK_LEFT_PLAYER

self.WALK_RIGHT_PLAYER

self.WALK_UP_PLAYER

self.WALK_DOWN_PLAYER



dself.WALK_RIGHT_PLAYERTrue



False



CheckKeysObjects ()
def CheckKeysObjects(self):
    #    False,    
    self.WALK_LEFT_PLAYER = False
    self.WALK_RIGHT_PLAYER = False
    self.WALK_UP_PLAYER = False
    self.WALK_DOWN_PLAYER = False
    #    
    if keyboard.is_pressed("a"):
        self.WALK_LEFT_PLAYER = True
    elif keyboard.is_pressed("d"):
        self.WALK_RIGHT_PLAYER = True
    if keyboard.is_pressed("w"):
        self.WALK_UP_PLAYER = True
    elif keyboard.is_pressed("s"):
        self.WALK_DOWN_PLAYER = True




After that, in the function, I CheckAll()check all the variables responsible for the movement, find out where the player is moving.



If any is in True, find out which one, and move the object in the opposite direction.



The resulting movement code
def CheckAll(self):
    self.CheckKeysObjects()  # check moves
    up_of_payer_objects = []
    for object_now in range(len(self.OBJECTS)):
        self.PLAYER["img"] = self.PLAYER["image_normal"]
        if self.WALK_LEFT_PLAYER:
            self.OBJECTS[object_now]["x"] += 1

        elif self.WALK_RIGHT_PLAYER:
            self.OBJECTS[object_now]["x"] -= 1


        if self.WALK_UP_PLAYER:

            self.OBJECTS[object_now]["y"] += 1
        elif self.WALK_DOWN_PLAYER:

            self.OBJECTS[object_now]["y"] -= 1




Yes, we move objects in the opposite direction in order to create the illusion of movement. If the player goes to the right, then all objects of the environment are shifted to the left.



Then I added more environmental items, and started spawning food, the player's goal is to collect food so as not to die.



For the countdown of the food spawn time, I used a simple one time.sleep(), and a library threading- in order to run 2 functions at the same time, food spawn and the main game loop. The food spawn function SpawnEat()is just a function that, when launched, generates food at random places, calling a function for each unit of food CreateObject().



Also, once I made the food spawn function, I made the player variableself.PLAYER["hungry"], this is his hunger, at the very beginning it is equal to 100 units, I will decrease it if the player walks and spends energy (such as energy, it is not in the game) or increase it if the player ate something.



I also made a function MinimizeHungry(), it is called every 5 seconds, and it just takes 2 units of hunger from the player. I did this so that the player had to move, and not stand still.



Finally, in a function Eat(), this function is called on a separate thread from the game loop. She checks if there is too much food on the map, if food is more than 10 units. it does NOT call the function SpawnEat()if less than 10 units. then calls SpawnEat().



Here's how it turned out:



Eat ()
def Eat(self):
    while True:
        sleep(4)
        if len([i for i in self.OBJECTS if i["name"] == "meat"]) < 10:
            self.SpawnEat()
        sleep(1)
        self.MinimizeHungry()




Function Start()to start the main loop:



Start ()
def Start(self):
    while True:  
        self.CheckAll()
        self.DrawAll()
        sleep(0.01)




And a function run()that launches the entire game.



run ()
def run(self):
    proc1 = threading.Thread(target=self.Start)
    proc1.start()
    proc2 = threading.Thread(target=self.Eat)
    proc2.start()




The process of eating itself, I implemented simply in the function CheckAll()and CheckKeysObjects(). Q CheckKeysObjects()I checked to see if the player pressed the button E. If pressed, then put the variable self.PRESS_Ein True.



In the loop CheckAll(), I checked if the current object in the loop was forfood, if the food then checked if the player collided with it, if it collided, then checked the variable self.PRESS_E, and if it Truethen simply deleted the object and increased hunger, i.e. variable self.PLAYER["hungry"].



This is how it is in the code
for object_now in range(len(self.OBJECTS)):
    ....
    if self.OBJECTS[object_now]["name"] == "meat":
        items_objects.append(object_now)
        is_clash = self.IsClash(
            x=self.OBJECTS[object_now]["x"],
            y=self.OBJECTS[object_now]["y"],
            h=self.OBJECTS[object_now]["h"],
            w=self.OBJECTS[object_now]["w"],
            x2=self.PLAYER["x"],
            y2=self.PLAYER["y"],
            h2=self.PLAYER["h"],
            w2=self.PLAYER["w"],
        )

        if is_clash:
            if self.PRESS_E:
                try:
                    self.PLAYER["hungry"] += self.HUNGRUY_ADD
                    del self.OBJECTS[object_now]
                    break

                except IndexError:
                    pass




I will say in advance, I will need to rewrite all this when I do the inventory


Making inventory



So, it's hard, we need to make an inventory.



The difficulty is that all objects will need to be displayed, stored history, deleted, placed objects on the floor.



I started by adding a new key to the player, it was self.PLAYER["inventory"], 4 cells are stored there, like this:



"inventory":{
    "0":{"status":"space","name":"#0", "minimize_image":"#0"},
    "1":{"status":"space","name":"#1", "minimize_image":"#1"},
    "2":{"status":"space","name":"#2", "minimize_image":"#2"},
    "3":{"status":"space","name":"#3", "minimize_image":"#3"},
}


Are just cell numbers.



status- this key stores the value whether the egg cell is empty or not. If empty then "space", if there is an item, then the name of the item is stored there.



name- stores the name of the item, it will be used when the player puts the item.



minimize_image- this is a small picture of the item that is displayed in the player's inventory.



After, I made new checks in ours CheckKeysObjects(), when you click on the Xitem, it will throw itself to the ground, and when you click on the button, the Efunction will be called self.UseEat(), which we will now analyze.



So the functionself.UseEat()is a passage through all the cells of the inventory, in search of food, and if food is found, then it is removed from the inventory, and 10 units are added to hunger. To remove an item from the inventory, I made a function self.DestroyItem()in which the cell index is supplied, and the entire cell simply becomes empty by default and without anything.



self.DestroyItem ()
def DestroyItem(self,index_item: str):
    item = self.PLAYER["inventory"][index_item]
    self.PLAYER["inventory"][index_item] = self.PLAYER["default_inventory_item"](index_item)
    self.PLAYER["inventory_must_update"] = True
    return item




self.CheckKeysObjects ()
def CheckKeysObjects(self):
    self.WALK_LEFT_PLAYER = False
    self.WALK_RIGHT_PLAYER = False
    self.WALK_UP_PLAYER = False
    self.WALK_DOWN_PLAYER = False
    if key("a"):
        self.WALK_LEFT_PLAYER = True
    elif key("d"):
        self.WALK_RIGHT_PLAYER = True
    if key("w"):
        self.WALK_UP_PLAYER = True
    elif key("s"):
        self.WALK_DOWN_PLAYER = True
    if key("f"):
        self.KEY_F = True
    else:
        self.KEY_F= False
    if key("e"):
        self.UseEat()




self.UseEat ()
def UseEat(self):
    for inventory_item in range(len(self.PLAYER["inventory"])):
        if self.PLAYER["inventory"][str(inventory_item)]["name"] == "meat":
            if self.PLAYER["hungry"] + self.ADD_HUNGRY_COUNT < 100.0:
                self.PLAYER["hungry"] += self.ADD_HUNGRY_COUNT
                self.DestroyItem(index_item=str(inventory_item))




Next is the function of throwing an object to the ground.



There is, however, nothing complicated, when you click on the Xfunction is called self.QuitItem(), the for loop goes through all the cells of the inventory, and if the key is ["status"]not equal "space", then we delete this cell using the previously considered function self.DestroyItem(), and create an object based on what was in the cell, X and Y puts the player as if he had thrown him next to him.



self.Quititem ()
def QuitItem(self):
    for inventory_item in range(len(self.PLAYER["inventory"])):
        if self.PLAYER["inventory"][str(inventory_item)]["status"] != "space":
            self.CreateObject(
                img=self.PLAYER["inventory"][str(inventory_item)]["img"],
                x=self.PLAYER["x"],
                y=self.PLAYER["y"],
                name=self.PLAYER["inventory"][str(inventory_item)]["name"],
                data=self.PLAYER["inventory"][str(inventory_item)]["data"],
            )
            self.DestroyItem(index_item=str(inventory_item))
            break




And yet all, many things I did not say how I did, T.K. they were not the main part of the game, albeit interesting. For example, messages about the possibility of picking up an item or not (when the inventory is full), that I added a walking animation, that I made a separate library of pictures, and other things.



That's all?



No, I'm going to add a neural network to the game, using a library that I wrote in Python,

I'm going to make the player's interaction with NPCs equipped with a neural network, a

small, but some kind of plot, and also some supplies for the player, such as armor, food. items, the ability to build in blocks.



Try the game



You can freely download it from my GitHub, you only need Python3 to run it, and the keyboard library . You need to run the file main.py.



A game



All Articles