this function is supposed to find if there is above a set number of points within the window, and if there is draw a rectangle around it and then subdivide by recursively calling the same function on all four quadrants of the previous rectangle. The behaviour seems almost right for the first 2 recursions and then seems to go a bit awry.. I also set a max depth so that it dosn't recurse too long. Im using pygame to draw the rectangles on the screen. Im assuming I have got muddled in my logic somewhere.
def recursion(x,x2,y,y2,max):
#draws a square
pygame.draw.rect(screen,(0,255,255),(x,y,x2,y2),1)
currentMax = 0
total_points = 0
if max >= 30:
return None
currentMax = max + 1
for i in range(len(xlist)):
if xlist[i] in range(x,x2) and ylist[i] in range(y,y2):
total_points += 1
if total_points > 3:
recursion(int(x),int(x2/2),int(y),int(y2/2),currentMax)#top_left
recursion(int(x2/2),int(x2),int(y),int(y2/2),currentMax)#top_right
recursion(int(x),int(x2/2),int(y2/2),int(y2),currentMax)#bottom_left
recursion(int(x2/2),int(x2),int(y2/2),int(y2),currentMax)#bottom_right
I also call it once to start the recursion with:
recursion(int(0),int(1000),int(0),int(1000),0)
The points are generated using:
for i in range(5):
xlist.append(random.randint(0,1000))
ylist.append(random.randint(0,1000))
When drawing a rectangle with pygame.draw.rect you have to specify the top, left point and the width and height instead of the bottom right point.
Furthermore, the computation of the center is wrong. A maximum depth of 30 (2^30) is far too much and you can use the // (floor division) operator.
Start with max >= 5 and total_points >= 1:
def recursion(x,x2,y,y2,depth):
if depth >= 5:
return
depth += 1
total_points = 0
for i in range(len(xlist)):
if x < xlist[i] <= x2 and y < ylist[i] <= y2:
total_points += 1
if total_points >= 1:
pygame.draw.rect(screen, (0,255,255), (x, y, x2-x, y2-y), 1)
centerx = (x+x2) // 2
centery = (y+y2) // 2
recursion(x, centerx, y, centery, depth)
recursion(centerx, x2, y, centery, depth)
recursion(x, centerx, centery, y2, depth)
recursion(centerx, x2, centery, y2, depth)
Minimal example:
import pygame, random
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
def recursion(x,x2,y,y2,depth):
if depth >= 5:
return
depth += 1
total_points = 0
for i in range(len(xlist)):
if x < xlist[i] <= x2 and y < ylist[i] <= y2:
total_points += 1
if total_points >= 1:
pygame.draw.rect(screen, (0,255,255), (x, y, x2-x, y2-y), 1)
centerx = (x+x2) // 2
centery = (y+y2) // 2
recursion(x, centerx, y, centery, depth)
recursion(centerx, x2, y, centery, depth)
recursion(x, centerx, centery, y2, depth)
recursion(centerx, x2, centery, y2, depth)
xlist = []
ylist = []
for i in range(5):
xlist.append(random.randrange(screen.get_width()))
ylist.append(random.randrange(screen.get_height()))
screen.fill(0)
recursion(0, screen.get_width(), 0, screen.get_height(), 0)
for p in zip(xlist, ylist):
pygame.draw.circle(screen, (255, 0, 0), p, 8)
pygame.display.flip()
#pygame.image.save(screen, "grid.png")
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.display.flip()
clock.tick(60)
pygame.quit()
exit()
Related
I'm doing a pygame project for practice and I need a sprite to move to some point on screen and I did it, but it moves in a straight line and I would like to learn how to make it move to the same point in a curve.
def move_to_point(self, dest_rect, speed, delta_time):
# Calculates relative rect of dest
rel_x = self.rect.x - dest_rect[0]
rel_y = self.rect.y - dest_rect[1]
# Calculates diagonal distance and angle from entity rect to destination rect
dist = math.sqrt(rel_x**2 + rel_y**2)
angle = math.atan2( - rel_y, - rel_x)
# Divides distance to value that later gives apropriate delta x and y for the given speed
# there needs to be at least +2 at the end for it to work with all speeds
delta_dist = dist / (speed * delta_time) + 5
print(speed * delta_time)
# If delta_dist is greater than dist entety movement is jittery
if delta_dist > dist:
delta_dist = dist
# Calculates delta x and y
delta_x = math.cos(angle) * (delta_dist)
delta_y = math.sin(angle) * (delta_dist)
if dist > 0:
self.rect.x += delta_x
self.rect.y += delta_y
This movement looks like
and I would like it to be like
There are many many ways to achieve what you want. One possibility is a Bézier curve:
def bezier(p0, p1, p2, t):
px = p0[0]*(1-t)**2 + 2*(1-t)*t*p1[0] + p2[0]*t**2
py = p0[1]*(1-t)**2 + 2*(1-t)*t*p1[1] + p2[1]*t**2
return px, py
p0, p1 and p2 are the control points and t is a value in the range [0,0, 1,0] indicating the position along the curve. p0 is the start of the curve and p2 is the end of the curve. If t = 0, the point returned by the bezier function is equal to p0. If t=1, the point returned is equal to p2.
Also see PyGameExamplesAndAnswers - Shape and contour - Bezier
Minimal example:
import pygame
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
def bezier(p0, p1, p2, t):
px = p0[0]*(1-t)**2 + 2*(1-t)*t*p1[0] + p2[0]*t**2
py = p0[1]*(1-t)**2 + 2*(1-t)*t*p1[1] + p2[1]*t**2
return px, py
dx = 0
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pts = [(100, 100), (100, 400), (400, 400)]
window.fill(0)
for p in pts:
pygame.draw.circle(window, (255, 255, 255), p, 5)
for i in range(101):
x, y = bezier(*pts, i / 100)
pygame.draw.rect(window, (255, 255, 0), (x, y, 1, 1))
p = bezier(*pts, dx / 100)
dx = (dx + 1) % 101
pygame.draw.circle(window, (255, 0, 0), p, 5)
pygame.display.update()
pygame.quit()
exit()
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.
In my pygame-code, I have a drone that is supposed to follow a flight path.
I used pygame.draw.lines to draw lines between specified points. Now, I have a flight path with 10 points where after each point the path angle changes (a bit like a zigzag). The player can move the drone by pressing the keys.
My goal is to print a warning once the drone deviates from the path, e.g. by +/-30. I have been racking my brain for two days but can't come up with a condition to detect a deviation. I just can't figure out how to approach this.
I can determine the drone's x-coordinate at any time but how do I determine the offset from the path? I have attached an image to visualize my problem.
Edit:
As I am a beginner my code is a mess but when copy-pasting it, I guess only the lines 35-91 are interesting. Thank you for any kind of advice in advance!!
import pygame
import pygame.gfxdraw
import random
import sys
import math
pygame.init()
# Define some colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
red_transp = (255,0,0, 150)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
X = 0
Y = 250
#Display
display_width, display_height = 1200, 700
h_width, h_height = display_width/2, display_height/2
gameDisplay = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Game Display')
#Drone Sprite Image Load Function
droneImg_interim = pygame.image.load('drone.png')
droneImg = pygame.transform.scale(droneImg_interim, [50,50])
drone_width, drone_height = droneImg.get_rect().size
#Create 11 Waypoints with the same coordinates
p1=[X, Y]
p2=[X, Y]
p3=[X, Y]
p4=[X, Y]
p5=[X, Y]
p6=[X, Y]
p7=[X, Y]
p8=[X, Y]
p9=[X, Y]
p10=[X, Y]
p11=[X, Y]
pointlist = [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11]
x_min=drone_width
x_max=100
#Setting new x-coordinate for each point
for i in pointlist:
i[0] = random.randrange(x_min, x_max)
x_min+=250
x_max+=250
#Setting new y-coordinate for each point
for i in range(len(pointlist)):
if i == 0:
pointlist[i][1] = random.randrange(200, 400)
else:
prev = pointlist[i-1][1]
pointlist[i][1] = random.randrange(200, prev+100)
#Plotting pointlist on gameDisplay and connecting dots
def flightpath(pointlist):
pygame.draw.lines(gameDisplay, (255, 0, 0), False, pointlist, 2)
def margin(x):
for i in range(len(pointlist)-1):
p1_x = pointlist[i][0]
p2_x = pointlist[i+1][0]
p1_y = pointlist[i][1]
p2_y = pointlist[i+1][1]
distance_x = p2_x - p1_x
distance = math.sqrt((p2_x-p1_x)**2+(p2_y-p1_y)**2)
halfwaypoint_x = math.sqrt((p2_x - p1_x)**2)/2 + p1_x
halfwaypoint_y = math.sqrt((p2_y - p1_y)**2)/2 + p1_y
if p2_y < p1_y:
angle_rad = math.acos(distance_x/distance)
elif p2_y > p1_y:
angle_rad = 0 - math.acos(distance_x/distance)
angle_deg = math.degrees(angle_rad)
rect_width = distance
rect_height = 60
"""
This part of the code is meant for displaying the margins (the rectangles) around the flight path on the display.
marginSize = (rect_width, rect_height)
surface = pygame.Surface(marginSize, pygame.SRCALPHA)
surface.fill((255,0,0,25))
rotated_surface = pygame.transform.rotate(surface, angle_deg)
#new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((pointlist[i][0], pointlist[i][1]))).center)
new_rect = rotated_surface.get_rect(center = surface.get_rect(center = ((halfwaypoint_x, halfwaypoint_y))).center)
#gameDisplay.blit(rotated_surface, new_rect)
"""
#Placing drone on the screen
def drone(x,y):
rect = droneImg.get_rect ()
rect.center=(x, y)
gameDisplay.blit(droneImg,rect)
def displayMSG(value,ttext,posx,posy):
myFont = pygame.font.SysFont("Verdana", 12)
Label = myFont.render(ttext, 1, black)
Value = myFont.render(str(value), 1, black)
gameDisplay.blit(Label, (posx, posy))
gameDisplay.blit(Value, (posx + 100, posy))
#Main Loop Object
def game_loop():
global X, Y, FThrustX, FThrustY, FDragY, Time
FThrustY = 0
gameExit = False
while not gameExit:
#Event Checker (Keyboard, Mouse, etc.)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed() #checking pressed keys
if keys[pygame.K_LEFT]:
X -= 1
if keys[pygame.K_RIGHT]:
X +=1
if keys[pygame.K_DOWN]:
Y += 1
if keys[pygame.K_UP]:
Y -=1
#Display Background Fill
gameDisplay.fill(white)
#Plot flightpath
flightpath(pointlist)
#YS: Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
displayMSG(current_pos_x,'x:',20,665)
displayMSG(current_pos_y,'y:',20,680)
#Plot margin
margin(5)
#Move Drone Object
drone(X,Y)
#Determine the position of the mouse
current_pos_x, current_pos_y = pygame.mouse.get_pos()
#No exceeding of display edge
if X > display_width - drone_width: X = display_width - drone_width
if Y > display_height - drone_height: Y = display_height - drone_height
if X < drone_width: X = drone_width
if Y < drone_height: Y = drone_height
pygame.display.update()
#MAIN
game_loop()
pygame.quit()
sys.exit()
One approach is to find the minimum distance between the center of the drone and the line.
Write the function that calculates the minimum distance from a point to a line segment. To do this, use pygame.math.Vector2 and the Dot product:
def distance_point_linesegment(pt, l1, l2):
LV = pygame.math.Vector2(l2[0] - l1[0], l2[1] - l1[1])
PV = pygame.math.Vector2(pt[0] - l1[0], pt[1]- l1[1])
dotLP = LV.dot(PV)
if dotLP < 0:
return PV.length()
if dotLP > LV.length_squared():
return pygame.math.Vector2(pt[0] - l2[0], pt[1]- l2[1]).length()
NV = pygame.math.Vector2(l1[1] - l2[1], l2[0] - l1[0])
return abs(NV.normalize().dot(PV))
Find the line segment with the shortest distance in a loop:
def minimum_distance(pt, pointlist):
min_dist = -1
for i in range(len(pointlist)-1):
dist = distance_point_linesegment(pt, pointlist[i], pointlist[i+1])
if i == 0 or dist < min_dist:
min_dist = dist
return min_dist
Create an alert when the distance exceeds a certain threshold:
def game_loop():
# [...]
while not gameExit:
# [...]
dist_to_path = minimum_distance((X, Y), pointlist)
if dist_to_path > 25:
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
Another possible solution is to use pygame.Rect.clipline and detect the collision of the line segments and the rectangle surrounding the drone:
def intersect_rect(rect, pointlist):
for i in range(len(pointlist)-1):
if rect.clipline(pointlist[i], pointlist[i+1]):
return True
return False
def game_loop():
# [...]
while not gameExit:
# [...]
if not intersect_rect(droneImg.get_rect(center = (X, Y)), pointlist):
pygame.draw.circle(gameDisplay, (255, 0, 0), (X, Y), 25, 4)
drone(X,Y
# [...]
The interesting part of the question is of course finding the nearest point on the desired path to the actual position; distance is easy. The hard part of that is in turn identifying the nearest element (line segment) of the path; projecting onto it is also straightforward.
If the path is simple enough (in particular, if it doesn’t branch and it’s impossible/disallowed to skip sections at a self-intersection), you can finesse that part by just maintaining that current element in a variable and updating it to the previous or next element when the projection onto one of them is closer than the projection onto the current one. This is a typical algorithm used by racing games to determine the instantaneous order of racers.
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)