I can't draw all functions with my script - python

I made a script, which should take the user input of a mathematical function (f(x)=...) and draw it. I used pygame for that because I want to use that mechanic for a game.
I have to run the code of a function once without any output once, but after that, it flawlessly works
heres the code:
import pygame
def replace_x(function):
f = lambda x: eval(function)
return f
def convert_y(y_coords):
y_coords = 540 - y_coords
return y_coords
def convert_x(x_coord):
x_coord = x_coord + 960
return x_coord
# variables
background_colour = (255, 255, 255)
screen = pygame.display.set_mode((1920, 1080))
running = True
current_y = 0
previous_y = 0
pygame.init()
pygame.display.set_caption('Mathe Kreativarbeit')
screen.fill(background_colour)
pygame.display.flip()
function_input = input("Funktion: ")
function_input = function_input.replace("^", "**")
pygame.display.flip()
for x_coords in range(-15, 17):
f = replace_x(function_input)
current_y = convert_y(f(x_coords))
previous_y = convert_y(f(x_coords - 1))
start_pos = (convert_x((x_coords - 1) * 60), previous_y)
end_pos = (convert_x(x_coords * 60), current_y)
print(start_pos)
print(end_pos)
pygame.draw.aaline(screen, (0, 0, 0), start_pos, end_pos)
pygame.display.flip()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False

Create a list of points from the function:
f = replace_x(function_input)
pt_list = []
for x in range(-20, 20):
pt_list.append((x, f(x)))
Either print the list of points:
print(pt_list)
or print the list in a loop:
for pt in pt_list:
print(pt)
Create a list of screen coordinates from the list of point:
coord_list = []
for pt in pt_list:
x = round(convert_x(pt[0] * 20))
y = round(convert_y(pt[1]))
coord_list.append((x, y))
Draw the curve in the application loop with pygame.draw.aalines():
clock = pygame.time.Clock()
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.draw.aalines(screen, (0, 0, 0), False, coord_list)
pygame.display.flip()
pygame.quit()
Example for input x**3:

Related

Duplicates of squares going in the wrong direction pygame

The program is supposed to make 3 squares and move them to the right. That works well. However, there are 2 squares that move down even though there are no functions to do that. there are only 3 square creating functions but there are 2 more that appear
import pygame
import random
import time
enemyCoordList = []
pygame.init()
rand = random.Random()
screen = pygame.display.set_mode((1000, 600))
px = 30
py = 30
def enemy(x, y, sizex=30, sizey=30, health=0, speed=6,):
# pygame.draw.rect(screen, (0, 255, 0), pygame.Rect(x, y, sizex, sizey))
enemyCoordList.append(x)
enemyCoordList.append(y)
def drawEnemy():
for l in range(len(enemyCoordList)-1):
# print(range(len(enemyCoordList)))
pygame.draw.rect(screen, (0, 255, 0), pygame.Rect(enemyCoordList[l], enemyCoordList[l+1], 50, 50))
enemy(30, 30)
enemy(50, 50)
enemy(100,100)
print(enemyCoordList)
run = True
while run:
time.sleep(0.05)
screen.fill((9, 68, 43))
drawEnemy()
for i in range(len(enemyCoordList)):
#print(i)
if i % 2 != 0:
#print(i)
continue
# print(enemyCoordList)
#print(i)
enemyCoordList[i] += 1
# for i in enemyCoordList:
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
A coordinate has 2 components so:
for l in range(len(enemyCoordList)-1):
for l in range(0, len(enemyCoordList), 2):
Correct drawEnemy function:
def drawEnemy():
for l in range(0, len(enemyCoordList), 2):
pygame.draw.rect(screen, (0, 255, 0), pygame.Rect(enemyCoordList[l], enemyCoordList[l+1], 50, 50))
However, it would be much easier to work with a list of coordinate pairs (list of lists):
import pygame
import random
import time
pygame.init()
rand = random.Random()
screen = pygame.display.set_mode((1000, 600))
def enemy(x, y, sizex=30, sizey=30, health=0, speed=6):
enemyCoordList.append([x, y])
def drawEnemy():
for x, y in enemyCoordList:
pygame.draw.rect(screen, (0, 255, 0), pygame.Rect(x, y, 50, 50))
enemyCoordList = []
enemy(30, 30)
enemy(50, 50)
enemy(100,100)
print(enemyCoordList)
run = True
while run:
time.sleep(0.05)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for pos in enemyCoordList:
pos[0] += 1
screen.fill((9, 68, 43))
drawEnemy()
pygame.display.update()
pygame.quit()

How to Move Sprite to a Specific Location Without Keys

Im trying to make a game with pygame where I can click a sprite then click somewhere on the screen for the sprite to move towards. So far, I'm able to click the sprite and get a response but I'm not sure how to tell the sprite to go to a given location where I click. I've seen something online with sprite.goal but I can't seem to make it work.
This is what I have
if event.type==pygame.MOUSEBUTTONDOWN:
pos=pygame.mouse.get_pos()
#White is a rectangle
if White.collidepoint(pos):
Moving=True
elif Moving==True:
#This is where I would tell it to move to pos
I'll show you a very simple example. Write a function that moves a point 1 step to a target point and returns the new position:
def move_to_target(pos, target):
x, y = pos
if x < target[0]:
x += 1
elif x > target[0]:
x -= 1
if y < target[1]:
y += 1
elif y > target[1]:
y -= 1
return (x, y)
Set a new target when the mouse button is pressed and call the function at each frame:
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
def move_to_target(pos, target):
x, y = pos
if x < target[0]:
x += 1
elif x > target[0]:
x -= 1
if y < target[1]:
y += 1
elif y > target[1]:
y -= 1
return (x, y)
my_sprite = pygame.Surface((20, 20), pygame.SRCALPHA)
pygame.draw.circle(my_sprite, (255, 255, 0), (10, 10), 10)
pos = (200, 200)
target = (200, 200)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
target = event.pos
pos = move_to_target(pos, target)
window.fill(0)
window.blit(my_sprite, pos)
pygame.display.flip()
clock.tick(100)
pygame.quit()
exit()
For variable speed and a straighter and smoother movement, you need to tweak the function. See How to make smooth movement in pygame.
import pygame
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
LERP_FACTOR = 0.05
minimum_distance = 0
maximum_distance = 100
def move_to_target(pos, target):
target_vector = pygame.math.Vector2(*target)
follower_vector = pygame.math.Vector2(*pos)
new_follower_vector = pygame.math.Vector2(*pos)
distance = follower_vector.distance_to(target_vector)
if distance > minimum_distance:
direction_vector = (target_vector - follower_vector) / distance
min_step = max(0, distance - maximum_distance)
max_step = distance - minimum_distance
step_distance = min_step + (max_step - min_step) * LERP_FACTOR
new_follower_vector = follower_vector + direction_vector * step_distance
return (new_follower_vector.x, new_follower_vector.y)
my_sprite = pygame.Surface((20, 20), pygame.SRCALPHA)
pygame.draw.circle(my_sprite, (255, 255, 0), (10, 10), 10)
pos = (200, 200)
target = (200, 200)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN:
target = event.pos
pos = move_to_target(pos, target)
window.fill(0)
window.blit(my_sprite, pos)
pygame.display.flip()
clock.tick(100)
pygame.quit()
exit()

Other ways to create line animation

I'm trying to create a animation that runs around a shape. The method I came up with is similar to the snake game, drawing the desired part and refresh it with white screen.
For example, if I want to draw a linear line animation, with the max animated length of 3:
lst = [1, 2, 3]
dummy_lst = [1, 1, 1, 2, 3, 3, 3]
colored_position = [[1,1,1], [1,1,2], [1,2,3], [2,3,3], [3,3,3]] # if the numbers overlapped, it draws the same place which is fine
and then I iterate the colored_position to get the animation done.
The real code:
import pygame
import sys
SCREEN_WIDTH = 800
CELLSIZE = 10
x, y = 150, 200
position = [[x+CELLSIZE*i, y] for i in range(50)] +\
[[x+CELLSIZE*50, y+CELLSIZE*i] for i in range(50)] +\
[[x+CELLSIZE*50-CELLSIZE*i, y+CELLSIZE*50]for i in range(50)] +\
[[x, y+CELLSIZE*50-CELLSIZE*i]for i in range(50)]
# creates fake firsts and lasts
def dummy_list(list, length):
return [list[0]]*(length-1) + list + [list[-1]]*(length-1)
def main():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_WIDTH))
count = 0
while True:
clock.tick(10)
screen.fill((255, 255, 255))
dummy = dummy_list(position, 50)
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_SPACE:
count = 0
while count < 1: # I could chaget this to see many times it rotates
for i in range(len(dummy)-50+1):
for j in range(9): # change this to adjust how wide the colored part is
pygame.draw.rect(screen, (255, 0, 0), pygame.Rect(
dummy[i+j][0], dummy[i+j][1], CELLSIZE, CELLSIZE), 0)
pygame.display.update()
screen.fill((255, 255, 255))
count += 1
pygame.display.update()
But it seems very hard-coding. since I have to know each part of the desired position's Xs and Ys. What's the smarter way to complete the task?
You want to move the object along a path. I don't see any problem in defining the path through a list of points.
However you can write a function that generates the list of points from a pygame.Rect object:
def generate_positions(rect, step):
return [[x, rect.top] for x in range(rect.left, rect.right, step)] +\
[[rect.right, y] for y in range(rect.top, rect.bottom, step)] +\
[[x, rect.bottom] for x in range(rect.right, rect.left, -step)] +\
[[rect.left, y] for y in range(rect.bottom, rect.top, -step)]
CELLSIZE = 10
x, y = 150, 200
position = generate_positions(pygame.Rect(x, y, 50*CELLSIZE, 50*CELLSIZE), CELLSIZE)
I recommend to use the application loop to draw the snake:
import pygame
import sys
SCREEN_WIDTH = 800
def generate_positions(rect, step):
return [[x, rect.top] for x in range(rect.left, rect.right, step)] +\
[[rect.right, y] for y in range(rect.top, rect.bottom, step)] +\
[[x, rect.bottom] for x in range(rect.right, rect.left, -step)] +\
[[rect.left, y] for y in range(rect.bottom, rect.top, -step)]
CELLSIZE = 10
x, y = 150, 200
position = generate_positions(pygame.Rect(x, y, 10*CELLSIZE, 10*CELLSIZE), CELLSIZE)
def main():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_WIDTH))
current_point = None
snake_len = 9
while True:
clock.tick(10)
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_SPACE:
current_point = -snake_len
screen.fill((255, 255, 255))
if current_point != None:
for i in range(max(0, current_point), min(len(position), current_point+snake_len)):
pygame.draw.rect(screen, (255, 0, 0), pygame.Rect(*position[i], CELLSIZE, CELLSIZE), 0)
current_point += 1
if current_point >= len(position):
current_point = None
pygame.display.update()
main()

How to STOP generating numbers

I have made a code that renders 2 different random numbers on screen, but it justs keep updating the numbers. I want the program to stop updating those numbers once the first 2 appear on screen. Here is the code:
import pygame
import random
pygame.init()
clock = pygame.time.Clock()
surface = pygame.display.set_mode((600, 400))
pygame.display.set_caption("Projecte MatZanfe")
font = pygame.font.SysFont('comicsans', 50)
base_font = pygame.font.Font(None, 32)
user_text = ''
color_active = pygame.Color('lightskyblue3')
def start_the_game():
# Variables
is_correct = False
points = 0
x = random.randint(0,10)
y = random.randint(0,10)
z = x + y
surface.fill((255,70,90))
text = font.render (str(x) + "+" + str(y), True, (255,255,255))
input_rect = pygame.Rect(200,200,180,50)
pygame.draw.rect(surface,color_active,input_rect)
text_surface = base_font.render(user_text,True,(255,255,255))
surface.blit(text_surface, input_rect)
surface.blit(text,(260,120))
input_rect.w = max(100,text_surface.get_width()+10)
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
start_the_game()
pygame.display.update()
pygame.quit()
Maybe using an if statement would be the solution, but I can't figure out where and what should I introduce to the code. How would you do it?
As #jasonharper suggested, you probably are better off with two functions, one to initialize things and another to display the game:
def start_the_game():
x = random.randint(0, 10)
y = random.randint(0, 10)
return x, y
def display_the_game(x, y):
# Variables
is_correct = False
points = 0
z = x + y
surface.fill((255, 70, 90))
text = font.render(str(x) + "+" + str(y), True, (255, 255, 255))
input_rect = pygame.Rect(200, 200, 180, 50)
pygame.draw.rect(surface, color_active, input_rect)
text_surface = base_font.render(user_text, True, (255, 255, 255))
surface.blit(text_surface, input_rect)
surface.blit(text, (260, 120))
input_rect.w = max(100, text_surface.get_width() + 10)
x, y = start_the_game()
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
display_the_game(x, y)
pygame.display.update()
pygame.quit()
By putting random.randint in a function outside the loop, it will only be called once.

get position of sprite, and move it in response to it being clicked on [duplicate]

I'm making a chess game, but I'm completely stuck on the drag and drop element, there's a few guides out there but they're all either dragging shapes, or only dragging one image.
I've tried several variants of code, but all were 50+ lines just to move one .png and most were incredibly inefficient
pygame.init()
pygame.display.set_caption("Python Chess")
clock = pygame.time.Clock()
red = (213,43,67)
chew = pygame.image.load("chew.png")
gameDisplay.fill(red)
gameDisplay.blit(chew, (400, 400))
pygame.display.update()
drag = 0
if pygame.MOUSEBUTTONDOWN:
drag = 1
if pygame.MOUSEBUTTONUP:
drag = 0
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
Image simply doesn't drag.
Let's walk through this step by step.
Step 1: Let's start with a basic skeleton of every pygame game:
import pygame
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill(pygame.Color('grey'))
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
We create a window and then start a loop to listen for events and drawing the window.
So far, so good. Nothing to see here, let's move on.
Step 2: a chess board
So, we want a chess game. So we need a board. We create a list of lists to represent our board, and we create a Surface that draws our board on the screen. We want to always seperate our game's state from the actual drawing functions, so we create a board variable and a board_surf.
import pygame
TILESIZE = 32
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
dark = not dark
dark = not dark
return board_surf
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
return board
def main():
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, (0, 0))
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Step 3: Where's the mouse?
We need to know which piece we want to select, so we have to translate the screen coordinates (where's the mouse relative to the window?) to the world coordinates (which square of the board is the mouse pointing to?).
So if the board is not located at the origin (the position (0, 0)), we also have to take this offset into account.
Basically, we have to substract that offset (which is the position of the board on the screen) from the mouse position (so we have the mouse position relative to the board), and divide by the size of the squares.
To see if this works, let's draw a red rectangle on the selected square.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
dark = not dark
dark = not dark
return board_surf
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
return board
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def main():
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
piece, x, y = get_square_under_mouse(board)
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
if x != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Step 4: Let's draw some pieces
Chess is boring without some pieces to move around, so let's create some pieces.
I just use a SysFont to draw some text instead of using real images, so everyone can just copy/paste the code and run it immediately.
We store a tuple (color, type) in the nested board list. Also, let's use some other colors for our board.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
dark = not dark
dark = not dark
return board_surf
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
for x in range(0, 8):
board[1][x] = ('black', 'pawn')
for x in range(0, 8):
board[6][x] = ('white', 'pawn')
return board
def draw_pieces(screen, board, font):
for y in range(8):
for x in range(8):
piece = board[y][x]
if piece:
color, type = piece
s1 = font.render(type[0], True, pygame.Color(color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
screen.blit(s1, s1.get_rect(center=pos.center))
def draw_selector(screen, piece, x, y):
if piece != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
def main():
pygame.init()
font = pygame.font.SysFont('', 32)
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
piece, x, y = get_square_under_mouse(board)
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
draw_pieces(screen, board, font)
draw_selector(screen, piece, x, y)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Step 5: Drag'n'Drop
For drag and drop we need two things:
we have to change the state of your game (going into the "drag-mode")
eventhandling to enter and leave the "drag-mode"
It's actually not that complicated. To enter the "drag-mode", we just set a variable (selected_piece) when the MOUSEBUTTONDOWN event occurs. Since we already have the get_square_under_mouse function, it's easy to know if there's actually a piece under the mouse cursor.
if selected_piece is set, we draw a line and the piece under the mouse cursor, and we keep track of the current square under the cursor in case the MOUSEBUTTONUP event occurs. If that's the case, we swap the position of the piece in our board.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
dark = not dark
dark = not dark
return board_surf
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
for x in range(0, 8):
board[1][x] = ('black', 'pawn')
for x in range(0, 8):
board[6][x] = ('white', 'pawn')
return board
def draw_pieces(screen, board, font, selected_piece):
sx, sy = None, None
if selected_piece:
piece, sx, sy = selected_piece
for y in range(8):
for x in range(8):
piece = board[y][x]
if piece:
selected = x == sx and y == sy
color, type = piece
s1 = font.render(type[0], True, pygame.Color('red' if selected else color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
screen.blit(s1, s1.get_rect(center=pos.center))
def draw_selector(screen, piece, x, y):
if piece != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
def draw_drag(screen, board, selected_piece, font):
if selected_piece:
piece, x, y = get_square_under_mouse(board)
if x != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (0, 255, 0, 50), rect, 2)
color, type = selected_piece[0]
s1 = font.render(type[0], True, pygame.Color(color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Vector2(pygame.mouse.get_pos())
screen.blit(s2, s2.get_rect(center=pos + (1, 1)))
screen.blit(s1, s1.get_rect(center=pos))
selected_rect = pygame.Rect(BOARD_POS[0] + selected_piece[1] * TILESIZE, BOARD_POS[1] + selected_piece[2] * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.line(screen, pygame.Color('red'), selected_rect.center, pos)
return (x, y)
def main():
pygame.init()
font = pygame.font.SysFont('', 32)
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
selected_piece = None
drop_pos = None
while True:
piece, x, y = get_square_under_mouse(board)
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.MOUSEBUTTONDOWN:
if piece != None:
selected_piece = piece, x, y
if e.type == pygame.MOUSEBUTTONUP:
if drop_pos:
piece, old_x, old_y = selected_piece
board[old_y][old_x] = 0
new_x, new_y = drop_pos
board[new_y][new_x] = piece
selected_piece = None
drop_pos = None
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
draw_pieces(screen, board, font, selected_piece)
draw_selector(screen, piece, x, y)
drop_pos = draw_drag(screen, board, selected_piece, font)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Of course there's a lot that can be improved (like using better datatypes than tuples, extract common logic into functions etc), but this should give you a good start on how to implement such things.
Always keep in mind:
write a single game loop that handles events, game logic, and drawing
make sure to only call pygame.display.flip once per frame
seperate your game state from your drawing functions
never call time.sleep or pygame.time.wait
use the build-in classes like Vector2 and Rect, they'll make your live easier (I didn't the Sprite class in this code, but it's also very usefull)
use functions to clean up your code
avoid global variables, except for constants
PyGame is low-level library, not game engine, and you have to make almost all from scratch.
This example drags two images but for more images it could use list or pygame.sprite.Group with pygame.sprite.Sprites and then code is getting longer and longer but as I said PyGame is not game engine like ie. Godot Engine (which uses language similar to Python). Maybe with Pyglet could be easier because you don't have to write mainloop from scratch but it still need some work. In PyGame you have to write from scratch even main element - mainloop.
import pygame
# --- constants ---
RED = (213, 43, 67)
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800,600))
chew1 = pygame.image.load("chew.png")
chew1_rect = chew1.get_rect(x=400, y=400)
chew2 = pygame.image.load("chew.png") # use different image
chew2_rect = chew1.get_rect(x=200, y=200)
drag = 0
# --- mainloop ---
clock = pygame.time.Clock()
game_exit = False
while not game_exit:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
elif event.type == pygame.MOUSEBUTTONDOWN:
drag = 1
elif event.type == pygame.MOUSEBUTTONUP:
drag = 0
elif event.type == pygame.MOUSEMOTION:
if drag:
chew1_rect.move_ip(event.rel)
chew2_rect.move_ip(event.rel)
# - draws -
screen.fill(RED)
screen.blit(chew1, chew1_rect)
screen.blit(chew2, chew2_rect)
pygame.display.update()
# - FPS -
clock.tick(30)
# --- end ---
pygame.quit()
EDIT: the same with Group and Sprite and now you have to only add images to group items and rest of the code doesn't need to be changed.
import pygame
# --- constants ---
RED = (213, 43, 67)
# --- classes ---
class Item(pygame.sprite.Sprite):
def __init__(self, image, x, y):
super().__init__()
self.image = pygame.image.load(image)
self.rect = self.image.get_rect(x=x, y=y)
def update(self, rel):
self.rect.move_ip(rel)
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800,600))
items = pygame.sprite.Group(
Item("chew.png", 200, 200),
Item("chew.png", 400, 200),
Item("chew.png", 200, 400),
Item("chew.png", 400, 400),
)
drag = 0
# --- mainloop ---
clock = pygame.time.Clock()
game_exit = False
while not game_exit:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
elif event.type == pygame.MOUSEBUTTONDOWN:
drag = 1
elif event.type == pygame.MOUSEBUTTONUP:
drag = 0
elif event.type == pygame.MOUSEMOTION:
if drag:
items.update(event.rel)
# - draws -
screen.fill(RED)
items.draw(screen)
pygame.display.update()
# - FPS -
clock.tick(30)
# --- end ---
pygame.quit()
EDIT: this version use Group to move only clicked image(s). If you click in place where two (or more) images are overlapped then it will drag two (or more) images.
import pygame
# --- constants ---
RED = (213, 43, 67)
# --- classes ---
class Item(pygame.sprite.Sprite):
def __init__(self, image, x, y):
super().__init__()
self.image = pygame.image.load(image)
self.rect = self.image.get_rect(x=x, y=y)
def update(self, rel):
self.rect.move_ip(rel)
# --- main ---
pygame.init()
screen = pygame.display.set_mode((800,600))
items = pygame.sprite.Group(
Item("chew.png", 150, 50),
Item("chew.png", 400, 50),
Item("chew.png", 150, 300),
Item("chew.png", 400, 300),
)
dragged = pygame.sprite.Group()
# --- mainloop ---
clock = pygame.time.Clock()
game_exit = False
while not game_exit:
# - events -
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_exit = True
elif event.type == pygame.MOUSEBUTTONDOWN:
dragged.add(x for x in items if x.rect.collidepoint(event.pos))
elif event.type == pygame.MOUSEBUTTONUP:
dragged.empty()
elif event.type == pygame.MOUSEMOTION:
dragged.update(event.rel)
# - draws -
screen.fill(RED)
items.draw(screen)
pygame.display.update()
# - FPS -
clock.tick(30)
# --- end ---
pygame.quit()
EDIT: similar program in Pyglet - it moves two images with less code.
Pyglet has point (0,0) in left bottom corner.
To create red background it has to draw rectangle (QUADS) in OpenGL.
import pyglet
window = pyglet.window.Window(width=800, height=600)
batch = pyglet.graphics.Batch()
items = [
pyglet.sprite.Sprite(pyglet.resource.image('chew.png'), x=200, y=100, batch=batch),
pyglet.sprite.Sprite(pyglet.resource.image('chew.png'), x=400, y=300, batch=batch),
]
#window.event
def on_draw():
#window.clear()
pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2f', [0,0, 800,0, 800,600, 0,600]), ('c3B', [213,43,67, 213,43,67, 213,43,67, 213,43,67])) #RED = (213, 43, 67)
batch.draw()
#window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
for i in items:
i.x += dx
i.y += dy
pyglet.app.run()

Categories