Different page from different functions - python

I'm trying to get pygame to display different pages based on functions called that's determined randomly. Ideally, when the right buttons are pressed, it'll move on to the next page automatically. When running this, the pygame window just stops responding. Any insight would be appreciated.
Running on Python 3x and pygame 1.9.5
import pygame
from pygame.locals import *
import random
import keyboard
pygame.init()
display_width = 500
display_height = 500
black = (0,0,0)
white = (255,255,255)
gd = pygame.display.set_mode((display_width,display_height))
myfont = pygame.font.SysFont("Arial", 30)
def rw():
gd.fill(white)
letter = myfont.render("Press r and w",0,(black))
gd.blit(letter,(100,100))
pygame.display.flip()
while True:
try:
if keyboard.is_pressed('r+w'):
break
else:
pass
except:
break
def yk():
gd.fill(white)
letter = myfont.render("Press y and k",0,(black))
gd.blit(letter,(100,100))
pygame.display.flip()
while True:
try:
if keyboard.is_pressed('y+k'):
break
else:
pass
except:
break
def ctsh():
gd.fill(white)
letter = myfont.render("Press CTRL and Shift",0,(black))
gd.blit(letter,(100,100))
pygame.display.flip()
while True:
try:
if keyboard.is_pressed('ctrl+shift'):
break
else:
pass
except:
break
my_sample = random.sample(range(3), 3)
for i in my_sample:
if my_sample[i] == 0:
rw()
if my_sample[i] == 1:
yk()
if my_sample[i] == 2:
ctsh()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
quit()
pygame.display.update()

As I mentioned in comments, the main part of the problem is your attempt to use something called the keyboard module with pygame, which generally requires you to use its own event-processing model to obtain input from the user.
Normally that wouldn't be too much of a problem, but in this case it's a little more difficult because of three things:
In your current program, you have each page function doing it's own keyboard handling.
The page functions have too much repetitive code.
Some the the keys you want to detect actually correspond to two different physical keys on standard keyboards, namely the Shift and Ctrl keys.
The following shows some methods of addressing these issues. It reduces the code repetition by what is called OOP (object oriented programming)—which in this case is by defining a generic Page class that can represent each of the pages you want to display along with methods containing the common code needed to handle them.
It deals with the dual key mapping by separating them from the regular keys so both physical keys they corresponds to can be detected. (This wouldn't be necessary if you restricted yourself to specifying the exact key, such as the left Shift key (K_LSHIFT) or the right Ctrl key (K_RCTRL), instead of KMOD_SHIFT and KMOD_CTRL which can be used to designate either of pair—a list of all the key name constants is shown here in the pygame documentation.) In other words, the code makes it possible to specify KMOD_CTRL to mean either one of them, whereas passing K_LCTRL and K_RCTRL would mean pressing both of them at the same time is required.
To support event processing for the currently displayed Page, the main event processing loop checks whether the required key(s) are being pressed by calling the current page's keys_pressed() method to determine whether the required key(s) are all being pressed at once.
import pygame
from pygame.locals import *
import random
DISPLAY_WIDTH, DISPLAY_HEIGHT = 500, 500
BLACK = 0, 0, 0
WHITE = 255, 255, 255
# Modifier keys which which correspomd to two different keys on keyboards.
DUAL_MODIFIERS = {KMOD_SHIFT, KMOD_CTRL, KMOD_ALT, KMOD_META}
class Page:
def __init__(self, title, background, message, text_color, *keys):
""" Save everything needed to show page and detect its keys. """
self.title = title
self.background = background
self.message = message
self.text_color = text_color
# Create two lists and put each key into one or the other
# depending on whether it's a "dual modifier".
lists = (self.keys, self.dual_mods) = [], []
for key in keys:
lists[key in DUAL_MODIFIERS].append(key)
def keys_pressed(self):
""" Check whether all keys are all being pressed simultaneously. """
pressed, mods = pygame.key.get_pressed(), pygame.key.get_mods()
return (all(pressed[key] for key in self.keys) and
all((mods & dual_mod) for dual_mod in self.dual_mods))
def show(self, surface, font):
pygame.display.set_caption(self.title)
surface.fill(self.background)
img = font.render(self.message, True, self.text_color)
surface.blit(img, (100, 100))
pygame.display.flip()
def main():
pygame.init()
screen = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))
msg_font = pygame.font.SysFont("Arial", 30)
# Create a list of Page instances.
pages = [Page("RW Page", WHITE, "Press r and w", BLACK, K_r, K_w),
Page("YK Page", WHITE, "Press y and k", BLACK, K_y, K_k),
Page("CTSH Page", WHITE, "Press Ctrl and Shift", BLACK, KMOD_CTRL,
KMOD_SHIFT)]
current_page = random.choice(pages) # Start with random initial page.
current_page.show(screen, msg_font)
while True: # Process events until terminated.
event = pygame.event.poll()
if event.type == QUIT:
break
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE: # Keypress to quit?
break
elif current_page.keys_pressed():
# Select a different page to display.
new_page = current_page
while new_page == current_page:
new_page = random.choice(pages)
current_page = new_page
current_page.show(screen, msg_font)
pygame.quit()
# Run application.
main()
A closing comment:
I don't know exactly the purpose of your application, but think you should seriously reconsider the design of its human interface—needing to press multiple keys simultaneously is somewhat awkward and assigning a different combination of them to each page makes them hard to remember due to the lack of consistency (and is unnecessary especially since it always does the same thing of just switching to a different random page).

Related

Python keeps crashing with pygame function

I can't get python to work when I am using my Raycast function to display images, how do I fix this?
I tried moving some variables and played around with the function, but I can't seem to get it to work.
import pygame
pygame.init()
Screen = "Title"
DB = 0
Width = 800
Height = 600
Frame = pygame.display.set_mode((Width,Height))
pygame.display.set_caption("GAME")
FPS = pygame.time.Clock()
def Raycast(RayXPos, RayYPos):
RaycastThis = pygame.image.load(TTR)
Frame.blit(RaycastThis, (RayXPos, RayYPos))
Loop = True
while Loop == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
pygame.display.update()
FPS.tick(60)
while Screen == "Title" and DB == 0:
TTR = 'TitleScreenSmall.png'
Raycast(0, 0)
I expected the frame to display the image (the same size as the window) and it instead crashed, and I can't run the program
Your problem is the infinite loop:
while Screen == "Title" and DB == 0:
TTR = 'TitleScreenSmall.png'
Raycast(0, 0)
Since the loop control variables Screen and DB never change, you have no way to exit the loop. You're stuck here, eternally repeating a function that does very little, and includes no changes to observe.
See this lovely debug blog for help.

Pygame buttons not working

I have created some sort of menu navigation system in my game. All the screens are blitted in. The "Play" and "Quit" and "Controls" button works just fine but when I try to press menu from the controls screen, nothing happens. On the controls screen, you can faintly see the first menu screen from before. That might be the problem. I think that as the return to menu button is over the previous controls page button, it somehow is pressing the controls button from before. The button and menu segment of my code will be pasted here and the full thing will be pasted in a pastebin.
def text_to_button(msg,color,buttonx,buttony,buttonwidth,buttonheight,size = "small"):
textSurf, textRect = text_objects(msg,color,size)
textRect.center = ((buttonx + buttonwidth/2)), buttony+(buttonheight/2)
gameDisplay.blit(textSurf, textRect)
def button(text,x,y,width,height,inactive_color,active_color,size = "small",action = None):
cur = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
#print(click)
if x + width > cur[0] > x and y + height > cur[1] > y:
pygame.draw.rect(gameDisplay, active_color,(x,y,width,height))
if click[0] == 1 and action != None:
if action == "quit":
pygame.quit()
quit()
if action == "controls":
game_controls()
if action == "play":
gameLoop()
if action == "main":
game_intro()
else:
pygame.draw.rect(gameDisplay, inactive_color,(x,y,width,height))
text_to_button(text,black,x,y,width,height,size)
def game_controls():
gcont = True
while gcont:
gameDisplay.blit(cont,(0,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
button("Play",150,500,100,50,white,gray,"small",action = "play")
button("Main Menu",320,500,150,50,white,gray,"tiny", action = "main")
button("Quit",550,500,100,50,white,gray,"small", action = "quit")
pygame.display.update()
clock.tick(15)
def game_intro():
intro = True
while intro:
gameDisplay.blit(imggg,(0,0))
button("Play",150,500,100,50,white,gray,"small",action = "play")
button("ControLs",320,500,150,50,white,gray,"tiny", action = "controls")
button("Quit",550,500,100,50,white,gray,"small", action = "quit")
pygame.display.update()
clock.tick(15)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_p:
intro = False
Full Code: https://pastebin.com/jrd82gkJ
You will have very hard time to debug your code in order to achieve the behavior you want for one simple reason:
The logic you use to switch between different screens providing different functionality is causing much trouble you can't directly see if you only run the game.
So you think: "oh ... how come the button doesn't work, there must be an issue with the button".
You are probably not aware of the fact that using functions having own while loops you go deeper and deeper into recursive calls with increasing recursion depth with each switch from one view to another - it is not how pygame is thought to be programmed.
I suggest you add some print() commands into your code to see in the console output that the code doesn't really do what you expect even if it appears to be OK at the first glance because it works.
Then I suggest you REWRITE your entire code so that you have one main while notGameExit: loop, and don't use any other looping in the helper functions. If you want use looping in your helper functions at least don't call from the helper functions another functions with own loops (and so on), but RETURN from them with an explicit return to avoid recursion.
If you leave the in the main loop called function with return your main loop will continue running and depending on some switches you can display in it different things on the screen and react differently to user actions.
Maybe looking at a minimal working pygame script showing "action" without usage of a loop you will gain better understanding and some deep "enlightenment" about how pygame works and then start a total rewrite of your game using another approach as this one you have used in the current code? Then come back with what you have achieved if you have further questions, but you won't probably have any, because it would be much easier to debug it yourself if the code will become more straightforward.
import pygame
pygame.init() # start PyGame (necessary because 'import pygame' doesn't start PyGame)
winDisplay = pygame.display.set_mode((1024, 768)) # set PyGame window size to 1024x768 pixel
pygame.display.set_caption("Minimal PyGame Test Script")
# Time in pygame is measured in milliseconds (1/1000 seconds) (defined by TIMER_RESOLUTION constant):
pygame.TIMER_RESOLUTION = 1000 # assure 1000 explicit, don't relay on default value
colorWhite = (255, 255, 255) # RGB color in Pygame format (valueRed=255, valueGreen=255, valueBlue=255)
colorRed = (255, 0, 0)
colorGreen = ( 0, 255, 0)
colorBlue = ( 0, 0, 255)
winDisplay.fill(colorWhite)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorRed)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorGreen)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorBlue)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds
winDisplay.fill(colorWhite)
pygame.display.update()
pygame.time.wait(3000) # show the Pygame window for 3 seconds

Python PyGame draw rectangles using for loop

I'm new to PyGame and I am learning using the book Beginning Game Development with Python and PyGame. There is an example (Listing 4-9) where the author says a script will draw ten randomly placed, randomly colored rectangles on the PyGame screen. Here is the code from the book:
import pygame
from pygame.locals import *
from sys import exit
from random import *
pygame.init()
screen = pygame.display.set_mode((640, 480), 0,32)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
pygame.draw.rect(screen, random_color, Rect(random_pos, random_size))
screen.unlock()
pygame.display.update()
What happens when I do this (and this is what I would expect to happen logically) is that it draws infinitely many rectangles. It just keeps doing the for loop because the while loop is always True. I have searched online about this, and I tried moving the display update around, but those things didn't work. It is driving me crazy!
Thanks!
Looks like you already knew why it were drawing infinite with rectangles.
I guess you want to draw 10 random rectangles with random size, pos and color once.
Then you can do like this:
import pygame
from pygame.locals import *
from sys import exit
from random import *
pygame.init()
screen = pygame.display.set_mode((640, 480), 0,32)
class Rectangle:
def __init__(self, pos, color, size):
self.pos = pos
self.color = color
self.size = size
def draw(self):
pygame.draw.rect(screen, self.color, Rect(self.pos, self.size))
rectangles = []
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
rectangles.append(Rectangle(random_pos, random_color, random_size))
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
for rectangle in rectangles:
rectangle.draw()
screen.unlock()
pygame.display.update()
The code does run an infinitely.
However, the inner loop does create 10 rectangles.
So technically the script does draw ten randomly placed, randomly colored rectangles.
It just does that an infinite amount of times.
Note: This is the part that draw ten randomly placed, randomly colored rectangles.
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
pygame.draw.rect(screen, random_color, Rect(random_pos, random_size))
It will draws many rectangles, and its endless yes.It is not drawing 10 times, because there is no arguments says break the while loop except -sys exit-. So you have to define a variable for that.
Normally there should be an if statement in the event statement, that will change the boolean of while to False and it will stop the loop.
You can simply change your codes to:
import pygame
from pygame.locals import *
from sys import exit
from random import *
pygame.init()
screen = pygame.display.set_mode((640, 480), 0,32)
count=0 #new variable for stop the loop
while count<10: #new limit
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.lock()
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
pygame.draw.rect(screen, random_color, Rect(random_pos, random_size))
count+=1 #variable updating after drawing each rectangle
screen.unlock()
pygame.display.update()
Its maybe a misstyping in the book I dont know, but that loop is infinite so you can change it to this :)
Yes, this is a poorly written example, but it's not "broken." Everyone so far has overlooked this bit:
for event in pygame.event.get():
if event.type == QUIT:
exit()
PyGame includes the notion of processing events. This first line gets all the events that your game has access to, and looks at each of them. If one of them is a "QUIT" event, then it calls exit() (more commonly known as sys.exit(0)) directly, which immediately ends the application.
The "QUIT" type event is generated when you quit the application, such as by clicking the red X. There are likely other ways to end the application that will also generate this event, but I'm not sure what they are. There are also ways to end the application that will NOT generate this event. So yes, if you never quit the application it will run forever-ish.
A much better way to write this functionality is as follows:
done = False
while not done:
for event in pygame.event.get():
if event.type == QUIT: # or other types of events
done = True
//do other game logic and drawing stuff after this
pygame.display.update()
This makes sure that the loop is handled consistently, and that the exit point is always at the same place: the top of the loop. If there's only one place to exit the loop, then you have an easier time knowing how much of the code is getting run (all of it). It also allows you to have some more sophisticated ways of deciding when and whether to end the loop.
However, even this is going to be a very confusing "example" because (just like you and #user3679917 noted) it seems to do something different than described. It seems like it's just drawing random rectangles forever. Let's see if we can make it more predictable...
//pre-roll the random numbers before the while loop
random_attributes = []
for count in range(10):
random_color = (randint(0,255), randint(0,255), randint(0,255))
random_pos = (randint(0,639), randint(0,479))
random_size = (639-randint(random_pos[0], 639), 479-randint(random_pos[1],479))
random_attributes.append(random_color, random_pos, random_size)
//then when you draw, just draw what you already rolled each time, without rolling new stats
done = False
while not done:
for event in pygame.event.get():
if event.type == QUIT: # or other types of events
done = True
//do other game logic and drawing stuff after this
for color, position, size in random_attributes:
pygame.draw.rect(screen, color, Rect(position, size))
pygame.display.update()
That will draw the same thing infinitely, until you close the application. Incidentally, this is also what (almost) all visual applications do all the time, i.e. draw everything every time. It's not as if you can draw it on the screen once and it stays. Every time the screen is drawn, the screen asks everyone "Okay guys, what's going on the screen right now," and unless your application says "draw this stuff," then it's not going to get drawn, even if it was drawn last time.
Hope this helps.

Pygame text input not on screen

I need to kb input onto a pygame screen
at the moment it appears on the idle shell
any advice would be appreciated.
This code is extracted from a larger program
mostly screen based but i need to input some
data (numeric) from the kb at times
import sys
import pygame
from pygame.locals import *
pygame.init()
N= ''
screen = pygame.display.set_mode((600,600))
font= pygame.font.Font(None,40)
screen.fill((255,255,255))
pygame.display.flip
pygame.display.update()
def score(C,y):
SetWnd = font.render( C,True,(0,0,255))
screen.blit(SetWnd, (15, 100+y))
pygame.display.update()
def start():
while True:
name=''
for evt in pygame.event.get():
if evt.type == KEYDOWN:
if evt.unicode.isalnum(): # unicode
name+=evt.unicode
print name,
elif evt.key == K_BACKSPACE:
name = name[:-1]
print name,
elif evt.key == K_RETURN:
return N
elif evt.type == QUIT:
pygame.quit()
sys.exit()
def Pchange(c,y):
block = font.render(N, True, (0,0,0))
rect = block.get_rect()
rect.move_ip(75,100 + y)
screen.blit(block,rect)
pygame.display.flip()
score('wind', 0)
score('elev',20)
N = start()
Pchange(N,0)
Pchange(N,20)
Firstly you draw the score twice, which i assume works well.
The problem lies in you start function.
You are not calling any draw or update function in your while loop.
In your event foreach, you add a digit to name, and exit the while loop when enter is pressed. Then you draw twice with Pchange, but you are the function does not use the right parameters. You have:
def Pchange(c,y):
block = font.render(N, True, (0,0,0))
you are using the global N which is ''. So to fix that, you need to change N to c.
The next problem is that the game quits right after pressing enter. Since you pasted only a part of the program, this might not be the case. If it is, make another while loop, and just wait for the ESC key to call pygame.quit() and sys.exit()

Using classes in Pygame

Okay, so I am starting to have fun with pygame. But I've got a problem. I tried to somehow enchance my code, make it organised, so I've decided to use classes here. It looks like this:
import pygame
from pygame.locals import *
import sys
pygame.init()
class MainWindow:
def __init__(self, width, height):
self.width=width
self.height=height
self.display=pygame.display.set_mode((self.width,self.height))
pygame.display.set_caption("Caption")
def background(self)
img = pygame.image.load("image.png")
self.display.blit(img, (0,0))
mainWindow = MainWindow(800,600)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.exit()
sys.exit()
mainWindow.background()
pygame.display.update()
Okay, works. But what if I want to, for example fill the windows with white color? Then I have to define a method fill(), which will just self.display.fill(), right? Is there a way, to handle it normally, without defining hundreds of pygame-already-existing methods in my class?
And one another thing. If I do something by using my class, and I screw up, I always get this msg:
File "C:/Python35/game.py", line 23, in <module>
pygame.display.update()
pygame.error
And I actually don't know what the heck is wrong. If I do this normally, without classes, then I get erros such as, pygame object blabla has no method blablabla or something like that, I just know what's happening. Is there a way to get through this, and find what's going on?
Thanks in advance for your help!
What you are doing here is on the right track, but it is done the wrong way. Your main "game loop" should be inside the class itself as a method, rather than calling stuff from outside the class in an actual loop. Here is a basic example of what you should be doing.
# Load and initialize Modules here
import pygame
pygame.init()
# Window Information
displayw = 800
displayh = 600
window = pygame.display.set_mode((displayw,displayh))
# Clock
windowclock = pygame.time.Clock()
# Load other things such as images and sound files here
image = pygame.image.load("foo.png").convert # Use convert_alpha() for images with transparency
# Main Class
class MainRun(object):
def __init__(self,displayw,displayh):
self.dw = displayw
self.dh = displayh
self.Main()
def Main(self):
#Put all variables up here
stopped = False
while stopped == False:
window.fill((255,255,255)) #Tuple for filling display... Current is white
#Event Tasking
#Add all your event tasking things here
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
elif event.type == pygame.KEYDOWN:
stopped = True
#Add things like player updates here
#Also things like score updates or drawing additional items
# Remember things on top get done first so they will update in the order yours is set at
# Remember to update your clock and display at the end
pygame.display.update()
windowclock.tick(60)
# If you need to reset variables here
# This includes things like score resets
# After your main loop throw in extra things such as a main menu or a pause menu
# Make sure you throw them in your main loop somewhere where they can be activated by the user
# All player classes and object classes should be made outside of the main class and called inside the class
#The end of your code should look something like this
if __name__ == __main__:
MainRun()
The main loop will call itself when the object MainRun() is created.
If you need more examples on specific things such as object handling let me know and I will see if I can throw some more information up for you.
I hope this helps you with your programming and the best of luck to you.
========================= EDIT ================================
In this case for these special operations make them object specific. Instead of using one generic method to blit your objects, make each object have its own function. This is done this way to allow for more options with each object you make. The general idea for the code is below... I have created a simple player object here.
#Simple player object
class Player(object):
def __init__(self,x,y,image):
self.x = x
self.y = y
self.image = image
#Method to draw object
def draw(self):
window.blit(self.image,(self.x,self.y))
#Method to move object (special input of speedx and speedy)
def move(self,speedx,speedy):
self.x += speedx
self.y += speedy
Now here is how you use the object's methods... I have included an event loop to help show how to use the move function. Just add this code to your main loop wherever it is needed and you will be all set.
#Creating a player object
player = Player(0,0,playerimage)
#When you want to draw the player object use its draw() method
player.draw()
#Same for moving the player object
#I have included an event loop to show an example
#I used the arrow keys in this case
speedx = 0
speedy = 0
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
speedy = -5
speedx = 0
elif event.key == pygame.K_DOWN:
speedy = 5
speedx = 0
elif event.key == pygame.K_RIGHT:
speedy = 0
speedx = 5
elif event.key == pygame.K_LEFT:
speedy = 0
speedx = -5
elif event.type == pygame.KEYUP:
speedx = 0
speedy = 0
#Now after you event loop in your object updates section do...
player.move(speedx,speedy)
#And be sure to redraw your player
player.draw()
#The same idea goes for other objects such as obstacles or even scrolling backgrounds
Be sure to use the same display name of the display inside your draw function.

Categories

Resources