Draw over a pygame rect with a smaller width - python

I'm making a Sudoku Solver via pygame and I've been able to draw the whole board, however, while programming the section of code that deals with clicking on a tile, I made it so that the current tile would "light up" green so the user can know the current tile. However, I'm stuck figuring out how to draw over the green highlighted section when a user decides to click on a different tile. I'd like to remove that part entirely but, since it's a rect with a thickness of 4 and the base tiles have a thickness of 1, all I accomplish is having an ugly black line over the thick green line. I tried redrawing the whole board but I obtain similar results. Any help?
class Tile:
'''Represents each white tile/box on the grid'''
def __init__(self, value, window, x1, x2):
self.value = value #value of the num on this grid
self.window = window
self.active = False
self.rect = pygame.Rect(x1, x2, 60, 60) #dimensions for the rectangle
def draw(self, color, thickness):
'''Draws a tile on the board'''
pygame.draw.rect(self.window, color, self.rect, thickness)
pygame.display.flip()
def main():
board = Board(screen)
tiles = board.draw_board() #store the locations of all the tiles on the grid
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONUP:
mousePos = pygame.mouse.get_pos()
#board.draw_board() or (see next comment)
for i in range(9):
for j in range (9): #look for tile we clicked on
#tiles[i][j].draw((0,0,0),1)
#yield same results
if tiles[i][j].is_clicked(mousePos):
tiles[i][j].draw((50,205,50),4) #redraws that tile but with a highlighted color to show it's been clicked
break
main()

You could simply store the highlighted tile in a variable. I also don't think you need a Tile class at all, since a sudoku game state is basically just a list of list of numbers.
Here's a simple example. Note the comments:
import random
import pygame
TILE_SIZE = 64
# function to draw the grid
def draw_board():
board_surface = pygame.Surface((9*TILE_SIZE, 9*TILE_SIZE))
board_surface.fill((255, 255, 255))
for x in range(9):
for y in range(9):
rect = pygame.Rect(x*TILE_SIZE, y*TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(board_surface, (0, 0, 0), rect, 1)
pygame.draw.line(board_surface, (0, 0, 0), (0, 3*TILE_SIZE), (9*TILE_SIZE, 3*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (0, 6*TILE_SIZE), (9*TILE_SIZE, 6*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (3*TILE_SIZE, 0), (3*TILE_SIZE, 9*TILE_SIZE), 5)
pygame.draw.line(board_surface, (0, 0, 0), (6*TILE_SIZE, 0), (6*TILE_SIZE, 9*TILE_SIZE), 5)
return board_surface
def main():
# standard pygame setup
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((9*TILE_SIZE, 9*TILE_SIZE))
font = pygame.font.SysFont(None, 40)
# seperate the game state from the UI
# create a dummy 9x9 sudoku board
state = [[None for _ in range(10)] for _ in range(10)]
for _ in range(15):
x = random.randint(0, 9)
y = random.randint(0, 9)
state[y][x] = random.randint(1, 9)
# a variable to hold the selected tile's position on the board
# (from 0,0 to 8,8)
selected = None
# create the grid surface ONCE and reuse it to clear the screen
board_surface = draw_board()
while True:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
return
# when the player clicks on a tile
# we translate the screen coordinates to the board coordinates
# e.g. a pos of (140, 12) is the tile at (2, 0)
if event.type == pygame.MOUSEBUTTONDOWN:
w_x, w_y = event.pos
selected = w_x // TILE_SIZE, w_y // TILE_SIZE
# clear everything by blitting the grid surface to the screen
screen.blit(board_surface, (0, 0))
# print all numbers in the state to the screen
# we use a Rect here so we can easily center the numbers
rect = pygame.Rect(0, 0, TILE_SIZE, TILE_SIZE)
for line in state:
for tile in line:
if tile != None:
tmp = font.render(str(tile), True, (0, 0, 0))
screen.blit(tmp, tmp.get_rect(center=rect.center))
rect.move_ip(TILE_SIZE, 0)
rect.x = 0
rect.move_ip(0, TILE_SIZE)
# if a tile is selected, we calculate the world coordinates from the board coordinates
# and draw a simple green rect
if selected:
rect = pygame.Rect(selected[0] * TILE_SIZE, selected[1] * TILE_SIZE, TILE_SIZE, TILE_SIZE)
pygame.draw.rect(screen, (0, 200, 0), rect, 5)
clock.tick(30)
pygame.display.flip()
main()
Also, you shouldn't call pygame.display.flip() outside your main loop. It can lead to effects like flickering or images not showing up correctly.

I see several options. I'll start with the ones that will require the least amount of code changes.
Draw your selection rect only inside your tile. Problem solved.
when you draw a tile, draw the border around it as well.
You might also want to store the selected tile in a variable, so you can some stuff with it later on.

Related

Why does this bug happen when I click on two sprites at the same time?

I'm making a simple game using pygame where you keep clicking on tiles as fast as you can until you miss a tile. this is the progress I've made so far. sometimes when I click on a tile (usually when 2 tiles are next to each other and you click between them) one of them does what they're supposed to while the other just disappears from the screen.
import pygame
import random
import sys
#Setting up all possible Tile positions
grid = [[0,0], [0,150], [0,300], [0,450], [0,600],
[150,0],[150,150],[150,300],[150,450],[150,600],
[300,0],[300,150],[300,300],[300,450],[300,600],
[450,0],[450,150],[450,300],[450,450],[450,600],
[600,0],[600,150],[600,300],[600,450],[600,600]]
taken = []
#Classes
class Cursor(pygame.sprite.Sprite):
def __init__(self, pic):
super().__init__()
self.image = pygame.image.load(pic).convert_alpha()
self.image = pygame.transform.scale(self.image, (50,50))
self.rect = self.image.get_rect()
def destroyTile(self):
pygame.sprite.spritecollide(cursor, tileGroup, True)
def update(self):
self.rect.topleft = pygame.mouse.get_pos()
class Tiles(pygame.sprite.Sprite):
def __init__(self, tileSize, color, x, y):
super().__init__()
self.image = pygame.Surface(([tileSize, tileSize]))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.topleft = [x, y]
def drawTiles():
takenLen = len(taken)
while takenLen != 3:
m = random.randint(0,24)
x, y = grid[m]
if grid[m] not in taken:
blackTile = Tiles(150, black, x, y)
blackTile.add(tileGroup)
taken.append(grid[m])
takenLen += 1
def handleTiles():
mx, my = pygame.mouse.get_pos()
modx = mx % 150
mody = my % 150
x = mx - modx
y = my - mody
taken.remove([x, y])
drawTiles()
def drawRedTile():
mx, my = pygame.mouse.get_pos()
modx = mx % 150
mody = my % 150
x = mx - modx
y = my - mody
redTile = Tiles(150, red, x, y)
redTile.add(tileGroup)
#Colours
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
blue = (0, 0, 255)
grey = (46, 46, 46)
#Initializing Pygame
pygame.init()
clock = pygame.time.Clock()
#Screen
screenWidth = 750
screenHeight = 900
screen = pygame.display.set_mode((screenWidth, screenHeight))
pygame.display.set_caption("Tiles Game")
whiteSurface = pygame.Surface((750, 750))
whiteSurface.fill(white)
pygame.mouse.set_visible(False)
#Blue line
line = pygame.Surface((750, 10))
line.fill(blue)
#Groups
tileGroup = pygame.sprite.Group()
cursor = Cursor("cursor.png")
cursorGroup = pygame.sprite.Group()
cursorGroup.add(cursor)
score = 0
drawTiles()
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
score += 1
print(score)
print(taken)
print(tileGroup)
cursor.destroyTile()
handleTiles()
#Background
screen.fill(grey)
screen.blit(whiteSurface, (0,0))
screen.blit(line, (0,750))
tileGroup.draw(screen)
cursorGroup.draw(screen)
cursorGroup.update()
pygame.display.update()
In the code I tried using print statements to see if the tile that seems to have disappeared is still there. When this happens, I assume that the tile is not in its group anymore since the number of sprites in the tile group went from 3 to 2. But the list showing all the taken positions still shows that there are 3 positions that are taken. I can still click on the tile if I just click on the space where there should be a tile and the tile comes back. I thought the game should exit when a tile isn't clicked on but it doesn't if there is an "invisible" tile in that position.
How do I make it so that this bug doesn't happen and every new tile made is visible?
The problem is that the cursor has an area and can hit more than one block at a time. So in destroyTile more than 1 block can be removed at once:
def destroyTile(self):
pygame.sprite.spritecollide(cursor, tileGroup, True)
However, the function handleTiles cannot handle this, because it can only remove one block position from the taken list. I suggest to simplify the code and recreate the taken list completely from tileGroup when blocks are removed:
def handleTiles():
taken.clear()
for tile in tileGroup:
x, y = tile.rect.topleft
taken.append([x, y])
drawTiles()

How to get mouse coordinates on a grid [duplicate]

This question already has answers here:
Is it possible to update a pygame drawing attribute when a event occurs?
(1 answer)
Python: How to make drawn elements snap to grid in pygame
(1 answer)
Closed 5 months ago.
I have a function to get a position on a grid (the size of the grid is flexible) based on the mouse position
def get_coords_from_mouse_pos(self, mx, my):
return mx // self.item_width, my // self.item_height
self.item_width and self.item_height are the width and height of one tile on the grid. mx and my are the mouse x and y positions. This probably exists somewhere else but I can't find it.
I probably just have the math wrong, but I would appreciate it if someone could help.
Here's a minimal example that shows your logic working, the left and top grid borders are considered as part of the cell below.
import pygame
# Configuration
width, height = 320, 240
cell_width, cell_height = 32, 32
pygame.init()
sys_font = pygame.font.SysFont(None, 60)
text = sys_font.render(" ", True, "turquoise")
clock = pygame.time.Clock()
screen = pygame.display.set_mode((width, height))
# create transparent background grid
grid = pygame.Surface((width, height), flags=pygame.SRCALPHA)
for y in range(0, height, cell_height):
pygame.draw.aaline(grid, "green", (0, y), (width, y)) # horizontal lines
for x in range(0, width, cell_width):
pygame.draw.aaline(grid, "green", (x, 0), (x, height)) # vertical lines
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
mx, my = pygame.mouse.get_pos()
pygame.display.set_caption(f"Mouse: {mx}, {my}")
if mx > 0 and my > 0:
cellx = mx // cell_width
celly = my // cell_height
text = sys_font.render(f"Cell: {cellx}, {celly}", True, "turquoise")
# Graphics
screen.fill("grey25")
screen.blit(grid, (0, 0))
# Draw Text in the center
screen.blit(text, text.get_rect(center=screen.get_rect().center))
# Update Screen
pygame.display.update()
clock.tick(30)
pygame.quit()
This will look something like this:

All Button Boxes and Text are black

It's me again...
As my most recent questions have been discussing, I am making a main menu navigation for a game. Everything was going fine and dandy until a recent bug that has turned all text, either in buttons or simply drawn on the screen, black. I'm not sure why this is and I was hoping that someone smarter than me could figure it out. I really need this done so any help is greatly greatly appreciated.
# Libraries #
import pygame # Imports the Pygame library.
from pygame.locals import * # Imports the Pygame Locals library.
import ResolutionMenu # Imports the ResolutionMenu file (must be stored in the same location as this file to work)
# Classes #
class GuiSettings: # Class used to define the colour and size of buttons.
def __init__(self):
self.TextSize = 26
self.button_color = (35, 65, 145)
self.button_color_hover = (70, 105, 150)
class Button():
def __init__(self, x, y, width, height, outline, settings, text = "", action = None): # Classes used to actually create a button.
self.x = x # Screen is divided into an axis, x being horizontal.
self.y = y # And Y being vertical. The maximum bound changes depending on screen resolution.
self.width = width # This determines the dimensions of the box that is going to be created.
self.height = height # Height and Width are measured in pixels, similar to X and Y.
self.text = text # Allows text to be printed over the top of the button.
self.settings = settings # Allows it to be manipulated by the GuiSettings.
self.action = action # Will be used to determine what 'action' the user is performing on the button.
self.outline = outline # Will be used to determine if an outline is or is not drawn around the button.
def draw(self, screen, outline = None):
if self.outline: # Used to draw a black outline around the perimeter of the square.
pygame.draw.rect(screen, self.outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
if self.text != "":
font = pygame.font.SysFont('segoeuisemibold', self.settings.TextSize) # Settings for text inside of the button box.
text = font.render(self.text, 1, (0, 0, 0)) # Renders the font with the variables 'text' which is the text on the box, anti-aliasing is the second
# option, of which removes the jagged effect that might appear around the box.
# and finally the colour of the outline.
screen.blit(text, (self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2))) # Places the text in the middle of the box.
def update(self, events): # Simply used to determine what actions need to be taken when the button is pressed, if any.
for event in events:
if event.type == pygame.MOUSEBUTTONDOWN and self.isOver(pygame.mouse.get_pos()) and self.action:
self.action()
def isOver(self, pos): # Determines if the cursor is actually over the button box or not.
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
# Variables #
settings = GuiSettings() # Assigning class to variable to access parameters.
H, W = ResolutionMenu.resolution # Splits the tuple value of resolution into individual segments and assigns them their respective variables.
pygame.init() # Initialises the pygame library.
pygame.display.set_caption("Main Menu") # Creates a title for the program.
font = pygame.font.SysFont(None, settings.TextSize) # Sets the default font to that of the font on the System. Allows for cross platform usability.
screen = pygame.display.set_mode((ResolutionMenu.resolution)) # Sets the resolution to the resolution determined in ResolutionMenu.py.
# Functions #
def draw_text_center(text, font, colour, surface):
textobj = font.render(text, 1, colour) # Function for simple text creation. No button surrounding.
textrect = textobj.get_rect() # Creates a font taking the string 'text', sets antialiasing to true and sets the text colour.
textrect.center = ((H / 2), (W / 10)) # Creates a rectangle around 'textobj' which is simply the text.
surface.blit(textobj, textrect) # Finds the center of the screens
def draw_text(text, font, colour, surface, XandY): # Same function as above but with manipulatable positioning.
textobj = font.render(text, 1, colour)
textrect = textobj.get_rect()
textrect.topleft = (XandY)
surface.blit(textobj, textrect)
def MainMenu():
global font
while True:
pygame.display.set_mode((H, W)) # Sets the resolution to the resolution chosen before.
pygame.display.flip() # Updates the surface display.
buttons = [
Button(W / 10, H / 5, W / 3, H / 15, (0, 0, 0), settings, "Start Simulation", lambda: game()), # Buttons that will be displayed are placed inside of a list.
Button(W / 10, H / 2, W / 3, H / 15, (0, 0, 0), settings, "Options Menu", lambda: OptionsMenu()) # Lambda functions are one time use functions without names. This allows each
] # button to have a single use function that directs the program to the specified section.
running = True
while running:
events = pygame.event.get() # Gathers all events (user made inputs) that could occur.
for event in events:
if event.type == pygame.QUIT: # When the X is pressed in the corner of the application, it shall close.
pygame.quit()
return
if event.type == KEYDOWN: # When pressing Esc the function is broken and refreshes.
if event.key == K_ESCAPE:
running = False
for button in buttons:
button.update(events) # Enables the button to be interacted with.
screen.fill((100, 100, 100)) # Creates a grey background.
draw_text_center("Main Menu Navigation", font, (0, 0, 0), screen) # Creates Main Menu Navigation text in the top middle of the screen.
font = pygame.font.SysFont(None, settings.TextSize) # Makes the font size equal to that of settings.TextSize from GuiSettings class.
for button in buttons:
button.draw(screen) # Draws the buttons on the window.
pygame.display.flip() # Updates the surface display again.
pygame.display.update() # Updates a portion of the screen, allowing for text to render.
def game():
running = True
while running:
screen.fill((100, 100, 100))
draw_text_center("Simulation Navigation", font, (0, 0, 0), screen)
buttons = [
Button(W / 10, H / 5, W / 3, H / 15, (0, 0, 0), settings, "Enter Simulation", lambda: FileTraversal()),
Button(W / 10, H / 2, W / 3, H / 15, (0, 0, 0), settings, "Instructions", lambda: Instructions())
]
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
for button in buttons:
button.update(events)
for button in buttons:
button.draw(screen)
pygame.display.flip()
pygame.display.update()
def Instructions():
running = True
while running:
screen.fill((100, 100, 100))
draw_text("This is the simulation manual.", font, (0, 0, 0), screen, (50, 50))
draw_text("There are a few button combinations that will perform different tasks:", font, (0, 0, 0), screen, (50, 100))
draw_text("P", font, (200, 0, 0), screen, (50, 150))
draw_text(": Pauses the simulation.", font, (0, 0, 0), screen, (70, 150))
draw_text("D", font, (200, 0, 0), screen, (50, 200))
draw_text(": Toggles drawing mode. Allows for faster computation.", font, (0, 0, 0), screen, (70, 200))
draw_text("F", font, (200, 0, 0), screen, (50, 250))
draw_text(": Toggles the display of food. Increases FPS.", font, (0, 0, 0), screen, (70, 250))
draw_text("+ and -", font, (200, 0, 0), screen, (50, 300))
draw_text(": Increases or Decreases the speed of the simulation. FPS impact.", font, (0, 0, 0), screen, (130, 300))
draw_text("Middle Mouse Click", font, (200, 0, 0), screen, (50, 350))
draw_text(": Zoom in and out.", font, (0, 0, 0), screen, (250, 350))
draw_text("Right Mouse Click", font, (200, 0, 0), screen, (50, 400))
draw_text(": Pan around the environment", font, (0, 0, 0), screen, (240, 400)) # All of these are displayed on the screen as instructions for the user.
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
pygame.display.flip()
pygame.display.update()
def FileTraversal():
import os # Imports the Operating System library that allows for command line execution.
import platform # Imports the Platform library that allows for system analysis.
p = platform.system() # Assign the variable p the name of the operating system.
if p == "Linux":
os.chdir('/home/CHANGE/Desktop/School Work/New') # Directory traversal.
pygame.display.iconify() # Linux systems do not run EXE's. This file is an ELF file
os.system('./mygame') # and so must be run from terminal.
if p == "Windows":
os.chdir('C:/Users/CHANGE/Desktop/Project/') # Directory traversal.
# Uses EXE's as ELF's require Linux kernals.
if p == "OSX":
None
def OptionsMenu():
global settings # Functions use local variables. In order to manipulate the global value, global settings is specified.
pygame.display.set_mode((800, 600))
buttons = [
Button(100, 150, 250, 50, (0, 0, 0), settings, "Set Text Size: Small", lambda: settings.__setattr__('TextSize', 24)), # Button uses a lambda function to access
Button(450, 150, 250, 50, (0, 0, 0), settings, "Set Text Size: Regular", lambda: settings.__setattr__('TextSize', 26)), # the attributes of settings, which is the class
Button(100, 300, 250, 50, (0, 0, 0), settings, "Set Text Size: Large", lambda: settings.__setattr__('TextSize', 28)), # GuiSettings. It then sets the value of TextSize to
Button(450, 300, 250, 50, (0, 0, 0), settings, "Set Text Size: Larger", lambda: settings.__setattr__('TextSize', 30)) # the corresponding value, depending on the button pressed.
]
running = True
while running:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
pygame.display.set_mode((ResolutionMenu.resolution)) # Bug fix. Returns the resolution to the previously selected.
for button in buttons:
button.update(events)
screen.fill((100, 100, 100))
for button in buttons:
button.draw(screen)
pygame.display.flip()
MainMenu()
To whomever reads through all of this, good luck reading through all of that rubbish lol
**Added the code for ResolutionMenu below
# Libraries #
from tkinter import *
import time
# Variables List #
root = Tk() # Initialises Tkinter under the variable 'root'.
root.title("Resolution") # Applies a name to the window.
root.geometry("230x200") # Sets the window's geometry.
displays = 800, 600, 1024, 786, 1280, 1024, 1600, 1200, 1920, 1080 # Uses a string for resolutions.
running = True
v = IntVar() # Assignes the value of V to be an integer.
# Functions #
def CONFIRM(label1):
global resolution # Global variables used to access / manipulate outside of function.
global selection
selection = v.get() # Takes the value from the radio buttons.
if selection == 1:
resolution = (displays[0:2]) # Iterative Index naviation. 0 (0) indicates the first index up until the 2nd (2), but not including (, index.
time.sleep(0.2) # Pauses the program for .2 seconds for fluid transition appearance.
root.destroy() # Closes the TK window as it is no longer needed.
elif selection == 2:
resolution = (displays[2:4])
time.sleep(0.2)
root.destroy()
elif selection == 3:
resolution = (displays[4:6])
time.sleep(0.2)
root.destroy()
elif selection == 4:
resolution = (displays[6:8])
time.sleep(0.2)
root.destroy()
elif selection == 5:
resolution = (displays[8:10])
time.sleep(0.2)
root.destroy()
else:
resolution = (displays[0:2])
time.sleep(0.2)
root.destroy()
# Radio Button Creation #
v.set(1) # Validation. Sets the variable of V to 1 (radiobutton1) automatically.
Radio1 = Radiobutton(root, font = "Veranda 10", text = "800 x 600", variable = v, value = 1).pack(anchor = W)
Radio2 = Radiobutton(root, font = "Veranda 10", text = "1024 x 768", variable = v, value = 2).pack(anchor = W)
Radio3 = Radiobutton(root, font = "Veranda 10", text = "1280 x 1024", variable = v, value = 3).pack(anchor = W)
Radio4 = Radiobutton(root, font = "Veranda 10", text = "1600 x 1200", variable = v, value = 4).pack(anchor = W)
Radio5 = Radiobutton(root, font = "Veranda 10", text = "1920 x 1080", variable = v, value = 5).pack(anchor = W)
# Each of the above create a radio button. Using Tkinter's pre-made Radiobutton() function, you can simply link
# the radio button to the necessary window, in this case "root", the font size and the desired text message
# a variable that will be used to hold the value of the selection, followed by an integer that will be stored
# inside of said variable. Finally we pack it using .pack() function and give it an anchor, ie the direction it is aligned.
label1 = Label(root,text="") # Temp variable to hold information for lambda expression.
b = Button(root, font = "Veranda 10", text = "Confirm", command = lambda:CONFIRM(label1)) # Lambda creates temporary in-line function to pass selection to Confirm()
b.pack(anchor = SE)
label1.pack()
# Program Loop #
mainloop()
Okay! I solved the issue; it turns out some lines of code in my 'button' class had been deleted by accident. I had a backup of the code and after replacing the class with the working version, everything is back to normal.
The fixed class code will be at the bottom.
Thank you to #Nebulous29 for making the suggestion to change the value of the outline to (255, 0, 0). That's not actually the colour of the button but the outline surrounding the button. After I changed that I realised that something wasn't working with the outline function.
Fixed Code:
class Button():
def __init__(self, x, y, width, height, outline, settings, text = "", action = None):
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
self.settings = settings
self.action = action
self.outline = outline
def draw(self, screen, outline = None):
if self.outline:
pygame.draw.rect(screen, self.outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
color = self.settings.button_color if not self.isOver(pygame.mouse.get_pos()) else self.settings.button_color_hover
pygame.draw.rect(screen, color, (self.x, self.y, self.width, self.height), 0)
if self.text != "":
font = pygame.font.SysFont('segoeuisemibold', self.settings.TextSize)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def update(self, events):
for event in events:
if event.type == pygame.MOUSEBUTTONDOWN and self.isOver(pygame.mouse.get_pos()) and self.action:
self.action()
def isOver(self, pos):
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
The problem of the black background lies when you create your buttons:
Button(W / 10, H / 5, W / 3, H / 15, (0, 0, 0), settings, "Start Simulation", lambda: game()), # Buttons that will be displayed are placed inside of a list.
After the width and heights are set you have (0, 0, 0) which sets the button colour to black. Changing this to something like (255, 0, 0) would cause the background to be red and the text to be black (making the button at least readable).
I'm not sure why the button hover colour and button colour you've set in GuiSettings isn't updating the button though, but at least its a somewhat temporary fix

Is there any way to convert the screen of a pygame gui into an image somehow?

So I'm working on an interactive version of the MNIST handwritten digit image classification project with pygame, where the user draws on the gui and based off of that, the model which I have already made will look at the screen and output a prediction as to what number the image contains. My problem is that I'm not sure what approach to take to make whatever is displayed on the gui into an input of sorts for my model to predict (an image is required as an input)
Here's the code that I made for the gui:
import pygame as pg
#I'm importing a function that I made with my model
#Takes an image input and spits out a prediction as to what the number displayed in the image should be
from MNIST_Classification_GUI import makePrediction
pg.init()
screen = pg.display.set_mode([800, 600])
pg.display.set_caption("Draw a Number")
radius = 10
black = (0, 0, 0)
isGoing = True
screen.fill((255, 255, 255))
last_pos = (0, 0)
def roundline(srf, color, start, end, radius=1):
dx = end[0]-start[0]
dy = end[1]-start[1]
distance = max(abs(dx), abs(dy))
for i in range(distance):
x = int( start[0]+float(i)/distance*dx)
y = int( start[1]+float(i)/distance*dy)
pg.draw.circle(srf, color, (x, y), radius)
#To be used for the popup text containing the prediction
pg.font.init()
myFont = pg.font.SysFont("Sans Serif", 10)
draw_on = False
while isGoing:
for event in pg.event.get():
if event.type == pg.QUIT:
isGoing = False
if event.type == pg.MOUSEBUTTONDOWN:
spot = event.pos
pg.draw.circle(screen, black, spot, radius)
draw_on = True
#This is the part where I want to somehow obtain an image from the gui
#So when the user stops drawing, the popup text appears with the prediction
if event.type == pg.MOUSEBUTTONUP:
draw_on = False
#The makePrediction takes an image input and returns the predicted value
prediction = makePrediction(screen)
textSurface = myFont.render(f"The number should be {prediction}", False, black)
screen.blit(textSurface, (0, 0))
if event.type == pg.MOUSEMOTION:
if draw_on:
pg.draw.circle(screen, black, event.pos, radius)
roundline(screen, black, event.pos, last_pos, radius)
last_pos = event.pos
pg.display.flip()
pg.quit()
The PyGame display (window) is associated to a pygame.Surface object. Use pygame.image.save() to store the content of a Surface to a bitmap. The file type is automatically determined by the file extension:
pygame.image.save(screen, "screenshot.png")

How to create a pop-up in pygame with transparent background and how to control loop as a line is been drawn?

def timer_initial_state(screen):
'''Creates an thin line representing the timer'''
pygame.draw.line(screen,
COLOR_ORANGE_RED,
(0,screen.get_height()*0.35),
(screen.get_width(), screen.get_height()*0.35),
3)
def timing_sequence(screen):
'''Creates a timer in the form of a line'''
increment = 0.01
while increment >=0:
pygame.draw.line(
screen,
COLOR_BUTTON_UP,
(screen.get_width()*increment, screen.get_height()*0.35),
(0, screen.get_height()*0.35),
3)
pygame.draw.line(
screen,
COLOR_ORANGE_RED,
(0, screen.get_height()*0.35),
(screen.get_width(),screen.get_height()*0.35),
3)
increment += 0.01
The first function draws the line. The goal of the second is to draw over the first line in at a specific interval like that of a clock. ie. it should draw over a portion of the line after 1 second.
What I want to do looks just like this:
Furthermore, I want to show a popup after the timer is done with a transparent background.
Something like this will progressively draw a line:
import pygame, sys
from pygame.locals import *
FPS = 30 # frames per second
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
YELLOW = (255, 255, 0)
RED = (255, 0, 0)
class ProgressiveLine(object):
def __init__(self, x1, y1, x2, y2, color=WHITE, width=1):
self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2
self.color, self.width = color, width
self.dx = self.x2-self.x1
self.dy = self.y2-self.y1
def draw(self, surface, percentage):
percentage = min(abs(percentage), 1.0)
xcur = self.x1 + self.dx*percentage
ycur = self.y1 + self.dy*percentage
pygame.draw.line(surface, self.color, (self.x1, self.y1), (xcur, ycur),
self.width)
def main():
pygame.init()
fpsclock = pygame.time.Clock()
screen = pygame.display.set_mode((500,400), 0, 32)
screen.fill(WHITE)
prgrsline = ProgressiveLine(10, 390, 490, 10, RED, 3)
percent_complete = 0.0
while True: # display update loop
screen.fill(WHITE)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if percent_complete < 1.0:
percent_complete += 0.01
prgrsline.draw(screen, percent_complete)
pygame.display.update()
fpsclock.tick(FPS)
main()
As martineau said you need to fix your loop so it will complete.
You are drawing your partial line first, then drawing the full line over it. You need to draw the order of on top last.
You also need to flip (or update) the display after your draw.
This should all be done inside an event loops so your app will still responding to events.
Timing based on per frames is a bit ugly. I would have a number of seconds for animation and a start time and compute the increment = (now - start_time) / animation_time
maybe it would be better to have this just be a function that draws based on time and not have its own while loop.

Categories