How would I go about loading a map file in Pygame / Python?
I would like to load a map file in this format;
http://pastebin.com/12DZhrtp
EDIT: I'm guessing it's a for loop, but I'm not sure on how to do that.
Though some people have helped open a file, I understand you are actually looking to import a text file as a map:
I did not write this, but have been using it as an example for my game:
# This code is in the Public Domain
# -- richard#mechanicalcat.net
class Map:
def __init__(self, map, tiles):
self.tiles = pygame.image.load(tiles)
l = [line.strip() for line in open(map).readlines()]
self.map = [[None]*len(l[0]) for j in range(len(l))]
for i in range(len(l[0])):
for j in range(len(l)):
tile = l[j][i]
tile = tile_coords[tile]
if tile is None:
continue
elif isinstance(tile, type([])):
tile = random.choice(tile)
cx, cy = tile
if random.choice((0,1)):
cx += 192
if random.choice((0,1)):
cy += 192
self.map[j][i] = (cx, cy)
def draw(self, view, viewpos):
'''Draw the map to the "view" with the top-left of "view" being at
"viewpos" in the map.
'''
sx, sy = view.get_size()
bx = viewpos[0]/64
by = viewpos[1]/64
for x in range(0, sx+64, 64):
i = x/64 + bx
for y in range(0, sy+64, 64):
j = y/64 + by
try:
tile = self.map[j][i]
except IndexError:
# too close to the edge
continue
if tile is None:
continue
cx, cy = tile
view.blit(self.tiles, (x, y), (cx, cy, 64, 64))
def limit(self, view, pos):
'''Limit the "viewpos" variable such that it defines a valid top-left
rectangle of "view"'s size over the map.
'''
x, y = pos
# easy
x = max(x, 0)
y = max(y, 0)
# figure number of tiles in a view, hence max x and y viewpos
sx, sy = view.get_size()
nx, ny = sx/64, sy/64
mx = (len(self.map[0]) - nx) * 64
my = (len(self.map) - ny) * 64
print y, my
return (min(x, mx), min(y, my))
def main():
pygame.init()
win = pygame.display.set_mode((640, 480))
map = Map('map.txt', 'tiles.png')
viewpos = (0,0)
move = False
clock = pygame.time.Clock()
sx, sy = win.get_size()
while 1:
event = pygame.event.poll()
while event.type != NOEVENT:
if event.type in (QUIT, KEYDOWN):
sys.exit(0)
elif event.type == MOUSEBUTTONDOWN:
x, y = viewpos
dx, dy = event.pos
x += dx - sx/2
y += dy - sy/2
viewpos = map.limit(win, (x, y))
move = True
event = pygame.event.poll()
win.fill((0,0,0))
map.draw(win, viewpos)
pygame.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
If you're literally asking how to load a file in Python -- disregarding the pygame side of the question -- then it's very simple.
>>> with open('a.map', 'r') as f:
... for line in f:
... print line,
...
x g g g x
x g g g x
x g x g x
x g g g x
x x x x x
KEY:
x = wall
g = grass / floor
Now instead of printing each line, you can simply read through it and store it in whatever data structure you're using.
I don't know anything about pygame though -- if it has some custom function for this, I can't help with that.
Since a map is just a 2d array, it's two loops to get through the whole thing.
self.map = [[None]*len(l[0]) for j in range(len(l))]
for i in range(len(l[0])):
for j in range(len(l)):
Details can be found many places. Here's one:
http://www.mechanicalcat.net/richard/log/Python/PyGame_sample__drawing_a_map__and_moving_around_it
Inside you'd determine what to draw. In your case: wall, grass, or floor, which would be sprites.
Related
I want to make a traffic simulator of a city and for that I need to get the exact location of the streets and then plot them.
To draw the map I'm taking the streets of omsnx, but these have some flaws.Here I show how it looks like using the omsnx.plot_graph() function.
But using the values of G.edges() and building the graph myself there are inconsistencies like extra streets. This is an example of what the graph looks like using exactly the values provided by the edges.
Note that both images show the same traffic circle.
So the question is how to obtain the real street values without noise, i.e. without several streets playing the role of one.
This is the code I use to plot with pygame.
import osmnx as ox
address_name='Ciudad Deportiva, Havana, Cuba'
#Import graph
point = (23.1021, -82.3936)
G = ox.graph_from_point(point, dist=1000, retain_all=True, simplify=True, network_type='drive')
G = ox.project_graph(G)
import pygame
from map import Map
RED = (255, 0, 0)
BLUE = (0, 0, 20)
GRAY = (215,215,215)
WHITE = (255, 255, 255)
inc = 0.8
m = Map(1400, 800, lng=inc*2555299.469922482, lat=inc*356731.10053785384, i_zoom=0.1)
last_x = -1
last_y = -1
i=0
while True:
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 4:
m.inc_zoom()
elif event.button == 5:
m.dec_zoom()
if event.type == pygame.MOUSEMOTION:
if event.buttons[0] == 1:
if last_x == last_y == -1:
last_x, last_y = event.pos
else:
m.x += (event.pos[0] - last_x)/2
m.y += (event.pos[1] - last_y)/2
last_x, last_y = event.pos
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
last_x = last_y = -1
m.fill(GRAY)
# Here I take the coordinates of the streets and paint them
for _, _, data in G.edges(data=True):
try:
m.draw_road(data['geometry'], inc, BLUE)
except:
pass
m.update()
Other functions and objects I use for plotting. This is the code of map.py
from typing import List, Tuple
from window import Window
from pygame import gfxdraw
from shapely.geometry import LineString
def build_rect(
start: Tuple[float,float],
end: Tuple[float,float],
width: float = 3
) -> List[Tuple[float,float]]:
x0, y0 = start
x1, y1 = end
if x0**2 + y0**2 > x1**2 + y1**2:
x0, y0 = end
x1, y1 = start
# vector from start to end
vX, vY = x1 - x0, y1 - y0
# normal vector
nX, nY = -vY, vX
# normalize
n = (nX**2 + nY**2)**0.5
nX, nY = width/n * nX, width/n * nY
# third vector
x2, y2 = x1 + nX, y1 + nY
# fourth vector
x3, y3 = x0 + nX, y0 + nY
return [(x0, y0), (x1, y1), (x2, y2), (x3, y3)]
class Map(Window):
def __init__(self, width, height, **kwargs):
super().__init__(width, height, **kwargs)
def draw_road(self, st: LineString, inc: float, color: Tuple[float,float,float]):
last = None
for c in st.__geo_interface__['coordinates']:
c = (inc*c[0], inc*c[1])
if last == None:
last = (c[0] - self.lat,c[1] - self.lng)
continue
lat = c[0] - self.lat
lng = c[1] - self.lng
pts = build_rect(last, (lat, lng))
gfxdraw.filled_polygon(self.screen, [
(self.x + lat * self.zoom, self.y + lng * self.zoom)
for lat, lng in pts
], color)
And this is the code of window.py.
import pygame
from pygame import gfxdraw
from pygame.locals import *
class Window:
def __init__(self, width, height, **kwargs):
self.width = width
self.height = height
self.zoom = 1
self.x = self.y = 0
self.i_zoom = 0.001
self.__dict__.update(kwargs)
pygame.init()
self.screen = pygame.display.set_mode((width, height))
def inc_zoom(self):
self.zoom += self.i_zoom
def dec_zoom(self):
self.zoom -= self.i_zoom
self.zoom = max(0, self.zoom)
def draw_polygon(self, points, color):
gfxdraw.filled_polygon(self.screen, [
(self.x + pt[0] * self.zoom, self.y + pt[1] * self.zoom)
for pt in points], color)
def fill(self, color):
self.screen.fill(color)
def update(self):
pygame.display.update()
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 2 years ago.
Improve this question
I'm new to python and PyGame and wanted to get some experience by doing what i thought would be a simple project. I can't tell if my error is in my game logic or my PyGame printing. I created two function, one that fills the grid with random values and one that fills the grid with a "Blinker". The program runs without error, however, the rules of the game are not followed. For example, When the "blinker" is set, the program's second frame clears the screen instead of rotating the "blinker".
Any help diagnosing this problem would be appreciated!
import pygame
import random
pygame.init()
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
# Sizes
size = (600, 600)
width = 20
height = 20
margin = 1
x_size = 600 / width
y_size = 600 / height
def init_grid():
return [[0 for x in range(x_size)] for y in range(y_size)]
def make_spinner(grind):
grid[0][0] = 1
grid[10][10] = 1
grid[10][11] = 1
grid[10][12] = 1
def random_grid(grid):
for x in range(x_size):
for y in range(y_size):
grid[x][y] = random.randint(0, 1)
def print_grid(screen, grid):
for x in range(x_size):
for y in range(y_size):
if grid[x][y] == 1:
pygame.draw.rect(
screen, BLACK, (x * width, y * height, width, height))
else:
pygame.draw.rect(
screen, BLACK, (x * width, y * height, width, height), margin)
def count_neighbours(grid, x, y):
count = 0
for i in range(-1, 1):
for j in range(-1, 1):
count += grid[x + i][y + j]
return count - grid[x][y]
def update_grid(grid):
next_grid = init_grid()
for x in range(x_size):
for y in range(y_size):
if x == 0 or x == x_size - 1 or y == 0 or y == y_size - 1:
next_grid[x][y] = 0
else:
count = count_neighbours(grid, x, y)
value = grid[x][y]
if value == 1 and (count == 2 or count == 3):
next_grid[x][y] = 1
elif value == 0 and count == 3:
next_grid[x][y] = 1
else:
next_grid[x][y] = 0
return next_grid
# Initialise game engine
screen = pygame.display.set_mode(size)
pygame.display.set_caption("The Game of Life")
running = True
clock = pygame.time.Clock()
grid = init_grid()
# random_grid(grid)
make_spinner(grid)
# Game loop
while running:
# Check for exit
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(WHITE)
print_grid(screen, grid)
next_grid = update_grid(grid)
pygame.display.update()
grid = next_grid
clock.tick(2)
pygame.quit()
Your count_neighbors function doesn't iterate over the right cells. range(-1,1) iterates over {-1,0} not {-1,0,1}.
Instead, use:
def count_neighbours(grid, x, y):
count = 0
for i in range(-1,2):
for j in range(-1,2):
count += grid[x + i][y + j]
return count - grid[x][y]
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
could you help me with a problem I have? I am a new person with regard to programming and to guide me I am using the book: How to think like a computer scientist 3rd edition. And it could not solve exercise 2 of chapter 17. This says that an error occurs when clicking on any frame that is on the right side of the sprite, which causes the animation to start, in theory it should only do the animation if it is you click directly on the sprite, I tried to solve it in many ways but I couldn't, could you help me ?, I think the error occurs in this part
return ( x >= my_x and x + my_width and y >= my_y and y < my_y + my_height)
but I'm not sure, for anything I leave you all the code I have
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_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
There is < my_x missing in the comparisons expression in the method contains_point of the class DukeSprite:
return ( x >= my_x and x + my_width and y >= my_y and y < my_y + my_height)
return ( x >= my_x and x < my_x + my_width and y >= my_y and y < my_y + my_height)
Anyway in python you should use chained comparisons:
return my_x <= x < my_x + my_width and my_y <= y < my_y + my_height
In pygame you should use pygame.Rect and collidepoint(). The rectangle of the object you can get from the pygame.Surface with the method get_rect and the position can be set by an keyword argument:
def contains_point(self, pt):
""" Return True if my sprite rectangle contains pt """
my_rect = self.image.get_rect(topleft = self.posn)
return my_rect.collidepoint(pt)
This is for an extra credit assignment in Python. I've finished most until the last part where I have to determine the area of the tictactoe box chosen.
I can only detect diagonal boxes, used from combining both code reply's below.
I can detect those 3 boxes but the rest still show as none and most logic is used with loop so I can understand where I am going wrong.
import turtle
from time import sleep
import sys
CURSOR_SIZE = 20
SQUARE_SIZE = 99
FONT_SIZE = 40
FONT = ('Arial', FONT_SIZE, 'bold')
BOXES = {}
# TRACK BOX
pen = turtle.Turtle()
pen.penup()
def mouse(x, y):
print('|--------------X={0} Y={1}--------------|'.format(x, y))
for key in BOXES:
minx, miny, maxx, maxy = BOXES[key]
print(key, BOXES[key])
if (minx <= x <= maxx) and (miny <= y <= maxy):
print("Found", key)
return key
print('None')
return None # Not found.
class TicTacToe:
global BOXES
def __init__(self):
# CREATES 2D LIST FOR INTERROGATION
self.board = [['?'] * 3 for i in range(3)]
def minmax(self, points):
""" Find extreme x and y values in a list of 2-D coordinates. """
minx, miny, maxx, maxy = points[0][0], points[0][1], points[0][0], points[0][1]
for x, y in points[1:]:
if x < minx:
minx = x
if y < minx:
miny = y
if x > maxx:
maxx = x
if y > maxy:
maxy = y
return minx, miny, maxx, maxy
def drawBoard(self):
##############################################
turtle.shape('square')
turtle.shapesize(SQUARE_SIZE * 3 / CURSOR_SIZE)
turtle.color('black')
turtle.stamp()
turtle.hideturtle()
##############################################
for j in range(3):
for i in range(3):
# CREATES SHAPE AND STORES IN PLACEHOLDER
turtle.shape('square')
box = turtle.shape('square')
# CREATES SHAPE SIZE AND STORES IN PLACEHOLDER
turtle.shapesize(SQUARE_SIZE / CURSOR_SIZE)
boxsize = turtle.shapesize()
# CREATES SHAPE COLOR
turtle.color('white')
turtle.penup()
# CREATES SHAPE POS AND STORES IN PLACEHOLDER
turtle.goto(i * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2), j * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2))
boxpos = turtle.pos()
mypos = []
pen.goto(boxpos[0]-50,boxpos[1]+50)
##############################################
for line in range(0, 4):
pen.forward(SQUARE_SIZE)
pen.right(90)
mypos.append(pen.pos())
turtle.showturtle()
turtle.stamp()
##############################################
a = mypos[0]
b = mypos[1]
c = mypos[2]
d = mypos[3]
self.board[j][i] = [a, b, c, d]
##############################################
BOXES['BOX01'] = self.minmax(self.board[0][0])
BOXES['BOX02'] = self.minmax(self.board[0][1])
BOXES['BOX03'] = self.minmax(self.board[0][2])
##############################################
BOXES['BOX11'] = self.minmax(self.board[1][0])
BOXES['BOX12'] = self.minmax(self.board[1][1])
BOXES['BOX13'] = self.minmax(self.board[1][2])
##############################################
BOXES['BOX21'] = self.minmax(self.board[2][0])
BOXES['BOX22'] = self.minmax(self.board[2][1])
BOXES['BOX23'] = self.minmax(self.board[2][2])
##############################################
turtle.onscreenclick(mouse)
turtle.setup(800, 600)
wn = turtle.Screen()
z = TicTacToe()
z.drawBoard()
turtle.mainloop()
I believe you're making the problem harder than necessary by not taking full advantage of Python turtle. Instead of trying to find a square within the board when clicking on the screen, make the squares of the board themselves turtles that respond to mouse clicks. Then there's nothing to figure out, position-wise.
Here's a reimplementation that draws a board, allows you to click on it, alternately sets the clicked sections to 'X' or 'O':
from turtle import Turtle, Screen
CURSOR_SIZE = 20
SQUARE_SIZE = 50
FONT_SIZE = 40
FONT = ('Arial', FONT_SIZE, 'bold')
class TicTacToe:
def __init__(self):
self.board = [['?'] * 3 for i in range(3)] # so you can interrogate squares later
self.turn = 'X'
def drawBoard(self):
background = Turtle('square')
background.shapesize(SQUARE_SIZE * 3 / CURSOR_SIZE)
background.color('black')
background.stamp()
background.hideturtle()
for j in range(3):
for i in range(3):
box = Turtle('square', visible=False)
box.shapesize(SQUARE_SIZE / CURSOR_SIZE)
box.color('white')
box.penup()
box.goto(i * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2), j * (SQUARE_SIZE + 2) - (SQUARE_SIZE + 2))
box.showturtle()
box.stamp() # blank out background behind turtle (for later)
self.board[j][i] = box
box.onclick(lambda x, y, box=box, i=i, j=j: self.mouse(box, i, j))
def mouse(self, box, i, j):
box.onclick(None) # disable further moves on this square
# replace square/turtle with (written) X or O
box.hideturtle()
box.color('black')
box.sety(box.ycor() - FONT_SIZE / 2)
box.write(self.turn, align='center', font=FONT)
self.board[j][i] = self.turn # record move
self.turn = ['X', 'O'][self.turn == 'X'] # switch turns
screen = Screen()
game = TicTacToe()
game.drawBoard()
screen.mainloop()
You can use board to do scoring, or implement a smart computer player, or whatever you desire.
This should give you the basic idea, which is compute the minimum and maximum x and y values of each box and store them in BOXES. This makes it very easy to determine if the given x and y coordinates passed to the mouse() callback function are within any of them.
With the multiple boxes in your real code, make sure to apply the new minmax() function to the corners of each one of them.
import turtle
""" This will be based off 1 box instead of all 9"""
pen = turtle.Turtle()
corners = []
BOXES = {}
for line in range(0, 4):
pen.forward(50)
pen.left(90)
corners.append(pen.pos())
def minmax(points):
""" Find extreme x and y values in a list of 2-D coordinates. """
minx, miny, maxx, maxy = points[0][0], points[0][1], points[0][0], points[0][1]
for x, y in points[1:]:
if x < minx:
minx = x
if y < minx:
miny = y
if x > maxx:
maxx = x
if y > maxy:
maxy = y
return minx, miny, maxx, maxy
BOXES['MIDDLEBOX'] = minmax(corners)
for i in BOXES:
print(i, BOXES[i])
"""
POINTS FROM LEFT TO RIGHT
(1) - TOP LEFT CORNER
(2) - BOTTOM LEFT CORNER
(3) - BOTTOM RIGHT CORNER
(4) - TOP RIGHT CORNER
"""
def mouse(x, y):
""" Return key of box if x, y are within global BOXES
or None if it's not.
"""
for key in BOXES:
minx, miny, maxx, maxy = BOXES[key]
if (minx <= x <= maxx) and (miny <= y <= maxy):
print(key)
return key
print('None')
return None # Not found.
turtle.onscreenclick(mouse)
turtle.mainloop()
I have a gravity vector (in the form [r, theta]) which I add to my ball's velocity vector. For some reason, the ball doesn't return to the same height after bouncing, but instead slowly loses height sporadically. I am guessing there's some rounding error or something in a calculation I'm using, but I can't isolate the issue.
Here is my code. You need both files and pygame to run it. Sorry if it's a little confusing. I can comment anything some more if you want.
I added a marker whenever the ball reaches its max height so you guys what I mean. I want the ball to return to exactly the same height every time it bounces.
I took a little bit of unnecessary code out. The full program is under the pastebin links.
https://pastebin.com/FyejMCmg - PhysicsSim
import pygame, sys, math, tools, random, time
from pygame.locals import *
clock = pygame.time.Clock()
lines = []
class Particle:
def __init__(self,screen,colour, mass, loc, vel):
self.screen = screen
self.colour = colour
self.mass = mass
self.x = loc[0]
self.y = loc[1]
self.location = self.x,self.y
self.speed = vel[0]
self.angle = vel[1]
def update(self):
global lines
# add gravity
self.speed,self.angle = tools.add_vectors2([self.speed,self.angle], tools.GRAVITY)
# update position
dt = clock.tick(60)
self.x += self.speed * tools.SCALE * math.cos(self.angle) * dt
self.y -= self.speed * tools.SCALE * math.sin(self.angle) * dt
self.location = int(self.x),int(self.y)
# border checking
do = False
n=[]
if ((self.y+self.mass) > tools.SCREEN_HEIGHT):
self.y = tools.SCREEN_HEIGHT-self.mass
n = [0,1]
do = True
# adds position to array so max height so max height can be recorded
if (self.speed==0):
lines.append([self.screen, self.location, self.mass])
# bounce
if do:
#init, convert everything to cartesian
v = tools.polarToCartesian([self.speed, self.angle])
#final -> initial minus twice the projection onto n, where n is the normal to the surface
a = tools.scalarP(2*abs(tools.dotP(v,n)),n) #vector to be added to v
v = tools.add_vectors(v,a)
self.angle = tools.cartesianToPolar(v)[1] # does not set magnitude
# drawing
pygame.draw.circle(self.screen, self.colour, self.location, self.mass, 0)
# draws max height line
def draw_line(l):
screen = l[0]
location = l[1]
radius = l[2]
pygame.draw.line(screen, tools.BLACK, [location[0] + 15, location[1]-radius],[location[0] - 15, location[1]-radius])
def main():
pygame.init()
DISPLAY = pygame.display.set_mode(tools.SCREEN_SIZE,0,32)
DISPLAY.fill(tools.WHITE)
particles = []
particles.append(Particle(DISPLAY, tools.GREEN, 10, [100,100], [0,0]))
done = False
while not done:
global lines
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
DISPLAY.fill(tools.WHITE)
for i in particles:
i.update()
for l in lines:
draw_line(l)
pygame.display.update()
main()
https://pastebin.com/Epgqka31 - tools
import math
#colours
WHITE = (255, 255, 255)
BLUE = ( 0, 0, 255)
GREEN = ( 0, 255, 0)
RED = ( 255, 0, 0)
BLACK = ( 0, 0, 0)
COLOURS = [WHITE,BLUE,GREEN,RED,BLACK]
#screen
SCREEN_SIZE = SCREEN_WIDTH,SCREEN_HEIGHT = 1000,700
#vectors
GRAVITY = [5.0, 3*math.pi/2] # not 9.8 because it seems too high
SCALE = 0.01
# converts polar coordinates to cartesian coordinates in R2
def polarToCartesian(v):
return [v[0]*math.cos(v[1]), v[0]*math.sin(v[1])]
# converts cartesian coordinates to polar coordinates in R2
def cartesianToPolar(v):
return [math.sqrt(v[0]**2 + v[1]**2), math.atan2(v[1],v[0])]
# dots two cartesian vectors in R2
def dotP(v1, v2):
return v1[0]*v2[0] + v1[1]*v2[1]
# multiplies cartesian vector v by scalar s in Rn
def scalarP(s,v):
v_=[]
for i in v:
v_.append(s*i)
return v_
# returns the sum of two cartesian vectors in R2
def add_vectors(v1, v2):
return [v1[0]+v2[0], v1[1]+v2[1]]
# returns the sum of two polar vectors in R2, equations from https://math.stackexchange.com/questions/1365622/adding-two-polar-vectors
def add_vectors2(v1,v2):
r1,r2,t1,t2 = v1[0],v2[0],v1[1],v2[1]
return [math.sqrt(r1**2 + r2**2 + 2*r1*r2*math.cos(t2-t1)), t1 + math.atan2(r2*math.sin(t2 - t1), r1 + r2*math.cos(t2 - t1))]
Your time interval, dt = clock.tick(60), is not a constant. If you change it to dt = 60 your program runs as expected.
Have a look a the Verlet Algorithm and implement it in your code. You are on the right track!