Python Forum
A question about implementing the state engine code
Thread Rating:
  • 1 Vote(s) - 1 Average
  • 1
  • 2
  • 3
  • 4
  • 5
A question about implementing the state engine code
#1
hey

I have a couple (or so) questions about using the state engine code:

I have written a simple maze game with room, bad guys, and weapons. Everything is low-res, the player and bad guys are just squares and so on. I wrote it and then re-wrote it using the state engine code. It works, shows the title screen, when I press space the game starts, and when all the players lives are gone it flips back to the title screen. The thing is, there is an instructions screen that can be shown either when the titles are showing or in game. And in game there is a weapon chooser (certain bad guys need certain guns to kill them!)

so I am currently using a mode var:

if self.mode == TITLE:
	title_screen.draw()
else:
	instructions_screen.draw()
and in game I do the same but in the draw() routine, and that just has an added:

if self.mode == CHOOSER:
And I have switching code in the game key check like so:

if event.key == pg.K_i:
	if self.mode == INSTR:
		self.mode = self.old_mode
	else:
		self.old_mode = self.mode
		self.mode = INSTR
Is this the best way to do this or is their a state-engine style solution?

I have also written a missile command type game, which I wrote when I was first learning python and pygame, and so it doesn't use classes and is full of global vars (yeah, I know!) So I was going to re-write it with classes and the state engine. The issue here is that, at the end of the game it checks for high score, so there are two routes:

game - high check - get name - show highs - title
game - high check - show highs - title

and the titles switch between showing the titles and the high scores. So how is it possible to have sub-states and switch between them? In the state engine code, everything is ran by the control, but then the states would also be controls and it seems like I would be duplicating code across two classes.

If anyone can help me further my understanding of these issues it would be much appreciated!

Also, on a slightly unrelated note, I am still using python 2.7 as the last I checked pygame only worked with it, does anyone know what version they are up to now? Does it work with Python3 as I feel I should be using that.
Reply
#2
Python 3 use pip to install pygame.

I been working on my state machine with event bus like system.
My StateMachine
99 percent of computer problems exists between chair and keyboard.
Reply
#3
(Oct-16-2018, 11:22 PM)marienbad Wrote: Is this the best way to do this or is their a state-engine style solution?
It is better to split out the state machine code elsewhere, but i dont know what you are exactly doing since you didnt share your entire code.

I use this style of state machine. It is a simplified version of it. The game and menu states are only there for an example, but they can be anything, and unlimited in number of them.

If the instructions screen does not need to accept input from the user, i would just make it an overlay. It would just literally pause the game, and display content over top of it. But you could make it a state though.

In my example, the States class is the super class of all sub-state (Menu and Game). Any data that you want to retain between states goes in the super class. Only data that is relevant to each specific state goes into the sub-states class. Example: Menu state with play, option, exit, etc. Menu state does not need to know the position of the player from game state and game state does not need to know the menu options. When you do want to pass such data between states you can use the cleanup and startup methods.

and pygame has been working for python3.x for a long time now.

Quote:and the titles switch between showing the titles and the high scores. So how is it possible to have sub-states and switch between them? In the state engine code, everything is ran by the control, but then the states would also be controls and it seems like I would be duplicating code across two classes.
in my example the Control class is the head honcho. It does all the boilerplate stuff. It setups all the states, flips the states, runs an main event loop and executes the current running state's event loop and update methods, runs the main game loop, etc. Its the meat of the program in terms of what order stuff occurs, however it does not hardly need to be touched once setup. It just starts the first state, and flips them accordingly to what that states next state value is (more about this at the end). The super class contains all data between all sub states. All states in the game are sub states. Each sub state only handles what occurs when that state is active. It renders, handles events, updates every frame, to each sub states separately. What is drawn to one state is never conflicted with another because only one state is active in control. A state could be a splash screen, level, title screen, menu, high scores page, instructions, a pause screen, or whatever you want.

Quote:game - high check - get name - show highs - title
game - high check - show highs - title
in my example...the self.next value is the name of the state that control will flip to when this state is over. You kill the state by defining an event in its event method. You can always change the next states value based on user input. So the order of the states have a default chain, but can also always go in any order. Example: Your instructions state would default the next state back to whatever state it came from. However if in the instructions state there was a button to go to main menu instead, it would switch the self.next value to the menu state and kill the instructions state. Thereby changing the default chain of returning back to what state it came from.
Recommended Tutorials:
Reply
#4
hey Metalburr

Thanks for the reply but I am actually using your state engine code!!
Reply
#5
well in that case....

Quote:
if self.mode == TITLE:
	title_screen.draw()
else:
	instructions_screen.draw()
and in game I do the same but in the draw() routine, and that just has an added:

if self.mode == CHOOSER:
And I have switching code in the game key check like so:

if event.key == pg.K_i:
	if self.mode == INSTR:
		self.mode = self.old_mode
	else:
		self.old_mode = self.mode
		self.mode = INSTR
This is not how i switch states. Control should be the only one switching states, and each state only needs to change the default next state if a case i described earlier (change the default chain) is required. You shouldnt need to do checks for which state you are in. Each state assumes its active. It runs when active and doesnt do anything when not. For example: title state should draw in its own sub class, and instruction state should as well too. There should never be a need to check which state your in to draw as they should be separate.


here is an example merging the basic state example with another example. Here in game state it counts to indicate game logic occurring. when you press the mouse button, it switches to the other state. Which that could be anything. Currently it is called a Menu state, but it could be your instruction state for example. Logic stops in the game state while not active as shown by the number only counting when you are in game state. To simplify the example i left it as two states, and they alternate between the two by mouse press. However as said before, you can change that by adding an event and changing the self.next value.

As shown in the example, you do not need to check for which state it is in to draw something like the number or backgrounds, as the control class handles that for you.

NOTE: I modified the example a little to access the screen' rect in Number class.
import pygame as pg
import sys
 
class Number:
    def __init__(self, screenrect):
        self.timer = 0.0
        self.delay = 1000
        self.screenrect = screenrect
        self.num = 0
        self.new_num()

    def inc_num(self):
        self.num += 1
        return str(self.num)
 
    def new_num(self):
        self.image, self.rect = self.make_text(self.inc_num(), (255,0,0), self.screenrect.center, 75, 'Ariel')
 
    def make_text(self,message,color,center,size, fonttype):
        font = pg.font.SysFont(fonttype, size)
        text = font.render(message,True,color)
        rect = text.get_rect(center=center)
        return text,rect
 
    def update(self):
        if pg.time.get_ticks()-self.timer > self.delay:
            self.timer = pg.time.get_ticks()
            self.new_num()
 
    def draw(self, surf):
        surf.blit(self.image, self.rect)
  
class States(object):
    def __init__(self):
        self.done = False
        self.next = None
        self.quit = False
        self.previous = None
  
class Menu(States):
    def __init__(self, screenrect):
        States.__init__(self)
        self.next = 'game'
    def cleanup(self):
        print('cleaning up Menu state stuff')
    def startup(self):
        print('starting Menu state stuff')
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            print('Menu State keydown')
        elif event.type == pg.MOUSEBUTTONDOWN:
            self.done = True
    def update(self, screen, dt):
        self.draw(screen)
    def draw(self, screen):
        screen.fill((255,0,0))
  
class Game(States):
    def __init__(self, screenrect):
        States.__init__(self)
        self.next = 'menu'
        self.num = Number(screenrect)
    def cleanup(self):
        print('cleaning up Game state stuff')
    def startup(self):
        print('starting Game state stuff')
    def get_event(self, event):
        if event.type == pg.KEYDOWN:
            print('Game State keydown')
        elif event.type == pg.MOUSEBUTTONDOWN:
            self.done = True
    def update(self, screen, dt):
        self.draw(screen)
        self.num.update()
    def draw(self, screen):
        screen.fill((0,0,255))
        self.num.draw(screen)
  
class Control:
    def __init__(self, **settings):
        self.__dict__.update(settings)
        self.done = False
        self.screen = pg.display.set_mode(self.size)
        self.screen_rect = self.screen.get_rect()
        self.clock = pg.time.Clock()
        self.state_dict = {
            'menu': Menu(self.screen_rect),
            'game': Game(self.screen_rect)
        }
    def setup_states(self, start_state):
        self.state_name = start_state
        self.state = self.state_dict[self.state_name]
    def flip_state(self):
        self.state.done = False
        previous,self.state_name = self.state_name, self.state.next
        self.state.cleanup()
        self.state = self.state_dict[self.state_name]
        self.state.startup()
        self.state.previous = previous
    def update(self, dt):
        if self.state.quit:
            self.done = True
        elif self.state.done:
            self.flip_state()
        self.state.update(self.screen, dt)
    def event_loop(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            self.state.get_event(event)
    def main_game_loop(self):
        while not self.done:
            delta_time = self.clock.tick(self.fps)/1000.0
            self.event_loop()
            self.update(delta_time)
            pg.display.update()
  
  
settings = {
    'size':(600,400),
    'fps' :60
}

pg.init()
  
app = Control(**settings)

app.setup_states('menu')
app.main_game_loop()
pg.quit()
sys.exit()
Recommended Tutorials:
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  A 3D game engine markcopperman78 5 5,965 Jun-08-2022, 10:43 PM
Last Post: netpipe
  Boomerang implementing logic Piethon 24 7,987 Nov-10-2019, 12:46 PM
Last Post: Piethon

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020