Launch pygames from a pygame menu - python

Good day, Stack Overflow
I've been working on a project that is a bunch of different minigames, and I have the menu and games all written up, the problem is, how would I go about writing code to launch these games if the right button is pressed, and then make the menu re-launch when the game is finished?

It is actually very simple. Your main loop should have sub loops. Each sub loop is a single game. If you hit button one you run loop 1. If you hit button 2 you run loop 2 etc. something like this..
while mainloop:
select=True
button1 = False
button2 = False
while select:
if button1 == True:
button1 = False
select = False
game1 = True
elif button2 == True:
button2 = False
select = False
game2 = 2
while game1:
# the game loop
if player_die is True:
game1 = False
select = True
while game2:
# the game loop
if player_die is True:
game2 = False
select = True
something like the above should work. You work out the rest. Remember!! Try before asking..

Related

Button (GPIO) Pressing Logic in MicroPython

I had another question open about iterative menu logic, and the problem morphed into button logic, so I'm separating them, since the original question was truly settled.
My code is as follows:
""" fit: a productivity logger """
import time
import sys
import os
import uhashlib
import machine
import framebuf
from ssd1306 import SSD1306_I2C
def final_print(sec,final_hash,final_survey):
""" leaves the summary on the screen before shutting down """
mins = sec // 60
sec = sec % 60
hours = mins // 60
mins = mins % 60
short_sec = int(sec)
duration = (str(hours) + "/" + str(mins) + "/" + str(short_sec))
oled_show("> fit the"+str(final_hash),str(final_survey),"//"+str(duration))
time.sleep(30)
oled_blank()
def timer_down(f_seconds,timer_focus):
""" counts down for defined period """
now = time.time()
end = now + f_seconds
while now < end:
now = time.time()
fit_progress(now,end,timer_focus,f_seconds)
time.sleep(0.01)
# if button1.value() == 0:
# oled_show("","Ended Manually!","")
# time.sleep(2)
# break
def timer_up(timer_focus):
""" counts up for indefinite period """
now = time.time()
while True:
minutes = int((time.time() - now) / 60)
oled_show(str(timer_focus)," for ",str(minutes))
time.sleep(0.01)
# if button1.value() == 0:
# oled_show("","Ended Manually!","")
# time.sleep(2)
# break
def fit_progress(now,end,timer_focus,f_seconds):
""" tracks progress of a count-down fit and prints to screen """
remain = end - now
f_minutes = int((remain)/60)
j = 1 - (remain / f_seconds)
pct = int(100*j)
oled_show(str(timer_focus),str(f_minutes)+" min",str(pct)+"%")
def debounce(btn):
""" some debounce control """
count = 2
while count > 0:
if btn.value():
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
oled_show("> fit",i,"1:yes 2:next")
# Wait for any button press.
while 1:
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
break
if b1pressed:
print( i, "chosen" )
debounce(button1)
return i
# We know B2 was pressed.
debounce(button2)
def oled_show(message1,message2,message3):
""" displays a three line message """
oled.fill(0) # clear the display
oled.text(message1,5,5)
oled.text(message2,5,15)
oled.text(message3,5,25)
oled.show()
def oled_blank():
""" blanks the oled display to avoid burn in """
oled.fill(0)
oled.show()
sda = machine.Pin(4)
scl = machine.Pin(5)
i2c = machine.I2C(0,sda=sda, scl=scl, freq=400000)
oled = SSD1306_I2C(128, 32, i2c)
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
F_TYPE = multi_choice(['30-minute fit','60-minute fit','indefinite fit'])
F_FOCUS = multi_choice(['personal fit','work fit','learn fit','admin fit'])
fStart = time.time()
if F_TYPE == "30-minute fit":
timer_down(1800,F_FOCUS)
elif F_TYPE == "60-minute fit":
timer_down(3600,F_FOCUS)
elif F_TYPE == "indefinite fit":
timer_up(F_FOCUS)
else:
sys.exit()
fEnd = time.time()
F_SURVEY = multi_choice(['went well','went ok','went poorly'])
fDuration = fEnd - fStart
F_HASH = uhashlib.sha256(str(fEnd).encode('utf-8')).digest()
F_HASH_SHORT = F_HASH[0:3]
fitdb = open("data.csv","a")
fitdb.write(str(F_HASH)+","+str(F_TYPE)+","+str(F_FOCUS)+","+str(F_SURVEY)+","+str(fStart)+","+str(fEnd)+","+str(fDuration)+"\n")
fitdb.close()
final_print(fDuration,F_HASH_SHORT,F_SURVEY)
In particular, you can see that I have two buttons defined:
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
And they are primarily used to select from menus with multiple choices:
def debounce(btn):
""" some debounce control """
count = 2
while count > 0:
if btn.value():
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
oled_show("> fit",i,"1:yes 2:next")
# Wait for any button press.
while 1:
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
break
if b1pressed:
print( i, "chosen" )
debounce(button1)
return i
# We know B2 was pressed.
debounce(button2)
However, I am encountering an issue whereby the buttons can only be pressed alternately. That is, when the multi_choice function begins, I can press button1 to select the first option, or I can press button2 to scroll to the next option, but, if I press button2, for example, it will not register for a second press (to select a second option), I can only then press button1... if I do that, I can only then press button2 next.
I'm certain this is just a logic issue I'm not seeing.
The buttons are ordinary momentary-closed Cherry MX switches on GPIO pins 2 and 3. They definitely work reliably, but something about this logic is wonky.
The following test works just fine, so it's not the buttons...
import machine
import time
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
while True:
b1pressed = button1.value()
b2pressed = button2.value()
time.sleep(0.01)
b1released = button1.value()
b2released = button2.value()
if b1pressed and not b1released:
print('Button1 pressed!')
if b2pressed and not b2released:
print('Button2 pressed!')
if not b2pressed and b2released:
print('Button2 released!')
elif not b1pressed and b1released:
print('Button1 released!')
I added in some print statements to debug this, and I can see the buttons taking and holding values. I feel like I need to tune in an artificial reset, maybe that's something I can do in debounce? I tried a few things, but I'm not making progress so far.
def debounce(btn):
""" some debounce control """
count = 2
while count > 0:
if btn.value():
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
print("below for")
print("button 1 below for",button1.value())
print("button 2 below for",button2.value())
oled_show(" fit",i,"1:sel 2:next")
while 1:
print("below while")
print("button 1 below while",button1.value())
print("button 2 below while",button2.value())
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
print("below first if")
print("button 1 first if",button1.value())
print("button 2 first if",button2.value())
break
if b1pressed:
print("below second if")
print("button 1 second if",button1.value())
print("button 2 second if",button2.value())
debounce(button1)
return i
debounce(button2)
and the output of the above debug prints:
>>> %Run -c $EDITOR_CONTENT
below for
button 1 below for 0
button 2 below for 1
below while
button 1 below while 0
button 2 below while 1
below first if
button 1 first if 0
button 2 first if 1
below for
button 1 below for 1
button 2 below for 0
below while
button 1 below while 1
button 2 below while 0
below first if
button 1 first if 1
button 2 first if 0
below second if
button 1 second if 1
button 2 second if 0
below for
button 1 below for 0
button 2 below for 1
below while
button 1 below while 0
button 2 below while 1
below first if
button 1 first if 0
button 2 first if 1
below for
button 1 below for 1
button 2 below for 0
below while
button 1 below while 1
button 2 below while 0
below first if
button 1 first if 1
button 2 first if 0
below second if
button 1 second if 1
button 2 second if 0
With both of your buttons tied to ground, and using PULL_UP the code is just:
import machine
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
while True:
print('Button1 down!') if not button1.value() else print('Button1 up!')
print('Button2 down!') if not button2.value() else print('Button2 up!')
The logic is simple. Your button is tied to ground on one end and pulled up on the other. When you press that button it's going to connect the pin of the MCU to ground. When the button is pressed value() will be zero, and when it is released value() will be one. If you want value() to be positive when you press the button then you need to tie the button to the Vcc rail and use PULL_DOWN on the MCU pin.
All that debouncing code probably isn't even necessary, and even if it was necessary creating a loop to handle it isn't the answer. What if your button doesn't bounce ~ now you're just stuck in a loop. Get a 74HC14 or make a simple RC network if you're worried about bouncing. Assigning all these arbitrary values to button states and sticking buttons in loops that block your other buttons is just a bunch of noise. It's very simple: You have one question that you want to ask twice. "Is this button pressed" ~ so just "ask" that question twice and move on. It doesn't take 20 lines of code to determine if something is 1 or 0.
Aside:
You do not have a separate "GND". All your "GND" pins are connected to the same "GND". You have one power source right? Well, one is one. Cutting a cake into 8 pieces doesn't give you 8 cakes.
Ok, with the help from everybody here, I figured this out. Just needed to add a little sleep to avoid spill-over of the keypress from the previous while loop.
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
oled_show(" fit",i,"1:sel 2:next")
time.sleep(0.5)
while 1:
if not button1.value():
return i
if not button2.value():
break
Thanks all!

Python onkey not stopping loop

def startgame():
start.state = False
print start.state
def restart():
end.state = False
start.state = True
print game.state, end.state
s.listen()
s.onkey(startgame, "Return")
s.onkey(restart, 'r')
# This Loop stops when you hit Enter
while start.state:
start.enter()
s.reset()
# I tried repeating it here but it doesn't work
while end.state:
end.enter()
s.reset()
game.state = 'playing'
Both loops are nested in a main while loop but the second one is nested in another while loop (If that helps) so it would look something like this
while True:
while start.state:
start.flash()
s.reset()
while True:
# main game code here
while end.state:
end.flash()
s.reset()
game.state = 'playing'
break
I simply want it to show the end screen and have 'Press r to play again' flash on screen until the player hits r and then it should restart the game and go back to the start screen. The end.state variable wont update during the while loop, but start.state does during its while loop.
Your drawn out (in time) while loops have no place in an event-driven environment like turtle. In general, we need to control everything with events, specifically timer events. Let's rewrite your code as a state machine. (Since I don't have your object classes, entities like start.state become globals like start_state in my example code.)
from turtle import Screen, Turtle
start_state = True
end_state = False
def start_game():
global start_state
start_state = False
def end_game():
global end_state
if not start_state:
end_state = True
def restart_game():
global end_state, start_state
end_state = False
start_state = True
def flash(text):
turtle.clear()
turtle.write(text, align="center", font=('Arial', 24, 'bold'))
color = turtle.pencolor()
turtle.pencolor(turtle.fillcolor())
turtle.fillcolor(color)
def game_states():
if start_state:
flash("Start")
elif end_state:
flash("End")
else:
flash("Playing")
screen.ontimer(game_states, 500)
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.fillcolor(screen.bgcolor())
screen.onkey(start_game, 'Return')
screen.onkey(restart_game, 'r')
screen.onkey(end_game, 'e')
screen.listen()
game_states()
screen.mainloop()
The state machine rules are:
Start via <Return> -> Playing
Playing via <e> -> End and via <r> -> Start
End via <r> -> Start

Slowing time, but not the program

I'm making a game, where when a player steps outside the screen, the level starts. I wish to show an image of "LEVEL 1" before the game starts, yet the program shows the image too quickly. My framerate is at 60.
I am wondering if there is a way to delay time for about 5 seconds during the screen blitting but after it resumes to it's normal pace. The problem for me with the pygame.time.delay() and wait stuff is that is slows the entire program down.
Is there a easier way?
EDIT______ CODE
#START OF LEVEL 1
if level1:
screen.blit(level1_image,background_position)
pygame.time.delay(500)
level1yay = True
if level1yay:
screen.blit(background,background_position)
#Flip the Display
pygame.display.flip()
clock.tick(time)
#Quit
pygame.quit()
The first image is not displayed and goes directly to the second image
You could set up a timer and keep track of the time. when the timer reaches the delay you want you can do something.
In the example below I set up the timer and I set the level1yay to true only once the timer has reached the delay value.
I added a condition canexit so the game doesn't terminate on the first loop. I assumed that the condition for "if level1: " has been set somewhere else.
mytimer = pygame.time.Clock() #creates timer
time_count = 0 #will be used to keep track of the time
mydelay = 5000 # will delay the main game by 5 seconds
canexit = False
#updates timer
mytimer.tick()
time_count += mytimer.get_time()
#check if mydelay has been reached
if time_count >= mydelay:
level1yay = True
canexit = True
#START OF LEVEL 1
if level1:
screen.blit(level1_image,background_position)
if level1yay:
screen.blit(background,background_position)
#Flip the Display
pygame.display.flip()
clock.tick(time)
#Quit
if canexit == True:
pygame.quit()
EDIT TO INCLUDE ACTIONS PRIOR TO ENTERING LEVEL1:
mytimer = pygame.time.Clock() #creates timer
time_count = 0 #will be used to keep track of the time
mydelay = 5000 # will delay the main game by 5 seconds
canexit = False
start_menu = True # to start with the start menu rather than level1
#Do something before level 1
if start_menu == True:
... do stuff
if start_menu end:
level1 = True
time_count = 0
#START OF LEVEL 1
if level1 == True:
#updates timer
mytimer.tick() # start's ticking the timer
time_count += mytimer.get_time() # add's the time since the last tick to time_count
#check if mydelay has been reached
if time_count >= mydelay:
level1 = False # so that you do not enter level1 again (even though this is redundant here since you will exit the game at the end of the loop... see canexit comment)
level1yay = True # so that you can enter level1yay
canexit = True # so that the game terminates at the end of the game loop
screen.blit(level1_image,background_position)
if level1yay:
screen.blit(background,background_position)
#Flip the Display
pygame.display.flip()
clock.tick(time)
#Quit
if canexit == True:
pygame.quit()
I don't know if this will really work, but you could try displaying the same image of "LEVEL 1" multiple times in a row, so it appears to stay around longer, but the program itself is never actually delayed.
pygame.time.wait(5000) is a delay of 5 seconds, 500 is half a second. You did set the level1 variable to True just before this code fragment, right?

Making multiple 'game screens' using Pygame

How would you be able to create multiple screens using Pygame and events performed by the user?
For example, if I had a menu screen with 2 buttons ('Start' and 'Exit') and the user clicked on 'Start', a new screen would appear in the same window with whatever is next in the game. From that screen, a user could click on another button and move on to another screen/ return to the menu, etc.
Make classes for each one, where each class is a subclass of pygame.Surface, of the same size as the display. Then you could have 3 variable TITLESCREEN, PLAYING, HIGHSCORES and change them on key press. Then you would be able to blit the correct screen to the display.
It is same as with any program/language, you simply trigger current loop.
Consider this example:
FPS = 25
MainLoop = True
Loop1 = True # Start app in Loop1
Loop2 = False # Loop2 is idle
while MainLoop :
clock.tick(FPS)
pygame.event.pump()
keypress = pygame.key.get_pressed()
keypress_dn = tuple(x > y for x,y in zip(keypress, keypress_old))
keypress_old = keypress
if Loop1:
if keypress_dn [pygame.K_ESCAPE] :
Loop1 = False
MainLoop = False
if keypress_dn [pygame.K_2] : # goto Loop2
Loop1 = False
Loop2 = True
...
if Loop2:
if keypress_dn [pygame.K_ESCAPE] :
Loop2 = False
MainLoop = False
if keypress_dn [pygame.K_1] : # goto Loop1
Loop2 = False
Loop1 = True
...
pygame.display.flip( )
pygame.quit( )
So you add your rendering and check keypresses in corresponding loop. Here you press "2" to go to loop2 and press "1" to go to loop 1. "Escape" will quit both loops and main loop.
What I did for this was have a literal main loop that checks to see what "Screen" the player wants to go to. Like if they press the exit button, it returns where to go next. The main loop then runs a the new script for the new screen.

pygame.key.set_repeat for Joystick

I'm building a menu using pygame and I want to make it navigable using a specific gamepad. Ideally I want to be able to press and hold *down" on the D-pad repeatedly, or get something like on a keyboard where the first button press has a delay before repeatedly entering the same character (seemingly).
I'm trying to emulate the pygame.key.set_repeat(...) function for a Joystick. my approach so far has been
pygame.time.set_timer(pygame.USEREVENT, 10)
DELAY_TIME = 0.250 #ms
y_delay = True
while not done:
for event in pygame.event.get():
y_axis = gamepad.get_axis(1)
if y_axis > 0.5: # pushing down
main_menu.move_down()
redraw() #redraw everything on the surface before sleeping
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False #don't delay the next time the y axis is used
elif y_axis < -0.5: #pushing up
# repetitive I know, but I'm still working on it
main_menu.move_up()
redraw()
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False
else:
y_delay = True # delay the next time
my issue is if someone taps up or down faster than DELAY_TIME they are limited to the DELAY_TIME before they can move again. Also if someone releases and depresses the up/down button within the time.sleep interval, python never sees that it was released at all and doesn't allow for a delay.
Maybe there's a way to do this using events or mapping the joystick to keys somehow? qjoypad doesn't cut it for me, and joy2keys is trash. I would need to do the mapping within the python program.
Sleep causes the program to halt execution, so it's not a viable option. You can also do this without using set_timer and events. I did it using a couple of flags and pygame.time's get_ticks.
import pygame
from pygame.locals import *
def main():
pygame.init()
pygame.display.set_mode((480, 360))
gamepad = pygame.joystick.Joystick(0)
gamepad.init()
delay = 1000
neutral = True
pressed = 0
last_update = pygame.time.get_ticks()
while True:
for event in pygame.event.get():
if event.type == QUIT:
return
move = False
if gamepad.get_axis(1) == 0:
neutral = True
pressed = 0
else:
if neutral:
move = True
neutral = False
else:
pressed += pygame.time.get_ticks() - last_update
if pressed > delay:
move = True
pressed -= delay
if move:
print "move"
last_update = pygame.time.get_ticks()
if __name__ == "__main__":
main()
pygame.quit()
When get_axis indicates no motion, the neutral flag is set, and the pressed timer is reset, causing the move flag to remain unset. When the neutral flag is unset, if it's newly set, the move flag is set. If it's not newly set, the pressed timer increases, and move is set only if the pressed timer is greater than delay.

Categories