Python: why the surface.blit function doesn't display all inputs? - python

I am trying to write a little digits game using pygame. The idea of the game is to guess the four-digit number randomly chosen by computer. But I am stuck at the very beginning I started by creating all the essential elements: colours, fonts, surfaces, etc. I used blit to 'simulate' computer choice and to show the user's guess. And interestingly enough, not all the inputs are displayed. E.g. '1234' and '9999' is displayed. However, '5738' and '7365' are not. Looking forward to hearing opinions of the experienced users.
import random
import pygame
pygame.init()
width = 900
height = 500
black = (0,0,0)
pastel_blue = (200,205,230)
win = pygame.display.set_mode((width,height))
pygame.display.set_caption("Bulls and Cows")
digit_font = pygame.font.SysFont('comicsans', 30)
a = (random.randint(1000, 10000))
print(a)
def display():
win.fill((pastel_blue))
number = digit_font.render("_ _ _ _", 1, black)
win.blit(number, (width//2-number.get_width()//2, height//4))
pygame.display.update()
display()
def guess_number():
global c
c = input("Guess the number: ")
guess_number()
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
win.blit(text, [width//2-text.get_width()/2, 300]) #this seems to be the part that doesn't work correctly
pygame.display.update()
pygame.time.delay(2000)
guess_display()

You have to handle the events in the application loop. See pygame.event.get() respectively pygame.event.pump():
For each frame of your game, you will need to make some sort of call to the event queue. This ensures your program can internally interact with the rest of the operating system.
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
win.blit(text, [width//2-text.get_width()/2, 300]) #this seems to be the part that doesn't work correctly
pygame.display.update()
pygame.event.pump() # <---
pygame.time.delay(2000)
However, the usual way is to use an application loop. Also see Why is my PyGame application not running at all?:
def guess_display():
text = digit_font.render(c, 1, black)
print(text.get_width()//2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.blit(text, [width//2-text.get_width()/2, 300])
pygame.display.update()
Also see:
Why is my display not responding while waiting for input?
Why does pygame.display.update() not work if an input is directly followed after it?
How to create a text input box with pygame?

Related

How to assign a pygame_gui Button to a specific surface

I'm currently working on a project based on pygame. It is the first time I'm attempting a project like this. I choose to try the pygmae_gui library in order to (who wold have guessed) implement a GUI to the project.
I successfully added some buttons to the application without any problems. I wrote a part where a new surface pops up when a certain button is pressed (which works without issues).
I want to add buttons on this new surface, but i don't know how.
My code looks something like this:
def _set_up_surfaces():
screen = pygame.display.set_mode((self.width, self.height))
background = pygame.Surface((self.width, self.height))
new_layer = pygame.Surface((440, 360))
def _set_up_click_button():
start_button_rect = pygame.Rect(0, 0, 150, 30)
bye_button_layout_rect = pygame.Rect(0, 0, 150, 30)
bye_button_layout_rect.bottomright = (-30, -20)
close_button_rect = pygame.Rect(320, 320, 150, 30)
start_button = pygame_gui.elements.UIButton(relative_rect=start_button_rect, text="START",manager=gui_manager)
new_layer_close_button = pygame_gui.elements.UIButton(relative_rect=close_button_rect, text='close', manager=self.gui_manager, container=new_layer)
active = True
# set up start display:
new_layer_visible = False
set_up_surfaces()
set_up_click_button()
# main game loop
while active:
for event in pygame.event.get():
if event.type == pygame.QUIT:
active = False
if event.type == pygame_gui.UI_BUTTON_PRESSED:
if event.ui_element == self.start_button:
self.machine_visible = True
if event.ui_element == self.bye_button:
print('Good Bye!')
active = False
self.gui_manager.process_events(event)
if new_layer_visible:
new_layer_visible.blit(imported_drawing, (0, 0))
self.background.blit(new_layer, (140, 60))
gui_manager.update(self.time_delta)
screen.blit(self.background, (0, 0))
gui_manager.draw_ui(self.screen)
pygame.display.update()
I know that the code does not run as I wrote it here since I only included the part I thought is relevant.
I tried to set the container argument to the surface as I saw as a suggestion online but I got an Error as response:
ValueError: container parameter must be of type IContainerLikeInterface.
The objective is to put the "close-Button" on the new_layer surface. Is this possible using pygame_gui, should I use a different library?
You cannot add a Pygame GUI button to a pygame.Surface object because a pygame.Surface consists only of a grid of pixels and cannot contain objects. Also, the Pygame GUI is built on top of Pygame, not the other way around, so a Pygame object cannot handle a Pygame GUI object and no Pygame object implements the interface IContainerLikeInterface. You have to draw the Surface manually in the background and put the GUI on top of it.

Pygame blit image and text simultaneously

I'm new to pygame and now wanted to blit a background image with a text on top of it. Both together should appear on the screen for only half a second, then both should be gone again. The background image is meant to be full-screen.
What I'm stuck on now: every time, a black screen appears shortly before text and image are visible, they disappear quickly again.
pg.init()
info = pg.display.Info()
window = pg.display.set_mode((info.current_w, info.current_h), pg.FULLSCREEN)
font = pg.font.SysFont("Arial", 33, bold=True)
text = font.render(text, True, (0, 0, 0))
background = pg.image.load('temp.png')
window.blit(background, [0, 0])
window.blit(text, text.get_rect(center = window.get_rect().center))
pg.display.flip()
sleep(0.5)
pg.quit()
I feel like this has to be possibly quite easily, still I haven't found out how to do it just yet.
I'm assuming you are using the time.sleep function in your code. This function isn't good for GUIs since it delays the program for a given amount of time without checking for events and updating the display. This is why a black screen appears. So to fix your issue, I wrote a function called wait that waits a given amount of time without freezing the game.
The wait Function:
def wait(ms):
start = pg.time.get_ticks()
running = True
while running:
for event in pygame.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit(0)
now = pg.time.get_ticks()
if now - start == ms:
running = False
blit_background()
Explanation
It takes one parameter: ms, which is the amount of time that you want the program to wait in milliseconds (1 second = 1000 milliseconds). In the function, I set a variable called start to pygame.time.get_ticks(), which returns the current time. Then, inside a while loop, I created an event loop which checks for the pygame.QUIT event so that if the user tries to close the game while the function is still running, the game will respond and quit the program. After the event loop, I set a variable called now to pygame.time.get_ticks() to get the current time. Then I checked if now (the current time) subtracted by start (the start time) is equal to ms (the given amount of waiting time in milliseconds). This checks if the given amount of time has passed. If it has, the while loop ends. If not, the while loop keeps running until that condition is True.
I also wrote another function called blit_background, which displays the background and the text on the screen. I am calling this function inside the wait function so that the screen can display the background and the text while also waiting for the given amount of time to pass.
The blit_background Function:
def blit_background():
window.blit(background, [0, 0])
window.blit(text, text.get_rect(center=window.get_rect().center))
pg.display.flip()
Full Modified Code:
import pygame as pg
import pygame.time
import sys
pg.init()
info = pg.display.Info()
WIDTH = info.current_w
HEIGHT = info.current_h
window = pg.display.set_mode((WIDTH, HEIGHT), pg.FULLSCREEN)
font = pg.font.SysFont("Arial", 33, bold=True)
text = font.render("text", True, (0, 0, 0))
background = pg.image.load('temp.png')
background = pg.transform.scale(background, (WIDTH, HEIGHT))
def blit_background():
window.blit(background, [0, 0])
window.blit(text, text.get_rect(center=window.get_rect().center))
pg.display.flip()
def wait(ms):
start = pg.time.get_ticks()
running = True
while running:
for event in pygame.event.get():
if event.type == pg.QUIT:
pg.quit()
sys.exit(0)
now = pg.time.get_ticks()
if now - start == ms:
running = False
blit_background()
wait(500)
pg.quit()
sys.exit(0)

How to stop drawing a specific rectangle pygame

My program is a 'Piano Hero' game in pygame which works in the same way as guitar hero except that it is for a computer keyboard and its based on playing the piano rather than the guitar. I am using a design similar to Synthesia for my interface where rectangles come down to a 'hitline' and you have to press the key at the right time.
My problem is that although the rectangles are drawing and working as intended at first, they do not seem to update so that the top ever stops. In other words, every note in the song is infinitely long.
I feel like this is probably where the error is although I am not 100% sure.
def Draw(self,hitLine):
if self.coords[2][1]<hitLine:
self.coords[0][1]+=2
self.coords[1][1]+=2
self.coords[2][1]+=2
self.coords[3][1]+=2
elif self.coords[2][1]>=hitLine and self.coords[0][1]<hitLine:
self.coords[0][1]+=2
self.coords[1][1]+=2
else:
self.drawing = False
pygame.draw.polygon(screen,BLUE,self.coords,0)
pygame.display.update()
This line is inside a while loop which just updates all of the rectangles in the song one at a time.
for z in notes:
if z.drawing:
z.Draw(hitLine)
I found you're question quite fun to work on and is very interesting!
Some items to consider.
It doesn't seem that there is any reason to use a "pygame polygon" for your Note objects which are clearly rectangles. In my code below I used "pygame Rect" objects.
You're main loop doesn't clear the screen every frame.
In your main loop you need to clear the screen every frame. In my code I used Rect objects. The Note stops drawing itself when it's top hits the hitLine.
import pygame
pygame.init()
gameScreen = pygame.display.set_mode((1100, 692))
hitLine = 500
class Note:
def __init__(self, rect):
self.rect = rect
self.drawing = True
def draw(self):
if self.rect.y < hitLine:
self.rect.y += 2
else:
self.drawing = False;
pygame.draw.rect(gameScreen, (0, 0, 255), self.rect, 0)
fNote = Note(pygame.Rect(500, -550, 80, 550))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameScreen.fill((0, 0, 0))
if fNote.drawing:
fNote.draw()
pygame.display.update()

Pygame responds incorrectly to button clicks

I'm having an issue with pygame. I've set up a window that randomly places circles across the screen very quickly, just for testing purposes. There are also three buttons: play/pause (switches back and forth, stops circles from appearing) and an increase speed and decrease speed button. I'm not very experienced with python or pygame, but I've come up with this function to create a clickable button on the screen:
def makeButton(rect, color, hovercolor, text, textsize, textcolor):
clicked = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
mouse = pygame.mouse.get_pos()
rect = pygame.Rect(rect)
displaycolor = color
if rect.collidepoint(mouse):
displaycolor = hovercolor
buttonSurface = pygame.draw.rect(gameDisplay, displaycolor, rect, 0)
font = pygame.font.Font('freesansbold.ttf',textsize)
TextSurf = font.render(text, True, textcolor)
TextRect = TextSurf.get_rect()
TextRect.center = rect.center
gameDisplay.blit(TextSurf, TextRect)
if clicked:
return True
else:
return False
This function can definitely be shortened and simplified, but it has worked for me, up until now. I took out a big chunk of code that I realized was useless (having a completely different block of code to render the button when hovered, instead of just changing the display color). Now, whenever I click any of the three previously-mentioned buttons, it seems to pick a random one and return True, messing up the rest of the program. For example, the play button will increase the speed one time, pressing decrease speed will pause, etc. Sometimes it does do what it is supposed to, but it seems to be random.
Some extra info, if it's useful:
-This function is called three times every tick. It's inside a loop, and if it returns true, its corresponding actions are supposed to be performed (pause or play the game, increase/decrease speed)
-The play/pause button is one button that toggles between green with an 'play' arrow, and red with a pause symbol. They are two separate buttons and functions, and only one of them is executed at a time.
-I have almost zero experience with classes, so they may be way better at handling this situation.
-The only explanation I can think of for this problem is that the returned booleans are getting mixed up between the different places this function is used. I'm pretty sure the problem is within this chunk of code, but ask me and I will post the places it is called too.
"pygame.event.get()" takes one event at a time, and *clears it** from the list of events that need to be processed.
So, more specifically, pygame.event.get() returns each event only once.
Take a look at the following code:
clicked = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
After this is called, all of the events are removed. Here is an analysis of the code. Assume that there are currently two events that haven't been processed, the first being a key pressed down and the other being a mouse button that's been pressed down.
The first event, event.KEYDOWN, is put into the variable "event".
The program checks whether "event" (currently equal to event.KEYDOWN) is equal to event.MOUSEBUTTONDOWN. They are obviously not the same thing, so the next line is skipped.
The second event, event.MOUSEBUTTONDOWN, is put into variable "event". This removes what was previously in the variable "event", removing the first event from existence.
The program checks whether "event" (currently equal to event.MOUSEBUTTONDOWN) is equal to event.MOUSEBUTTONDOWN. It is, so it proceeds to the next line...
"clicked" is set to True, and the for loop exits, because there are no event remaining.
You should now have a better understanding of how Pygame processes events.
There are also many problems with the function you gave (makeButton). You should find a python tutorial to learn the rest. I suggest a book called "Hello World", by Carter and Warren Sande. The book is kind of out of date (teaches Python 2.5), but its code still works with Python 2.7, and it is one of the few decent Python books I've been able to find.
I have included the code to do what you are trying to do. I don't use Rect objects, but if you want them you can change the code to include them. I also didn't include the text, because I am short on time. Instead of placing random circles, this prints text (to the shell) when buttons are clicked.
import pygame, sys
pygame.init()
screen = pygame.display.set_mode([640,480])
clock = pygame.time.Clock()
buttons = []
#buttons = [[rect, color, hovercolor, hovering, clicked, msg]]
def makeButton(rect, color, hovercolor, text):
global buttons
buttons.append([rect, color, hovercolor, False, False, text])
makeButton([0,0,50,50], [0,127,0], [0,255,0], "Clicked Green")
makeButton([50,0,50,50], [190,190,0], [255,255,0], "Clicked Yellow")
makeButton([100,0,50,50], [0,0,127], [0,0,255], "Clicked Blue")
while 1:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
mousepos = event.pos
for a in range(len(buttons)):
if mousepos[0] >= buttons[a][0][0] and mousepos[0] <= buttons[a][0][0]+buttons[a][0][2] and mousepos[1] >= buttons[a][0][1] and mousepos[1] <= buttons[a][0][1]+buttons[a][0][3]:
buttons[3] = True
else:
buttons[3] = False
if event.type == pygame.MOUSEBUTTONDOWN:
mousepos = event.pos
for a in range(len(buttons)):
if mousepos[0] >= buttons[a][0][0] and mousepos[0] <= buttons[a][0][0]+buttons[a][0][2] and mousepos[1] >= buttons[a][0][1] and mousepos[1] <= buttons[a][0][1]+buttons[a][0][3]:
buttons[4] = True
else:
buttons[4] = False
for a in range(len(buttons)):
if buttons[3] == 0:
pygame.draw.rect(screen, buttons[1], buttons[0])
else:
pygame.draw.rect(screen, buttons[2], buttons[0])
if buttons[4] == 1:
buttons[4] = 0
print buttons[5]
pygame.display.flip()
I haven't had the opportunity to test out the code I just typed (using school computer), but it should work. If there are any problems with the code, just leave a comment and I'll fix it.
Also leave a comment if you don't understand something. Don't give up, you can do it!

Pygame: Drawing a circle after input

this seemed like a really simple code, this is why I'm even more confused that it won't work. I'm creating a game that draws different lines of a picture and, after each shape, asks the user what it could be. My problem is that it won't even draw the first circle once I have the input()-part included, but without the input, it works perfectly fine.
import pygame, sys
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((1000, 600))
pygame.display.set_caption('PyDoodle')
clock= pygame.time.Clock()
clock.tick(30)
#importing background pictures:
backPimg = pygame.image.load('Wood.jpg')
backPimg = pygame.image.load('Paper.jpg')
backWx = 0
backWy = 0
backPx = 250
backPy = 0
screen.blit(backWimg, (backWx, backWy))
screen.blit(backPimg, (backPx, backPy))
#colors
black = (0, 0, 0)
#solutions
snowman = ('snowman'.capitalize(), 'snow man'.upper(), 'snowman', 'snow man')
#MAIN GAME
while True:
for event in pygame.event.get()
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
#DRAWING #1: SNOWMAN
#circle 1 - the part that's getting on my nerves
pygame.draw.circle(screen, black, (500,400), 70, 2)
guess1 = raw_input('Your guess:\n')
It'd be really nice if you could have a look at it, maybe you have some suggestions.
The problem is, that pygame does not receive any events while raw_input is waiting for input. (You are not in the event-loop at that point.) As a result you never execute pygame.display.update().
Add pygame.display.update() after pygame.draw.circle(screen, black, (500,400), 70, 2). However then if a redraw of the window is necessary it will not be executed until the input is finished still.
Probably you should use input mechanisms provided by pygame instead.

Categories