Related
I have made a sand simulation in tkinter. Only tkinter. No pygame, no matplotlib, none of that. It works great, it looks great, but nothing in life is perfect.
Minor Problem #1: When I hold down and let the sand fall, the column where the cursor is stays the same color. I haven't been able to track this down yet.
Minor Problem #2: When I create happy little piles of sand, the sides seem to build up from the bottom rather that fall from the top. I suspect this is a rendering issue, but I also haven't found the reason for this.
That's all the problems I've noticed, but if you see any others feel free to let me know.
Code:
from tkinter import *
from random import choice, random
from copy import deepcopy
from colorsys import hsv_to_rgb
CELLSIZE = 30
AIR = 0
WALL = 1
SAND = 2
BG = '#cef'
SANDCOLOR = (45, 45, 86)
WALLCOLOR = (224, 37, 34)
TARGETFPS = 100
def randomColor(h, s, v):
h, s, v = (h / 360), s / 100, v / 100
s += (random() - 0.5) * 0.1
v += (random() - 0.5) * 0.1
if s < 0: s = 0
if s > 1: s = 1
if v < 0: v = 0
if v > 1: v = 1
r, g, b = [round(i * 255) for i in hsv_to_rgb(h, s, v)]
return '#%02x%02x%02x' % (r, g, b)
class App:
def __init__(self):
global WIDTH, HEIGHT, SANDCOLOR, WALLCOLOR
self.master = Tk()
self.master.title('Sand Simulation')
self.master.resizable(0, 0)
self.master.attributes('-fullscreen', True)
WIDTH = self.master.winfo_screenwidth() // CELLSIZE
HEIGHT = self.master.winfo_screenheight() // CELLSIZE
Width, Height = WIDTH * CELLSIZE, HEIGHT * CELLSIZE
self.canvas = Canvas(self.master, width=Width, height=Height, bg=BG, highlightthickness=0)
self.canvas.pack()
self.map = [[AIR] * WIDTH for i in range(HEIGHT)]
self.colors = [[BG] * WIDTH for i in range(HEIGHT)]
self.positions = []
for x in range(WIDTH):
for y in range(HEIGHT):
self.positions.append([x, y])
self.positions.reverse()
self.dragging, self.dragX, self.dragY = False, 0, 0
self.canvas.bind('<Button-1>', self.mouseDown)
self.canvas.bind('<B1-Motion>', self.mouseDrag)
self.canvas.bind('<ButtonRelease-1>', self.mouseUp)
## self.images = [PhotoImage(file='images/sandButton.png'), PhotoImage(file='images/sandButtonActivated.png'),
## PhotoImage(file='images/wallButton.png'), PhotoImage(file='images/wallButtonActivated.png')]
self.images = [PhotoImage().blank(), PhotoImage().blank(), PhotoImage().blank(), PhotoImage().blank()]
self.sandButton = self.canvas.create_image(125, 125, anchor='center', image=self.images[1])
self.wallButton = self.canvas.create_image(125, 325, anchor='center', image=self.images[2])
self.drawingMode = 'SAND'
self.master.after(round(1 / TARGETFPS * 1000), self.frame)
self.master.mainloop()
def swapBlocks(self, x1, y1, x2, y2):
block1 = self.map[y1][x1]
color1 = self.colors[y1][x1]
self.map[y1][x1] = self.map[y2][x2]
self.colors[y1][x1] = self.colors[y2][x2]
self.map[y2][x2] = block1
self.colors[y2][x2] = color1
def mouseDown(self, event):
if 50 < event.x < 200 and 50 < event.y < 200:
self.drawingMode = 'SAND'
self.canvas.itemconfig(self.sandButton, image=self.images[1])
self.canvas.itemconfig(self.wallButton, image=self.images[2])
elif 50 < event.x < 200 and 250 < event.y < 400:
self.drawingMode = 'WALL'
self.canvas.itemconfig(self.sandButton, image=self.images[0])
self.canvas.itemconfig(self.wallButton, image=self.images[3])
else:
self.dragging = True
self.dragX = event.x // CELLSIZE
self.dragY = event.y // CELLSIZE
if self.dragX > WIDTH - 1: self.dragX = WIDTH - 1
if self.dragX < 0: self.dragX = 0
if self.dragY > HEIGHT - 1: self.dragY = HEIGHT - 1
if self.dragY < 0: self.dragY = 0
def mouseDrag(self, event):
self.dragX = event.x // CELLSIZE
self.dragY = event.y // CELLSIZE
if self.dragX > WIDTH - 1: self.dragX = WIDTH - 1
if self.dragX < 0: self.dragX = 0
if self.dragY > HEIGHT - 1: self.dragY = HEIGHT - 1
if self.dragY < 0: self.dragY = 0
def mouseUp(self, event):
self.dragging = False
def updateParticles(self):
if self.dragging:
color = choice(['red', 'white', 'blue'])
if self.drawingMode == 'SAND':
self.map[self.dragY][self.dragX] = SAND
self.colors[self.dragY][self.dragX] = randomColor(SANDCOLOR[0], SANDCOLOR[1], SANDCOLOR[2])
elif self.drawingMode == 'WALL':
self.map[self.dragY][self.dragX] = WALL
self.colors[self.dragY][self.dragX] = randomColor(WALLCOLOR[0], WALLCOLOR[1], WALLCOLOR[2])
for block in self.positions:
x, y = block
block = self.map[y][x]
if block == SAND:
if y == HEIGHT - 1:
below = WALL
else:
below = self.map[y + 1][x]
if below == AIR:
self.swapBlocks(x, y, x, y + 1)
else:
left, right, belowLeft, belowRight = AIR, AIR, AIR, AIR
if y == HEIGHT - 1:
belowLeft, belowRight = WALL, WALL
else:
if x == 0:
belowLeft = WALL
left = WALL
else:
belowLeft = self.map[y + 1][x - 1]
left = self.map[y][x - 1]
if x == WIDTH - 1:
belowRight = WALL
right = WALL
else:
belowRight = self.map[y + 1][x + 1]
right = self.map[y][x + 1]
if belowLeft == AIR and belowRight == AIR:
if choice([True, False]):
if left == AIR:
self.swapBlocks(x, y, x - 1, y + 1)
else:
if right == AIR:
self.swapBlocks(x, y, x + 1, y + 1)
else:
if belowLeft == AIR and left == AIR:
self.swapBlocks(x, y, x - 1, y + 1)
if belowRight == AIR and right == AIR:
self.swapBlocks(x, y, x + 1, y + 1)
def renderMap(self, previousMap):
for block in self.positions:
x, y = block
previousBlock = previousMap[y][x]
currentBlock = self.map[y][x]
x1, y1 = x * CELLSIZE, y * CELLSIZE
x2, y2 = x1 + CELLSIZE, y1 + CELLSIZE
if previousBlock == AIR and currentBlock != AIR:
if currentBlock == WALL: color = self.colors[y][x]
if currentBlock == SAND: color = self.colors[y][x]
rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline='', fill=color)
self.canvas.tag_lower(rect)
if previousBlock != AIR and currentBlock == AIR:
blockAtPosition = self.canvas.find_enclosed(x1, y1, x2, y2)
self.canvas.delete(blockAtPosition)
if previousBlock != AIR and currentBlock != AIR and previousBlock != currentBlock:
blockAtPosition = self.canvas.find_enclosed(x1, y1, x2, y2)
self.canvas.delete(blockAtPosition)
if currentBlock == WALL: color = self.colors[y][x]
if currentBlock == SAND: color = self.colors[y][x]
rect = self.canvas.create_rectangle(x1, y1, x2, y2, outline='', fill=color)
self.canvas.tag_lower(rect)
self.canvas.update()
def frame(self):
previousMap = deepcopy(self.map)
self.updateParticles()
self.renderMap(previousMap)
self.master.after(round(1 / TARGETFPS * 1000), self.frame)
def main():
app = App()
if __name__ == '__main__':
main()
Please help me fix any bugs, glitches, etc...
Problem #1 is caused by the fact that your rendering function doesn't create a new rectangle if both the old and new block types are sand. As the sand falls in a column, the first bit of sand causes rectangles to be created with its color, and because another bit of sand always falls in its place on the next frame, it just stays that color. You could compare old and new colors to see which blocks need refreshing, or store which blocks changed.
Problem #2 is related to the order of your positions. You're inserting [x,y] positions into the positions array from the left to the right, one column at a time, each column from top to bottom, and then reversing the result, which gives you an ordering of bottom to top per column with the columns from right to left. So when you iterate through the positions, you're essentially sweeping from right to left as you process the blocks, so any block that falls to the left is going to be reprocessed in the same update, and as it keeps falling to the left it will keep being reprocessed, until it settles, and all of that happened in one frame. So particles will seem to fall to the left instantly.
I bet you don't have this problem with particles falling to the right, which is why on the right side of your image you have some diagonal bands of color: problem #1 is happening there too any time two sand blocks fall to the right in sequence.
You can fix problem #2 by reordering your width and height loops when you set up self.positions. Since blocks always move down one row when they are moved, iterating from bottom to top will always update each particle only once. If you ever introduce anything that makes particles move up, you'll need a better solution.
This question already has answers here:
Random systematic movement in pygame
(1 answer)
How do i correctly update the position and velocity of 100 class objects individually drawing with pygame?
(1 answer)
Pygame change particle color
(1 answer)
Closed 1 year ago.
so i was wondering if i could quickly spawn a bunch of different shapes in pygame and have them all move randomly and spawn at different places without needing to write a bunch of if's. Here's the code:
import pygame
import random
import time
# Initializing Pygame
pygame.init()
# Initializing surface
surface = pygame.display.set_mode((5000,5000))
x = 300
y = 100
x2 = 150
y2 = 150
# Initialing Color
color2 = (0,255,0)
color = (255,0,0)
pygame.draw.rect(surface, color, pygame.Rect(30, 30, 60, 60))
# Drawing Rectangle
pygame.display.flip()
while True:
time.sleep(0.2)
var = random.randint(1,4)
var2 = random.randint(1,4)
if (var == 1):
x += 20
if (var == 2):
x -= 20
if (var == 3):
y += 20
if (var == 4):
y -= 20
if (var2 == 1):
x2 += 20
if (var2 == 2):
x2 -= 20
if (var2 == 3):
y2 += 20
if (var2 == 4):
y2 -= 20
surface.fill((0,0,0))
pygame.draw.rect(surface, color, pygame.Rect(x, y, 60, 60))
pygame.draw.rect(surface, color2, pygame.Rect(x2, y2, 60, 60))
if (x == x2 or y == y2 and color == (255,0,0)):
color2 = color
print("oof")
pygame.display.update()
i have a problem with this code, i am a new person with programming and been using the book "how to think like a computer scientist 3rd edition" and he did not solve exercise 2 of chapter 17 this given: "he deliberately left a mistake in the code to animate Duke. If you click on one of the checkerboard squares to the right of Duke, he salutes anyway. Why? Find a one-line solution to the error ", I've tried many forms but I have not succeeded, I leave you all the code and the images that I have used
PS: images must have the name: ball.png and duke_spritesheet.png
import pygame
gravity = 0.025
my_clock = pygame.time.Clock()
class QueenSprite:
def __init__(self, img, target_posn):
self.image = img
self.target_posn = target_posn
(x, y) = target_posn
self.posn = (x, 0) # Start ball at top of its column
self.y_velocity = 0 # with zero initial velocity
def update(self):
self.y_velocity += gravity
(x, y) = self.posn
new_y_pos = y + self.y_velocity
(target_x, target_y) = self.target_posn # Unpack the position
dist_to_go = target_y - new_y_pos # How far to our floor?
if dist_to_go < 0: # Are we under floor?
self.y_velocity = -0.65 * self.y_velocity # Bounce
new_y_pos = target_y + dist_to_go # Move back above floor
self.posn = (x, new_y_pos) # Set our new position.
def draw(self, target_surface): # Same as before.
target_surface.blit(self.image, self.posn)
def contains_point(self, pt):
""" Return True if my sprite rectangle contains point pt """
(my_x, my_y) = self.posn
my_width = self.image.get_width()
my_height = self.image.get_height()
(x, y) = pt
return ( x >= my_x and x < my_x + my_width and
y >= my_y and y < my_y + my_height)
def handle_click(self):
self.y_velocity += -2 # Kick it up
class DukeSprite:
def __init__(self, img, target_posn):
self.image = img
self.posn = target_posn
self.anim_frame_count = 0
self.curr_patch_num = 0
def update(self):
if self.anim_frame_count > 0:
self.anim_frame_count = (self.anim_frame_count + 1 ) % 60
self.curr_patch_num = self.anim_frame_count // 6
def draw(self, target_surface):
patch_rect = (self.curr_patch_num * 50, 0,
50, self.image.get_width())
target_surface.blit(self.image, self.posn, patch_rect)
def contains_point(self, pt):
""" Return True if my sprite rectangle contains pt """
(my_x, my_y) = self.posn
my_width = self.image.get_width()
my_height = self.image.get_height()
(x, y) = pt
return ( x >= my_x and x < my_x + my_width and
y >= my_y and y < my_y + my_height)
def handle_click(self):
if self.anim_frame_count == 0:
self.anim_frame_count = 5
def draw_board(the_board):
""" Draw a chess board with queens, as determined by the the_board. """
pygame.init()
colors = [(255,0,0), (0,0,0)] # Set up colors [red, black]
n = len(the_board) # This is an NxN chess board.
surface_sz = 480 # Proposed physical surface size.
sq_sz = surface_sz // n # sq_sz is length of a square.
surface_sz = n * sq_sz # Adjust to exactly fit n squares.
# Create the surface of (width, height), and its window.
surface = pygame.display.set_mode((surface_sz, surface_sz))
ball = pygame.image.load("ball.png")
# Use an extra offset to centre the ball in its square.
# If the square is too small, offset becomes negative,
# but it will still be centered :-)
ball_offset = (sq_sz-ball.get_width()) // 2
all_sprites = [] # Keep a list of all sprites in the game
# Create a sprite object for each queen, and populate our list.
for (col, row) in enumerate(the_board):
a_queen = QueenSprite(ball,
(col*sq_sz+ball_offset, row*sq_sz+ball_offset))
all_sprites.append(a_queen)
# Load the sprite sheet
duke_sprite_sheet = pygame.image.load("duke_spritesheet.png")
# Instantiate two duke instances, put them on the chessboard
duke1 = DukeSprite(duke_sprite_sheet,(sq_sz*2, 0))
duke2 = DukeSprite(duke_sprite_sheet,(sq_sz*5, sq_sz))
# Add them to the list of sprites which our game loop manages
all_sprites.append(duke1)
all_sprites.append(duke2)
while True:
# Look for an event from keyboard, mouse, etc.
ev = pygame.event.poll()
if ev.type == pygame.QUIT:
break;
if ev.type == pygame.KEYDOWN:
key = ev.dict["key"]
if key == 27: # On Escape key ...
break # leave the game loop.
if key == ord("r"):
colors[0] = (255, 0, 0) # Change to red + black.
elif key == ord("g"):
colors[0] = (0, 255, 0) # Change to green + black.
elif key == ord("b"):
colors[0] = (0, 0, 255) # Change to blue + black.
if ev.type == pygame.MOUSEBUTTONDOWN: # Mouse gone down?
posn_of_click = ev.dict["pos"] # Get the coordinates.
for sprite in all_sprites:
if sprite.contains_point(posn_of_click):
sprite.handle_click()
break
for sprite in all_sprites:
sprite.update()
# Draw a fresh background (a blank chess board)
for row in range(n): # Draw each row of the board.
c_indx = row % 2 # Alternate starting color
for col in range(n): # Run through cols drawing squares
the_square = (col*sq_sz, row*sq_sz, sq_sz, sq_sz)
surface.fill(colors[c_indx], the_square)
# Now flip the color index for the next square
c_indx = (c_indx + 1) % 2
# Ask every sprite to draw itself.
for sprite in all_sprites:
sprite.draw(surface)
my_clock.tick(60) # Waste time so that frame rate becomes 60 fps
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
draw_board([0, 5, 3, 1, 6, 4, 2]) # 7 x 7 to test window size
PS: I think the error is here but it did not succeed
return ( x >= my_x and x + my_width and y >= my_y and y < my_y + my_height)
The issue is caused by the face, that "duke_spritesheet.png" is a sprite sheet. When you define the rectangular region which is covered by the object, then you have to use the width of a single image, rather than the width of the entire sprite sheet:
my_width = self.image.get_width()
my_width = 50
Change this in the method contains_point of the class DukeSprite:
class DukeSprite:
# [...]
def contains_point(self, pt):
""" Return True if my sprite rectangle contains pt """
(my_x, my_y) = self.posn
my_width = 50
my_height = self.image.get_height()
(x, y) = pt
return ( x >= my_x and x < my_x + my_width and
y >= my_y and y < my_y + my_height)
I am trying to make a program that will increase the speed of the moving blue circle by 50% if the yellow square is clicked, and decrease it by 50% if the purple square is clicked. The purple square works fine but the yellow square will not register clicks at all. Interestingly, if the black square is used in place of the yellow square, it will function as necessary. Additionally, the blue circle will register clicks within it but will not close the window as intended. I am a novice in python and am beyond confused here. Any suggestions would be greatly appreciated.
As stated previously, functionality works as intended when the black square is used to carry out the functionality of the yellow square. I have used print statements to test if clicks are being registered in different shapes. Clicks register in the blue circle, black square and purple square, but not in the yellow square.
from graphics import*
from time import sleep
def main():
win,c=make()
yellow=Rectangle(Point(495,200),Point(395,300))
yellow.setFill("yellow")
yellow.draw(win)
purple=Rectangle(Point(5,200),Point(105,300))
purple.setFill("purple")
purple.draw(win)
black=Rectangle(Point(200,5),Point(300,105))
black.setFill("black")
black.draw(win)
z=0.2
dx,dy=3,-6
while True:
c.move(dx,dy)
sleep(z)
center=c.getCenter()
dx,dy=bounce(dx,dy,center)
point=win.checkMouse()
if point != None:
if isClicked(point,yellow):
z=z-0.05
elif isClicked(point,purple):
z=z+0.05
elif isClicked(point,c):
win.close
def bounce(dx,dy,center):
if center.getX()<25 or center.getX()>475:
dx *= -1
if center.getY()<25 or center.getY()>475:
dy *= -1
if (center.getX() > 370 and center.getX() < 470) and (center.getY() >175 and center.getY() < 325):
dx *= -1
dy *= -1
return dx,dy
def isClicked(click, shape):
# verify that click is a Point object otherwise return False
if not click:
return False
# get the X,Y coordinates of the mouse click
x,y = click.getX(), click.getY()
# check if shape is a Circle
if type(shape).__name__ == 'Circle':
center = shape.getCenter()
cx, cy = center.getX(), center.getY()
# if click is within the Circle return True
if ((x-cx)**2 + (y-cy)**2)**.5 <= 25:
return True
# shape must be a Rectangle
else:
x1, y1 = shape.getP1().getX(), shape.getP1().getY()
x2, y2 = shape.getP2().getX(), shape.getP2().getY()
# if click is within the Rectangle
if (x1 < x < x2) and (y1 < y < y2):
return True
# click was not inside the shape
return False
def make():
win = GraphWin('Tester',500,500)
win.setBackground('grey')
c = Circle(Point(250,250),25)
c.setFill('blue')
c.draw(win)
return win, c
main()
As stated previously, the intended function would be that a click in the purple rectangle would slow the circle by 50% and a click in the yellow rectangle should increase speed by 50%. Currently a click in the circle does nothing, as does a click in the yellow square. Additionally, a click in the blue circle should close the circle. Thank you for any advice!
You forgot () in win.close() and it never run this function. You could also add break to exit loop because win.close() only close window but it doesn't end loop which still try to win.checkMouse() but win doesn't exists any more and it gives error.
Problem with yellow rectangle is because first X is bigger then second X (495 > 395) in
yellow = Rectangle(Point(495, 200), Point(395, 300))
and Rectangle doesn't sort these values so finally this is not true
if (x1 < x < x2) and (y1 < y < y2)
If you change it to
yellow = Rectangle(Point(395,200),Point(495,300))
then it works correctly.
BTW:
Instead of type(shape).__name__ == 'Circle' you can use isinstance(shape, Circle)
You could add some spaces and empty lines to make it more readable.
Using import * is not prefered - but this part I didn't change.
PEP 8 -- Style Guide for Python Code
Instead of if point != None: you should rather use if point is not None: or shorter if point:
from graphics import *
from time import sleep
def main():
win, c = make()
yellow = Rectangle(Point(395, 200), Point(495, 300))
yellow.setFill("yellow")
yellow.draw(win)
purple = Rectangle(Point(5, 200), Point(105, 300))
purple.setFill("purple")
purple.draw(win)
black = Rectangle(Point(200, 5), Point(300, 105))
black.setFill("black")
black.draw(win)
z = 0.2
dx, dy = 3, -6
while True:
c.move(dx, dy)
sleep(z)
center = c.getCenter()
dx, dy = bounce(dx, dy, center)
point = win.checkMouse()
#if point is not None:
if point:
if isClicked(point, yellow):
z = z - 0.05
elif isClicked(point, purple):
z = z + 0.05
elif isClicked(point, c):
win.close()
break
def bounce(dx, dy, center):
if center.getX() < 25 or center.getX() > 475:
dx *= -1
if center.getY() < 25 or center.getY() > 475:
dy *= -1
if (center.getX() > 370 and center.getX() < 470) and (center.getY() > 175 and center.getY() < 325):
dx *= -1
dy *= -1
return dx, dy
def isClicked(click, shape):
# verify that click is a Point object otherwise return False
if not click:
return False
# get the X,Y coordinates of the mouse click
x, y = click.getX(), click.getY()
# check if shape is a Circle
if isinstance(shape, Circle):
center = shape.getCenter()
cx, cy = center.getX(), center.getY()
# if click is within the Circle return True
if ((x-cx)**2 + (y-cy)**2)**.5 <= 25:
return True
# shape must be a Rectangle
else:
x1, y1 = shape.getP1().getX(), shape.getP1().getY()
x2, y2 = shape.getP2().getX(), shape.getP2().getY()
# if click is within the Rectangle
if (x1 < x < x2) and (y1 < y < y2):
return True
# click was not inside the shape
return False
def make():
win = GraphWin('Tester', 500, 500)
win.setBackground('grey')
c = Circle(Point(250, 250), 25)
c.setFill('blue')
c.draw(win)
return win, c
main()
Using a PS4 controller in pygame, I already figured out how to capture axis rotation which can vary to a -1 or 1, but I don't know how to convert those numbers to a color ring like scale in order to turn it into a hex triplet number.
The values mimicking that of a color ring is more important than anything as I don't want the joystick capturing a color while it's not in motion. Picture
( Since that was a bit confusing, essentially I want to be able to move my joystick around and capture an accurate hex triplet number based on where it has moved )
This is my code so far:
import pygame
# Define some colors
BLACK = ( 0, 0, 0)
WHITE = ( 62, 210, 255)
# This is a simple class that will help us print to the screen
# It has nothing to do with the joysticks, just outputting the
# information.
class TextPrint:
def __init__(self):
self.reset()
self.font = pygame.font.Font(None, 25)
def print(self, screen, textString):
textBitmap = self.font.render(textString, True, BLACK)
screen.blit(textBitmap, [self.x, self.y])
self.y += self.line_height
def reset(self):
self.x = 25
self.y = 25
self.line_height = 30
def indent(self):
self.x += 10
def unindent(self):
self.x -= 10
pygame.init()
# Set the width and height of the screen [width,height]
size = [900, 1080]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("PS4Testing")
#Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Initialize the joysticks
pygame.joystick.init()
# Get ready to print
textPrint = TextPrint()
# -------- Main Program Loop -----------
while done==False:
# EVENT PROCESSING STEP
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop
screen.fill(WHITE)
textPrint.reset()
# Get count of joysticks
joystick_count = pygame.joystick.get_count()
# For each joystick:
for i in range(joystick_count):
joystick = pygame.joystick.Joystick(i)
joystick.init()
# Usually axis run in pairs, up/down for one, and left/right for
# the other.
axes = joystick.get_numaxes()
for i in range( axes ):
axis = joystick.get_axis( i )
textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
textPrint.unindent()
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 20 frames per second
clock.tick(20)
# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit ()
Modified code from official pygame documentation
Any help would be greatly appreciated
My plan:
Find the angle of stick on joystick
Get RGB value using HSV and stick's angle
Convert to HEX
Finding the angle of joystick's stick
First, we need to find the joystick's angle. We can do this using the law of cosines and axis statements as lengths of the sides of a triangle (because they are from one point/center).
Store axis statements in this block:
for i in range( axes ):
axis = joystick.get_axis( i )
#Storing axis statement
if i == 0:
Xaxis = axis
elif i == 1:
Yaxis = axis
textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
We store them because in for loop we can take only one statement at a time.
Then define a function that will return the angle of the stick. We need to use math module for pi and acos.
def get_angle(Xaxis,Yaxis):
#To avoid ZeroDivisionError
#P.S. - you can improve it a bit.
if Xaxis == 0:
Xaxis = 0.001
if Yaxis == 0:
Yaxis = 0.001
#defining the third side of a triangle using the Pythagorean theorem
b = ((Xaxis)**2 + (Yaxis)**2)**0.5
c = Xaxis
a = Yaxis
#Using law of cosines we'll find angle using arccos of cos
#math.acos returns angles in radians, so we need to multiply it by 180/math.pi
angle = math.acos((b**2 + c**2 - a**2) / (2*b*c)) * 180/math.pi
#It'll fix angles to be in range of 0 to 360
if Yaxis > 0:
angle = 360 - angle
return angle
Now get_angle(Xaxis,Yaxis) will return stick's angle. East is 0°, north is 90°, west is 180°, south is 270°.
Getting HSV and converting to RGB
We have stick's angle and we can use it to make an HSV color and then convert it to RGB, because Color Wheel is based on hue of the colors.
Hue has the same range as the stick's angle, so we can use stick's angle as hue.
Using the formulas from Wikipedia, define a function that converts HSV to RGB.
def hsv_to_rgb(H,S,V):
#0 <= H <= 360
#0 <= S <= 1
#0 <= V <= 1
C = V * S
h = H/60
X = C * (1 - abs(h % 2 -1))
#Yes, Python can compare like "0 <= 2 > 1"
if 0 <= h <= 1:
r = C; g = X; b = 0
elif 1 <= h <= 2:
r = X; g = C; b = 0
elif 2 <= h <= 3:
r = 0; g = C; b = X
elif 3 <= h <= 4:
r = 0; g = X; b = C
elif 4 <= h <= 5:
r = X; g = 0; b = C
elif 5 <= h < 6:
r = C; g = 0; b = X
m = V - C
#Final computing and converting from 0 - 1 to 0 - 255
R = int((r+m)*255)
G = int((g+m)*255)
B = int((b+m)*255)
return [R,G,B]
Now hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1) will return list of red, green and blue in range of 0 - 255 (for an easier conversion in hex). Saturation and Value are not necessary in this case.
Conversion to HEX
The easiest part. We need to get the list of RGB and convert values to hex.
colors = hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
#Converting to hex
lst = list(map(hex,colors))
#Cutting the "0x" part
for i in range(len(lst)):
lst[i] = lst[i][2:]
#If one of the colors has only one digit, extra 0 will be added for a better look
if len(lst[i]) == 1:
lst[i] = "0"+str(lst[i])
print(get_angle(Xaxis,Yaxis))
Something like #ff0000, #00ff00, and #0000ff will be printed.
You also can make your program to show color change in real time, simply add WHITE = colors in this block. DO NOT use it if you have photosensitive epilepsy.
Merging everything
Place both functions from first and second points somewhere at the beginning.
Add the storing from the first point to the block
for i in range( axes ):
axis = joystick.get_axis( i )
textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
Add the conversion from the third point after the block. I recommend to make a death zone feature.
death_zone = 0.1
if abs(Xaxis) > death_zone or abs(Yaxis) > death_zone:
#If you prefer HSV color wheel, use hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
#Else if you prefer RGB color wheel, use hsv_to_rgb(360-get_angle(Xaxis,Yaxis),1,1)
colors = hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
#Converting to hex
lst = list(map(hex,colors))
#Cutting the "0x" part
for i in range(len(lst)):
lst[i] = lst[i][2:]
#If one of the colors has only one digit, extra 0 will be added for a better look
if len(lst[i]) == 1:
lst[i] = "0"+str(lst[i])
print("#"+"".join(lst))
That's how your code may look after all:
P.S. - you will probably have to change some code, because I thing my joystick's axis weren't properly captured.
import pygame
import math
# Define some colors
BLACK = ( 0, 0, 0)
WHITE = ( 62, 210, 255)
def hsv_to_rgb(H,S,V):
#Accirding to https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
#0 <= H <= 360
#0 <= S <= 1
#0 <= V <= 1
C = V * S
h = H/60
X = C * (1 - abs(h % 2 -1))
#Yes, Python can compare like "0 <= 2 > 1"
if 0 <= h <= 1:
r = C; g = X; b = 0
elif 1 <= h <= 2:
r = X; g = C; b = 0
elif 2 <= h <= 3:
r = 0; g = C; b = X
elif 3 <= h <= 4:
r = 0; g = X; b = C
elif 4 <= h <= 5:
r = X; g = 0; b = C
elif 5 <= h < 6:
r = C; g = 0; b = X
m = V - C
#Final computing and converting from 0 - 1 to 0 - 255
R = int((r+m)*255)
G = int((g+m)*255)
B = int((b+m)*255)
return [R,G,B]
def get_angle(Xaxis,Yaxis):
#To avoid ZeroDivisionError
#P.S. - you can improve it a bit.
if Xaxis == 0:
Xaxis = 0.001
if Yaxis == 0:
Yaxis = 0.001
#defining the third side of a triangle using the Pythagorean theorem
b = ((Xaxis)**2 + (Yaxis)**2)**0.5
c = Xaxis
a = Yaxis
#Using law of cosines we'll fing angle using arccos of cos
#math.acos returns angles in radians, so we need to multiply it by 180/math.pi
angle = math.acos((b**2 + c**2 - a**2) / (2*b*c)) * 180/math.pi
#It'll fix angles to be in range of 0 to 360
if Yaxis > 0:
angle = 360 - angle
return angle
# This is a simple class that will help us print to the screen
# It has nothing to do with the joysticks, just outputting the
# information.
class TextPrint:
def __init__(self):
self.reset()
self.font = pygame.font.Font(None, 25)
def print(self, screen, textString):
textBitmap = self.font.render(textString, True, BLACK)
screen.blit(textBitmap, [self.x, self.y])
self.y += self.line_height
def reset(self):
self.x = 25
self.y = 25
self.line_height = 30
def indent(self):
self.x += 10
def unindent(self):
self.x -= 10
pygame.init()
# Set the width and height of the screen [width,height]
size = [900, 1080]
screen = pygame.display.set_mode(size)
pygame.display.set_caption("PS4Testing")
#Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# Initialize the joysticks
pygame.joystick.init()
# Get ready to print
textPrint = TextPrint()
# -------- Main Program Loop -----------
while done==False:
# EVENT PROCESSING STEP
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop
screen.fill(WHITE)
textPrint.reset()
# Get count of joysticks
joystick_count = pygame.joystick.get_count()
# For each joystick:
for i in range(joystick_count):
joystick = pygame.joystick.Joystick(i)
joystick.init()
# Usually axis run in pairs, up/down for one, and left/right for
# the other.
axes = joystick.get_numaxes()
for i in range( axes ):
axis = joystick.get_axis( i )
#Storing axis statement
if i == 0:
Xaxis = axis
elif i == 1:
Yaxis = axis
textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
textPrint.unindent()
#If joystick is not in the center
#Death zone is used to not capture joystick if it's very close to the center
death_zone = 0.1
if abs(Xaxis) > death_zone or abs(Yaxis) > death_zone:
#If you prefer HSV color wheel, use hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
#Else if you prefer RGB color wheel, use hsv_to_rgb(360-get_angle(Xaxis,Yaxis),1,1)
colors = hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
#Converting to hex
lst = list(map(hex,colors))
#Cutting the "0x" part
for i in range(len(lst)):
lst[i] = lst[i][2:]
#If one of the colors has only one digit, extra 0 will be added for a better look
if len(lst[i]) == 1:
lst[i] = "0"+str(lst[i])
print("#"+"".join(lst))
#You can use it to see color change in real time.
#But I don't recomend to use it if you have photosensitive epilepsy.
#WHITE = colors
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 20 frames per second
clock.tick(20)
# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit ()