Creating a state machine in python - python

I have created various simple 2 dimensional games in python before and because they were simple, I did not need to create such a thing. But I now need it due to needing to go back and fourth.
To go forward though, I need some sort of direction...
I am around 200 lines into a new game and I haven't started on the actual game, its currently all dealing with the window, events and state's
### state machine
def run:
#~setup window
# Current state
state = menu()
running = True
while running:
#~event handler
returns = state.update(events)
if returns == "playgame":
state = game()
state.draw(window)
#menu state
class menu:
def __init__(self):
#~define vars for the menu (buttons, etc...)
self.clickables = [] # filled with gui etc..
self.image = image() # image of the menu
def update(self, events):
for event in events: # go through events
for clickable in self.clickables: # go through clickables
if clickable.testClicked(event.pos) != False: # Returns if clicked
return clickable.testClicked(event.pos)
def draw(self, window):
self.image.draw(window)
#game state
class game(menu): # Exactly the same as menu (just used as example)
def __init__(self):
super(menu).__init__()
#gui button
class button: # Extremely shortened for example use
def __init__(self, do): # do is a string
self.whenClickedDo = do
def testClicked(self, mousePos):
if mousePos in self.myRect: # myRect is the area of the button (Example!)
return self.whenClickedDo
return False
This above example was completely rushed but the question I ponder is... What is a better way to achieve this, or is the above an achievable/smart way to do things?
TLDR; A function "run" has a value "state" which can return a value which would be used to change itself into a different state. Is that a reasonable way to make a state machine?

I wrote a simple game engine in C++ and used a screen based system. I'll try and write a simple version in python for you now. The idea is that each part of the game is a different screen. So the main menu, is a screen, there is a gameplay screen (where all the action takes place), there might be an options screen for the user to change the settings etc etc. These are all managed by a screen list, each screen has a position in the list, and you can switch between screens depending on game events. So if the main menu screen is active, when the user clicks the 'play' button, the gameplay screen is now loaded.
I cant think of the top of my head exactly how I'd port it to python but this should give you somewhere to start at least.
So a screen would be a class something like this:
class Screen:
# you can have objects in the screen as variables of the class here
# e.g self.player = None
def onEntry(self):
# here you would init the assets for your screen
# this could be the interface, sprites etc
# e.g self.player = Player()
def onExit(self):
# do any clean up
# maybe save player data if this is a gameplay screen
# e.g self.player.save()
def update(self):
# main loop for this screen
# e.g self.player.update()
Each specific Screen for your game would inherit from the Screen class and implement those functions with custom logic for that screen. Then the screen list would basically just be a list of these custom Screen classes. Your game would just flip between these Screens.

Related

How to only call functions once if user presses it multiple times

I am currently working on a program which has a simply GUI and 3 buttons. One of them is start, which runs my code. I am trying to make it as user friendly as possible, so if they spam click it it only runs once. Currently, if the user presses the button multiple times, then the images update multiple times a second.
Any help would be appreciated.
One of the most common OOP practices is to view everything in your GUI as a component, a class, which handles its own variables, a component state, which you initialize when you first render the component.
For example, if we want to monitor when a button is clicked, we could disable the button afterwards by updating the component state.
class Button:
"""
Button that self monitors how many clicks have been performed
"""
def __init__(self,):
#Initialize state
self.disabled = False
self.button_clicked = 0
def click(self,):
if not self.disabled: #Only perform action if self.disabled == False
self.disabled = True
self.button_clicked += 1
#Do stuff on click
class ButtonContainer:
"""
Container for a group of associated Buttons
"""
def __init__(self,):
self.button1 = Button()
self.button2 = Button()
self.button3 = Button()
class App:
def __init__(self,):
#Add components to your app
self.button_container = ButtonContainer()
The reason we may want to use a container is because we can group buttons together and monitor then from our main App class.
For example, within the App class we can call
print(self.button_container.button1.disabled)
With this we can see component states outside of the actual component.
use an if-else statement
pressed = 0
Then pay attention to the times it was clicked and use
if pressed == 3:
# do this
else:
# do something different

Pygame screen looping back

I am making a game in python, which has a main menu, followed by a difficulty select screen. When I select the play game button, the screen changes to the difficulty select screen as intended, but when I click a difficulty, it returns to the main menu screen, which is fully functional, leading me to believe that the function is continuing to run. If I then return to the difficulty select screen and select a difficulty a second time the game progresses as expected. No other button leads to this error which leads me to believe the error is in the select difficulty function itself.
def gameIntro(): # game intro loop
playButton=classes.Button(100,200,275,350,selectDifficulty) #classes is external file which contains a button class. params (x,y,w,h,action=None)
leaderButton=classes.Button(700,200,275,350,leaderboard)
quitButton=classes.Button(1000,200,250,350,quit)
instructButton=classes.Button(425,200,275,350,instructions)
spriteGroup = pygame.sprite.Group(playButton, leaderButton, quitButton, instructButton)
while not(playButton.loop and leaderButton.loop and quitButton.loop and instructButton.loop): #all of button objects have loop set to false, until they are clicked
eventLoop(spriteGroup) #runs an event handler for all of objects in spriteGroup
mainMenuScreen = pygame.image.load('Menu.jpg') # loads the menu Screens
mainMenuScreen = pygame.transform.scale(mainMenuScreen, (displayWidth, displayHeight)) #adjusts image to be same size as display
gameDisplay.blit(mainMenuScreen, (0, 0)) #displays the image
pygame.display.update() #updates the display
clock.tick(fps) #increments the clock by the fps amount to keep the screen changing
def selectDifficulty(): #selects the difficulty so that the appropriate AI is loaded
import Main
easyButton=classes.Button(70,150,300,400,Main.easyAIMaker)
mediumButton=classes.Button(450,150,300,400)
hardButton=classes.Button(800,150,300,400)
spriteGroup = pygame.sprite.Group(easyButton,mediumButton,hardButton)
while not (easyButton.loop and mediumButton.loop and hardButton.loop): #loops the screen while a choice hasnt been made
eventLoop(spriteGroup)
difficultyScreen = pygame.image.load('difficulty screen.jpg')
difficultyScreen = pygame.transform.scale(difficultyScreen, (displayWidth, displayHeight))
gameDisplay.blit(difficultyScreen, (0, 0))
pygame.display.update()
clock.tick(fps)
Problem was I didn't realise how python treated imported code. At the very end of intro.py the gameintro() function is called to start the game. When I imported intro.py this code was rerun causing the loop. By shielding game intro inside:
Def main():
gameIntro()
If __name__=="__main__":
Main()
The gameintro function only runs once

How to move from one screen to another, with any input from the user, in pygame? [duplicate]

I'm making a little game and I want to make another window separately from my main one.
I have the the main game in a main window, and I want to open a new window and do a little animation when the user does something.
In my example code below, when the user presses "a" I want it to open a new window and blit to there.
Here I set up the two windows: (I know this doesnt work, its what I'm asking how to do)
SCREEN_X = 400
SCREEN_Y = 400
BSCREEN_X = 240
BSCREEN_Y = 160
BATTLE_SCENE = pygame.display.set_mode((BSCREEN_X, BSCREEN_Y))
SCREEN = pygame.display.set_mode((SCREEN_X, SCREEN_Y))
and then the program:
def run_ani ():
#Do animation, blitting to BATTLE_SCENE
return
def main_game():
ending=False
while ending==False:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT: ending=True
if event.type == KEYDOWN: # key down or up?
if event.key == K_ESCAPE:
ending=True # Time to leave
print("Stopped Early by user")
elif event.key == K_a:
run_ani()
#Normal screen motion, blitting to SCREEN
if ending: pygame.quit()
return
So far what this does is draws the main screen, then when A is pressed, it stops drawing the main screen animations, but still draws the other animations on the main screen and draws in the top left corner.
I'm pretty sure it does this because I am setting BATTLE_SCENE to be smaller than the main screen, thus when blitting to BATTLE_SCENE it blits to the area I created (240x160) in the top corner of the main screen.
However I want BATTLE_SCENE to be a seperate window, so that when I press 'a' it will pop up, do its thing, then close or at least go behind the main screen.
How to do this? Is it even possible?
Do you really need multiple windows? I mean, do you really need them?
If yes, then you should probably use pyglet/cocos2d instead.
To have multiple windows in pygame, you need multiple processes (one for each window). While this is doable, it's not worth the efford. You'll need IPC to exchange data between the windows, and I guess your code will become error-prone and ugly.
Go with pyglet when you need more than one window.
The better solution is probably to divide your game into scenes. Create multiple scenes so that each one represent one stage of the game, something like MenuScene, MainScene, BattleScene, GameOverScene, OptionScene etc.
Then let each of those scenes handle input/drawing of that very part of the game.
MenuScene handles drawing and input etc. of the game's menu
MainScene handles drawing and input etc. of the running game
BattleScene handles drawing and input etc. of whatever you do in run_ani
In your mainloop, just pass control over to the current scene by implementing the methods draw(), handle_event(), and update().
Some example code to get the idea:
scenes = {'Main': MainScene(),
'Battle': BattleScene()} #etc
scene = scenes['Main']
class MainScene():
...
def handle_event(self, event):
if event.type == KEYUP:
if event.key == K_a:
scene = scenes['Battle']
...
class BattleScene():
...
def draw(self):
# draw your animation
def update(self):
# if animation is over:
scene = scenes['Main']
...
def main_game():
ending=False
While Not ending:
clock.tick(30)
for event in pygame.event.get():
scene.handle_event(event)
scene.update()
scene.draw()
This is an easy way to cleanly seperate the game logic and allow context switching.
======================================Edit=========================================
Actually it won't work. Apperantly pygame only supports one display screen, and when you initialize another, it will close the first. You will stay with two varibles, which in fact are the same surface. You can have instead the game increasing the window size and playing the battle scene on the side of it, to do this, you can call the pygame.display.set_mode() again with different values. The varible which references the display screen will still be usable, as it change its reference to the new one. After the scene is over you can decrease the window back the same way.
==================================================================================
What basically happens is you run a loop, and each iteration of it is rendering and displaying a new frame.
When you call a function inside a loop, it doesn't continue to run until you finish running the function.
One way to solve this problen is just keep calling the function that updates the battle scene in the main loop.
Another way is by using threading. Threading is basically running multiple scripts ("Threads") in the same time.
Luckily, python already implemented this for us with the threading module.
It's too long for me to explain the module here, but you can learn it here. It might be a little complex if you haven't use threads before, but after some time it will be easier.
And If you want to learn more about threading you can go here.
Specificly here, you can have two threads, one for each loop/window, and run them in the same time.
Hope I helped you!
Yes, that is possible. SDL2 is able to open multiple windows. In the example folder you can take a look at "video.py".
https://github.com/pygame/pygame/blob/main/examples/video.py
"This example requires pygame 2 and SDL2. _sdl2 is experimental and will change."

How can I include multiple pygame programs in a single .py file so they can be accessed from the same window

What I want to do is, I want to present different smaller games as options on the initial window to choose from. So whatever the game option user selects, that game will run in the same window. Is that possible and if it is then how?
This could be what you want in pseudocode:
class Menu():
#Has all menu-related knick-knacks
def main(screen):
if on_main_menu == True:
Menu.draw(screen)
if Menu.start_button_1.is_pressed:
on_main_menu = False
playing_game1 = True
if Menu.start_button_2.is_pressed:
on_main_menu = False
playing_game2 = True
while playing_game1:
#play the epic game
while playing_game2:
#play the even more epic game

Pygame: Why do both buttons activate if one hasn't been checked yet?

I've set up a basic Pygame graphical interface, but I'm having trouble with my buttons. I created a Button class, and the function to be executed by the button is determined in the __init__() method. In other words, I input the function when I create an instance of Button via lambda expression. The relevant code of the buttons basically looks like this:
class Button():
def __init__(self, action):
self.command = action
def check(self): # To be called while iterating through pygame.event.get()
if event.type == MOUSEBUTTONUP and self.rect.collidepoint(pygame.mouse.get_pos()):
self.command()
I also created a Window class where each instance is list of the buttons to be seen at a time:
class Window():
def __init__(self, buttons):
self.is_visible = False # This determines whether the buttons in this
# window should be updated, checked, and drawn
self.buttons = list(buttons)
def open(self):
self.is_visible = True
def close(self):
self.is_visible = False
def trans(self, new_window):
self.close()
new_window.open()
Next, I set up two instances of Window, each with a Button to toggle back to the other:
WINDOW_1 = Window([Button(lambda: WINDOW_1.trans(WINDOW_2))])
WINDOW_2 = Window([Button(lambda: WINDOW_2.trans(WINDOW_1))])
And finally:
WINDOW_1.is_visible = True
Here comes the problem.
Each button works exactly how it is supposed to: it closes the open window and opens the closed window. Unfortunately, if I click the mouse in a spot where both buttons overlap (or where they would overlap if they were both visible), the function for WINDOW_2's button is called immediately after the function for WINDOW_1's button is called. Basically, WINDOW_1 -> WINDOW_2 -> WINDOW_1, and it all happens in the same loop.
However, if we start from WINDOW_2, then this happens: WINDOW_2 -> WINDOW_1. It appears that the glitch is only one-way. I just can't figure out what's wrong here, I would really appreciate some help. (Just in case, here's a link to the full code so you can maybe reproduce the problem; I've set the buttons' location so that the bottom half of the first button overlaps the top half of the second: http://pastebin.com/m1zCQLRF). Thank you for reading and thank you in advance for answering!
Consider these lines (from the pastebin):
all_windows=[WINDOW_1,WINDOW_2]
....
for event in pygame.event.get():
for window in all_windows:
if window.is_visible:
for button in window.buttons:
button.update()
button.check()
If WINDOW_1's button is clicked, WINDOW_2.is_visible becomes True.
In the next iteration of the for window in all_windows loop, the check method of WINDOW_2 will be called because its is_visible attribute is now True. Because this is still the same iteration of for event in pygame.event.get(), WINDOW_2.check() sees the same MOUSEBUTTONUP event. The Button objects overlap, so the event causes the windows' visibility to toggle a second time, back to the state where WINDOW_1 is visible, and this is what is drawn.
Incidentally, using event.pos would be more accurate than pygame.mouse.get_pos() in the check() method. The former is the mouse's position at the time the event was posted, while the latter is the current position of the mouse.
Edit
I got the pastebin running with some tweaking and verified that what I described above is the problem by applying a quick and dirty fix.
First, I edited Button.check() so it returns a boolean that shows whether or not its check validated:
def check(self):
if event.type==MOUSEBUTTONUP and self.rect.collidepoint(event.pos):
self.command()
return True
return False
Then altered the code shown above to break out of the all_windows loop if check returns True (i.e. if a window is closed).
for window in all_windows:
if window.is_visible:
for button in window.buttons:
button.update()
_break = button.check()
if _break:
break
Now the windows close and open as expected when a button is clicked. Again, that's not a "real" fix just a confirmation of what caused the problem.

Categories