Page 1 of 1

Game input from Console and Game loops

#1 DK3250  Icon User is offline

  • Pythonian
  • member icon

Reputation: 292
  • View blog
  • Posts: 922
  • Joined: 27-December 13

Posted 09 April 2016 - 07:08 AM

Game input from Console and Game loops

From the Python Help Forum here on Dream In Code, I have seen two questions asked again and again by beginners into Game Programming.

It appears that:

A. Input from Keyboard (with validation)
B. Game Loop (Game Restart)

..are problematic for many. This small tutorial addresses those two issues.

Input from keyboard (with validation)
Looking back through the help forum, many non-working code snippets can be found. The one presented below was originally issued by baavgai:
def run_again():
    result = None
    while result == None:
        choice = input("Run again? y/n: ")
        if choice in ["y", "Y"]:
            result = True
        elif choice in ["n", "N"]:
            result = False
        else:
            print("Invalid input")
    return result


if run_again():
    # Your code here...


This code is excellent for simple yes/no input. But what if you are playing a number guessing game and want to validate input as integer? You can utilize that build-in string method. str.isdigit()
def get_integer():
    number = None
    while number == None:
        value = input("Guess number: ")
        if value.isdigit():
            number = int(value)
        else:
            print("Invalid input")
    return number


guess = get_integer()
print(guess)


More string methods exists, here str.isalpha is demonstrated for validation of a single word string:
def get_string():
    result = None
    while result == None:
        string = input("Enter your name: ")
        if string.isalpha():
            result = True
        else:
            print("Invalid input")
    return string


name = get_string()
print(name)


Look up the many other string methods in the documentation: https://docs.python....y/stdtypes.html section 4.7

If, however, the input is a float; you need to create your private validation table:
def get_float():
    number = None
    while number == None:
        value = input("Enter decimal number: ")
        if (all(digit in "0 1 2 3 4 5 6 7 8 9 .".split(" ") for digit in value)  # check of digits
            and value.count(".") <= 1):  # check for max one decimal point
            number = float(value)
        else:
            print("Invalid input")
    return number


number = get_float()
print(number)


I'm sure you have got the idea now...


Game Loop

Many post address the question of how to restart a game and possibly have a start screen, an end screen, a high score screen, etc.

Let's first look at a simple example, just to get the idea; later I'll expand it a bit.

The first example introduces the idea of a ”'Finite State Machine' Pattern”.
This pattern lets the value of a controlling variable (her called 'state') direct the game through a sequence of functions.
Depending on the value of 'state' different functions are called. When exiting the functions, the 'state' value may (or may not) be updated – depending on the game situation.
For better understanding, scroll down to line 112 of the example where this is described, - the loop starts in line 127.
All code before line 112 are various functions; each function is explained right after the def-line.
"""
Number guessing game by DK3250

The intention with this program is to introduce the
Finite State Machine Pattern for game control.

A variable, here named 'state', is controlling the game in the loop
starting line 127
"""

import random, sys

def get_integer():
    """
    This function is explained earlier in the tutorial.
    The function gets the user input and validate it to be an integer
    """
    number = None
    while number == None:
        value = input("Guess number: ")
        if value.isdigit():
            number = int(value)
        else:
            print("Invalid input")
    return number


def yes_no(txt):
    """
    This function is the same as the tutorial 'run_again()'
    Renamed here for a more general use.

    Furthermore it demonstartes how the printed text can be send as argument to the function
    See line 80 and 97; two different questions use this single function. 
    """
    result = None
    while result == None:
        choice = input(txt)
        if choice in ["y", "Y"]:
            result = True
        elif choice in ["n", "N"]:
            result = False
        else:
            print("Invalid input")
    return result


def game():
    """
    This is the primary game state, executing the central game core.
    When entering this function, you'll loop until the secret number is guessed.

    When leaving the function, the value of 'state' is set to "END".
    """
    global state
    secret = random.randint(1, 99)
    guess = 0
    while guess != secret:
        guess = get_integer()
        if guess == secret:
            state = "END"
            return
        elif guess < secret:
            print("Too low")
        else:            
            print("Too high")
    

def start():
    """
    This small function act as a 'start page' in a larger program.
    Here the game is presented, rules explained etc.

    When leaving the function the value of 'state' is either "GAME" or "STOP".
    """
    global state
    print("Welcome to the Number Guessing Game")
    print("You need to guess a number between 1 and 99")
    
    result = yes_no("Are you ready? ")
    if result:
        state = "GAME"
    else:
        state = "STOP"
    return


def end():
    """
    This function is like and 'end-screen' in a larger game.
    Here the result is summarized and the player asked to play again.

    When leaving the function the value of 'state' is either "START" or "STOP".
    """
    global state
    print("Right, you guessed the number")
    again = yes_no("Play again? ")
    if again:
        state = "START"
    else:
        state = "STOP"
    return

def stop():
    """
    This function ends the game completely
    """
    print("Thank you for playing")
    sys.exit()
    

"""
Below you find the main loop controlling the game.
A variable named 'state' can take a number of values:
"START", "GAME", "END" and "STOP".

Depending of the actual 'state' different functions are executed.
Upon leaving these functions, the 'state' value is updated
depending of the game situation

'state' is here a global variable.
In more advanced games (see next code sample) this is changed.
"""

state = "START"

while True:
    if state == "START":
        start()
    elif state == "GAME":
        game()
    elif state == "END":
        end()
    elif state == "STOP":
        stop()

I have made four states: START, GAME, END and STOP. Naturally you can have many more states; it all depends of the game complexity.

The idea in this code is to have a variable, 'state', that directs the game between various functions.
The variable 'state' is here global; the value of 'state' can change inside any state function depending of the game situation and/or user input.
The main game loop only detects the value of 'state' and executes the relevant function, all state functions ends by returning to the main loop.


The next game is larger and may need some introduction. You (the player) are a bee catcher; you need to catch all the bees as quickly as possible. But don't hit the walls, it will move you to the starting position.

The game is build around five class objects: Bee(), Me(), Wall(), Button() and Game().
The first four classes are explained directly in the code.

The Game() object, however, is in focus here. But first, look at the Game() __init__ method along with the very last three lines in the code
class Game():
    def __init__(self):
        ...
        self.state = self.game_start

    def game_start(self):
        ...
..
..
game = Game()
while True:
    game.state()

'game' is an instance of Game()
'game' has an attribute named 'state'; the 'state' value is actually names of game methods.

For instance the 'state' can be equal to 'game_start', as it is in the __init__() method
When executing game.state() we really execute game.game_start(), the method starting in line 123.
Similar with the other game.states.

This is possible because in Python functions and methods (like 'game.start_game()') are themselves objects and can be assigned to other variables.

OK, - it is important to understand that this code revolves around this tiny loop at the end of the code. We only need to make sure that the value of 'game.state' changes according to the game situation and make sure that the game returns to the loop in order for the new game.state value to take effect.

The Game() object itself is an abstract object, it is not representing a physical thing. The Game() object holds all the state functions as individual methods. Furthermore a number of support methods are found in Game(). Both state functions and support functions are described in the code.
"""
Demonstartion program by DK3250

Demonstrates the use of an abstract Game() object and the use of
game.state functions for advanced game control.

The game is simple: The player should eat all the bees as quickly as possible.
Hitting the walls will move the player to the start position.
"""

import sys, random, pygame
pygame.init()


class Bee():
    """
    The Bee object consist of a surface and a Rect along with
    position and speed components
    """
    
    def __init__(self, location):
        self.surface = pygame.surface.Surface([10, 6])
        self.surface.fill(WHITE)
        pygame.draw.line(self.surface, BLACK, (0, 0), (10, 6), 2)
        pygame.draw.line(self.surface, BLACK, (0, 6), (10, 0), 2)
        pygame.draw.circle(self.surface, YELLOW, [5, 3], 2, 0)
        self.surface.set_colorkey(WHITE)
        self.x = location[0]
        self.y = location[1]
        self.rect = self.surface.get_rect(center=(self.x, self.y))
        self.dx = [-3, -2, -1, 1, 2, 3][random.randint(0, 5)]  # avoid speed component = 0
        self.dy = [-3, -2, -1, 1, 2, 3][random.randint(0, 5)]

    def draw(self):
        screen.blit(self.surface, self.rect)

    def move(self):
        self.x += self.dx
        self.y += self.dy
        self.rect.center = (self.x, self.y)


class Wall():
    """
    The Wall() object is a simple surface and Rect element.
    It has a direction ("x" or "y"); this is used when bees collide,
    determines the direction of bounce
    """
    def __init__(self, location, size, direction):
        self.surface = pygame.surface.Surface(size)
        self.surface.fill(BLACK)
        self.rect = self.surface.get_rect(topleft=location)
        self.dir = direction

    def draw(self):
        screen.blit(self.surface, self.rect)


class Me():
    """
    The player object.
    """
    def __init__(self, location):
        self.surface = pygame.surface.Surface([12, 12])
        self.surface.fill(WHITE)
        pygame.draw.circle(self.surface, RED, [6, 6], 6, 0)
        self.surface.set_colorkey(WHITE)
        self.rect = self.surface.get_rect(center=location)

    def draw(self):
        screen.blit(self.surface, self.rect)

    def user_events(self):
        """
    Support function, handles user events; called from game.move_all().
    """
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game.state = game.stop()
                return

        # move the player
        keys = pygame.key.get_pressed()  # handles keys constantly pressed
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.left -= 5
        if keys[pygame.K_RIGHT] and self.rect.right < X:
            self.rect.left += 5
        if keys[pygame.K_UP] and self.rect.top > 0:
            self.rect.top -= 5
        if keys[pygame.K_DOWN] and self.rect.bottom < Y:
            self.rect.top += 5


class Button():
    """
    This Button() object is used for interphase with the player in
    start- and stop-screens
    """
    def __init__(self, txt, color, location):
        self.surf = pygame.Surface((100, 40))
        self.rect = self.surf.get_rect(center=location)
        self.surf.fill(WHITE)
        pygame.draw.rect(self.surf, BLACK, (0, 0, 100, 40), 1)
        txt_surf = button_font.render(txt, 1, color)
        txt_rect = txt_surf.get_rect(center=(50, 20))
        self.surf.blit(txt_surf, txt_rect)

    def draw(self):
        screen.blit(self.surf, self.rect)


class Game():
    """
    The Game object handles all aspect of the game running as a Finite State Machine.
    The value of game.state directs the game sequences.
    The states are: init, game_loop, game_start, game_over, stop.
    Furthermore a number of support functions are used.
    """
    def __init__(self):
        self.getColor = getDisplayColor()  # Initiates the color generator
        self.state = self.game_start  # the all-important game.state initialization

    def game_start(self):
        "This game state generates a start screen"
        
        play = Button("Play", GREEN, (X//2, 400))

        txt = "Demo - place any GAME START text here"
        txt_surf = button_font.render(txt, 1, BLACK)
        txt_rect = txt_surf.get_rect(center=(X//2, 200))

        while True:  # wait for mouse click at button
            screen.fill(next(self.getColor))
            screen.blit(txt_surf, txt_rect)
            play.draw()  #draw button
            pygame.display.flip()

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.state = self.stop
                    return
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    pos = pygame.mouse.get_pos()
                    if play.rect.collidepoint(pos):
                        self.state = self.init
                        return
            pygame.time.wait(20)

    def init(self):
        " This game state initializes the game"
        
        self.start = pygame.time.get_ticks()  # used for timer
        self.bees = []

        for _ in range(20):  # defines the number of bees, feel free to change
            self.bees.append(Bee((random.randint(3, X-3), random.randint(3, Y-3))))

        self.me = Me((X//2, Y//2-10))

        self.walls = []
        for item in wall_list:
            self.walls.append(Wall(item[0], item[1], item[2]))

        self.state = self.game_loop

    def game_loop(self):
        """
    This game state executes the main part of the game.

    As long as bees are left, it cycles through the steps:
    move_all(), detect_collisions() and draw_all().
    Those three support functions are defined below as methods.
    """
        if self.bees:
            clock.tick(60)
            self.move_all()
            self.detect_collisions()
            self.draw_all()
        else:
            self.state = self.game_over

    def stop(self):
        "This game state terminates the program"
        pygame.quit()
        sys.exit()

    def game_over(self):
        "This game state handles end screen when all bees are eaten"
        
        yes = Button("Play Again", GREEN, (X//2-100, 400))
        no = Button("Stop", RED, (X//2+100, 400))

        txt = "Demo - place any GAME END text here"
        txt_surf = button_font.render(txt, 1, BLACK)
        txt_rect = txt_surf.get_rect(center=(X//2, 200))

        while True:  # wait for mouse click at either button
            screen.fill(next(self.getColor))
            screen.blit(txt_surf, txt_rect)
            yes.draw()
            no.draw()
            pygame.display.flip()

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.state = self.stop
                    return
                elif event.type == pygame.MOUSEBUTTONDOWN:
                    pos = pygame.mouse.get_pos()
                    if yes.rect.collidepoint(pos):
                        self.state = self.game_start
                        return
                    if no.rect.collidepoint(pos):
                        self.state = self.stop
                        return
                elif event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    self.state = self.game_loop
                    return
            pygame.time.wait(20)

    def detect_collisions(self):
        "This support function detects collisions"
        for wall in self.walls:
            for bee in self.bees:
                if bee.rect.colliderect(wall.rect):
                    if wall.dir == "x":  # check wall type (horisontal or vertical)
                        bee.dy *= -1  # collision with horisontal wall
                    else:
                        bee.dx *= -1  # collision with vertical wall

            if self.me.rect.colliderect(wall.rect):
                self.me.rect.center = ((X//2, Y//2-10))

        for bee in self.bees:  # Collision with player >> Bee eaten (removed from list)
            if self.me.rect.colliderect(bee.rect):
                self.bees.remove(bee)

    def move_all(self):
        "support function"
        for bee in self.bees:
            bee.move()
        self.me.user_events()  # moves the player using a separate support method in Me()

    def draw_all(self):
        "support function"
        screen.fill(next(self.getColor))

        time = (pygame.time.get_ticks()-self.start)//100/10
        txt = "BeeEater by DK3250 - Bees left to eat: {}  -  Time: {} ".format(len(self.bees), time)
        pygame.display.set_caption(txt)  # dynamic caption

        for wall in self.walls:
            wall.draw()
        for bee in self.bees:
            bee.draw()
        self.me.draw()

        pygame.display.flip()


def getDisplayColor():
    """
    The generator cycles through the the three colors (r, g, b ) and move one color value
    at a time to a random end point in steps of +1 or -1
    """
    color = [200] * 3                # start color
    rgb_index = 1                    # indicates the r, g, b color to change, r=0, g=1, b=2
    colEnd = random.randint(0, 255)  # the color change end point

    while True:
        dif = colEnd - color[rgb_index]
        if dif != 0:
            color[rgb_index] += dif/abs(dif)
            yield color

        else:
            colEnd = random.randint(0, 255)
            rgb_index = (rgb_index+1) % 3


# Initialization
X = 900
Y = 600

wall_list = [
            ((0, 0), (X, 1), 'x'),  # walls: ((location), (size), direction)
            ((0, Y-1), (X, 1), 'x'),
            ((X//3, Y//2), (X//3, 5), 'x'),
            ((0, 0), (1, Y), 'y'),
            ((X-1, 0), (1, Y), 'y'),
            ((X//4, Y//4), (5, Y//2), 'y'),
            ((X//4*3, Y//4), (5, Y//2), 'y')
            ]

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
GREEN = (20, 200, 50)

screen = pygame.display.set_mode((X, Y))
clock = pygame.time.Clock()
button_font = pygame.font.SysFont("Segoe Print", 16)

# The game loop:
game = Game()
while True:
    game.state()


Note that this code pattern requires a frequent return to the outer while loop (here line 307/308), - this is the only way a changed state value can take effect.

If you build more complicated games, you may need more game situations than the simple game_loop() method can handle. That's easy to manage: If a special game situation appears (say, gravity is inverted or the 'hero' is in water or whatever..) and the normal game rules no longer apply, you just change game state to 'special' and have an equivalent method named 'special'; upon returning to normal, you reset state to 'game_loop'.

I hope this small tutorial has been helpful.
I will be very happy for any response: Did you like it, tell me. Did you not like it, tell me too. Please just leave a short feedback, it is the only way I can improve.
If you have questions, I'll be happy to try to answer.

Is This A Good Question/Topic? 2
  • +

Replies To: Game input from Console and Game loops

#2 sigint-ninja  Icon User is offline

  • D.I.C Head

Reputation: 1
  • View blog
  • Posts: 148
  • Joined: 05-June 15

Posted 26 May 2016 - 12:13 PM

studying this...its brilliant!!! the beekeeper game is so impressive to me...going to try apply the principles here to my UFO game...also is it imperative to have ME as a class...would a function be surfice? as i would never need to make more than one of myself...i could have a class ENEMYUFOS to instantiate enemy ufos...or am i missing something?

lastly,can this organisation method "finite state machines" be applied to any other language? is it a design principal?
i mean do things work pretty similarly when designing a game in javascript or c# etc

thanks DK

This post has been edited by sigint-ninja: 26 May 2016 - 12:23 PM

Was This Post Helpful? 0
  • +
  • -

#3 DK3250  Icon User is offline

  • Pythonian
  • member icon

Reputation: 292
  • View blog
  • Posts: 922
  • Joined: 27-December 13

Posted 27 May 2016 - 07:45 AM

Hi Ninja,
Thank you for the nice word, I'm glad you liked the tutorial.

Very few things in programming is imperative, you can program anything in more than one way.
The Me() class in the Bee-eater game could be substituted by a function. However, I try to make the examples as generic as possible.

Suppose you have another type of 'Hero' able to jump, swim, kick, etc. You control this Hero (this 'Me() instance') by the keyboard.
When on ground the Hero can jump (when you hit the up-key).
When in the middle of a jump the up-key should not release an 'air-jump'; the up-key should either not work at all, or it should release some other action (say: throw fire-ball).
In order to do this, the 'Me()' instance needs states of its own.
The 'Me' needs states like 'jump', 'kick', 'swim', 'walk', etc.

So, just like the single Game() instance has states controlling the various phases of the game - likewise the single Me() instance can have states controlling the various actions and the switch from one set of rules to another (e.g. from walking to swimming).

This long explanation only to motivate the use of a class object, - even for a single instance.

The design pattern "Finite State Machine" can certainly be applied to most languages. The pattern was developed long ago, but became widely known in 1994 when a book: "Design Patterns: Elements of Reusable Object-Oriented Software" was published. I use a later book by Robert Nystrom: "Game Programming Patterns", all examples in this book are in C++.

This post has been edited by DK3250: 27 May 2016 - 07:48 AM

Was This Post Helpful? 1
  • +
  • -

#4 albert003  Icon User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 294
  • Joined: 15-December 14

Posted 19 September 2016 - 10:22 AM

I loved your tutorial page and it really helped. I found a simpler way to make the first example work whether the user has caps or not. Tell me what you think.

def run_again():
result=None
while result==None:
choice=raw_input("Run again? y/n: ").lower()
if choice in ["yes","ys","y"]:
return run_again()
elif choice in ["no","n"]:
exit()
Was This Post Helpful? 0
  • +
  • -

#5 DK3250  Icon User is offline

  • Pythonian
  • member icon

Reputation: 292
  • View blog
  • Posts: 922
  • Joined: 27-December 13

Posted 19 September 2016 - 01:09 PM

Well, the use of str.lower() is the standard way of simplifying the subsequent conditional statement. I didn't include it to keep things simple, but now you did and that's perfectly ok.

I'm sure you don't mean to return run_again() on positive input; and I don't like the exit() on negative one. In both cases you should return something meaningful (I tend to use True or False) and let the main program handle the situation. Even if you want to exit on negative response, you might want to show an end-screen before closing.

My first example can be shortened by including conditional returns:
def run_again():
    while True:
        choice = input("Run again? y/n: ")
        if choice in ["y", "Y"]:
            return True
        elif choice in ["n", "N"]:
            return False
        else:
            print("Invalid input")


if run_again():
    # Your code here...

This, however, reduces readability a bit.

Thank you so much for leaving a feedback :bigsmile: :bigsmile:
Was This Post Helpful? 1
  • +
  • -

#6 albert003  Icon User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 294
  • Joined: 15-December 14

Posted 28 September 2016 - 12:00 AM

I have a question for you and I hope you can help me. I am learning python through the book "learn python the hard way" and I'm stuck on a lesson. Zed Shaw wants me to make my own game and these are the requisites of the game he wants me to make:

*Make a different game from the one I made.
*Use more than one file, and use import to use them.
Make sure you know what that is.
*Use one class per room and give the classes names
that fit their purpose (like GoldRoom, KoiPondRoom).
*Your runner will need to know about these rooms,
so make a class that runs them and knows about them.There's plenty of ways to do this, but consider having each room return what room is next or setting a variable of what room is next.

Ive been on this lesson now for four months and I can't figure out how to do it. I've searched online for game loops and I saw your column for game loops but whenever I see game loops the game only has one class and this lesson he wants me to use a class for each room. Could you give me a link or a sample how to do this?. The only example I have was in lesson 43 and I'm at wits end. This is a link to the only example I have found that has a game loop where it 'knows' each room. Could you please help me out?.

https://learnpythont.../book/ex43.html
Was This Post Helpful? 0
  • +
  • -

#7 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon


Reputation: 6932
  • View blog
  • Posts: 14,460
  • Joined: 16-October 07

Posted 28 September 2016 - 06:08 AM

View Postalbert003, on 28 September 2016 - 02:00 AM, said:



Your link was kinda buggered; probably this: https://learnpythont.../book/ex43.html

This one has showed up before. To be perfectly honest, I rather hate it. In fact, I think his prior version without classes was in many ways superior: http://cglab.ca/~mor.../book/ex41.html

In your link, the game loop is in the instance of Engine. All game loops can essentially be written the same way:
state = initialState()
while state.running:
    doStuff(state)



A game loop just spins until the halt condition is reached. The nature of the spinning differs, depending on the game. But all games tend to have the quality of "still going? ok." Thus, the loop.

I have considered trying to make a cleaner version of Zed's example... You're not the first to have trouble with it.
Was This Post Helpful? 1
  • +
  • -

#8 albert003  Icon User is offline

  • D.I.C Regular

Reputation: 11
  • View blog
  • Posts: 294
  • Joined: 15-December 14

Posted 04 October 2016 - 11:11 PM

That previous example makes a lot more sense to me than his new version using oop. His lesson #45 is just as confusing. I looked everywhere for examples for other programmers used for similar tasks and all I could find were text style games that where made mostly using functions. When they did have a class they only used a constructor and that was it. I know hes in the process of making a newer version of his book and I hope he changes that lesson back to the way it was previously. These are the requisites of the game he wants me to make:

*Make a different game from the one I made.
*Use more than one file, and use import to use them.
Make sure you know what that is.
*Use one class per room and give the classes names
that fit their purpose (like GoldRoom, KoiPondRoom).
*Your runner will need to know about these rooms,
so make a class that runs them and knows about them.There's plenty of ways to do this, but consider having each room return what room is next or setting a variable of what room is next.

It makes a lot more sense to make the game using functions for each room and using a constructor. Thank you very much for you input.
Was This Post Helpful? 0
  • +
  • -

#9 baavgai  Icon User is offline

  • Dreaming Coder
  • member icon


Reputation: 6932
  • View blog
  • Posts: 14,460
  • Joined: 16-October 07

Posted 05 October 2016 - 06:12 AM

View Postalbert003, on 05 October 2016 - 01:11 AM, said:

*Your runner will need to know about these rooms,


Not, exactly, true.

You inspired me to write a post I've been meaning to for a while. Found here: http://www.dreaminco...-python-my-way/

In the next post, I'll write my own little text adventure and maybe address a few of those bullet points. In the mean time, try writing something yourself and posting it to the forum for questions, so more folks can help. And, so we don't spam this nice tutorial. ;)
Was This Post Helpful? 1
  • +
  • -

Page 1 of 1