I want to visualize an algorithm (Graham scan) in python with tkinter.
I want to animate the algorithm and I am stuck.
I basically want to draw and delete lines but I don't understand canvas.after() well enough to make it work.
draw_line() returns the line object but when I call it in canvas.after(..., draw_line, ...) I don't see a way to get the return value or how to call another canvas.after() to change the color/delete that line if the function draw_line() hasn't been called yet because of the delay.
Thanks in advance.
from tkinter import *
import math
import random
class Point:
def __init__(self, _x, _y, _a=0):
self.x = _x
self.y = _y
self.angle = _a
def get_co(self):
return self.x, self.y
def draw_hull(hull):
for i in range(len(hull) - 1):
canvas.create_line(hull[i][0], hull[i][1], hull[i + 1][0], hull[i + 1][1], fill="red", width=2)
def draw_line(p1, p2, color="yellow"):
return canvas.create_line(p1.x, p1.y, p2.x, p2.y, fill=color, width=2)
def convex_hull(list_points):
# find bottom point
bottom_point = Point(math.inf, math.inf)
for point in list_points:
if point[1] < bottom_point.y:
bottom_point = Point(point[0], point[1])
# calculate angles between the bottom point and the other points
points = []
for point in list_points:
if point != bottom_point.get_co():
new_point = Point(point[0], point[1])
angle = calculate_angle(bottom_point, new_point)
new_point.angle = angle
points.append(new_point)
# sort the points by angle
swaps = None
while swaps != 0:
swaps = 0
for i in range(len(points) - 1):
point1 = points[i]
point2 = points[i + 1]
if point1.angle > point2.angle:
points[i], points[i + 1] = points[i + 1], points[i]
swaps += 1
# go through the points and add them to the convex hull
# if the angle between 3 points ever exeeds 180 degrees, discard the middle point
hull = [bottom_point, points[0]]
i = 1
while i < len(points):
####### DRAW LINE #######
canvas.after(i*500, draw_line, hull[-2], hull[-1])
##############
# check angle
angle = calculate_angle(hull[-2], hull[-1], points[i])
if angle == -1:
########## DELETE LINE ##########
# change color of line to red and delete it a bit later
# canvas.itemconfig(line, fill="red")
# canvas.after(i*500+250, canvas.delete, line)
####################
# pop the point of the stack
hull.pop()
else:
########## CHANGE COLOR OF LINE ##########
# change color of line to green
# canvas.itemconfig(line, fill="green")
####################
# move to the next point
hull.append(points[i])
i += 1
# add bottom point again for loop
hull.append(bottom_point)
# give easy return list (with coordinate-tupels not class objects)
output = []
for point in hull:
output.append(point.get_co())
return output
def calculate_angle(point1, point2, point3=None):
if point3 is None:
if point2.x - point1.x == 0:
return 90
elif point2.x - point1.x > 0:
return math.degrees(math.atan((point2.y - point1.y)/(point2.x - point1.x)))
else:
return 180 - math.degrees(math.atan((point2.y - point1.y)/(point1.x - point2.x)))
else:
v1 = Point(point1.x - point2.x, point1.y - point2.y)
v2 = Point(point3.x - point2.x, point3.y - point2.y)
det = (v1.x * v2.y) - (v2.x * v1.y)
if det < 0:
return 1
else:
return -1
window = Tk()
window.geometry("1000x600")
canvas = Canvas(window, width=1000, height=600)
canvas.pack()
POINTSIZE = 2
points = []
for i in range(100):
x = random.randint(50, 950)
y = random.randint(50, 550)
points.append((x, y))
canvas.create_oval(x - POINTSIZE, y - POINTSIZE, x + POINTSIZE, y + POINTSIZE, fill="black")
hull = convex_hull(points)
# draw_hull(hull)
window.mainloop()
If you have questions about the code, let me know. Because I dont know where to start to explain, since I made major changes to your code.
Anyway, I would be glad if you share your code again, once you are done with, on CodeReview and please do let me know. Because it was fun to work with and your code seems incomplete to me.
Happy Coding:
import tkinter as tk
import random
import math
class Point:
def __init__(self, _x, _y, _a=0):
self.x = _x
self.y = _y
self.angle = _a
return None
def get_co(self):
return self.x, self.y
class Window(tk.Tk):
def __init__(self,_w,_h):
super().__init__()
self.POINTSIZE = 2
self.points = []
self.swaps = None
self.count = 1
self.delay = 200
self.title('Graham scan simulation')
self.toolbar = tk.Frame(self,background='grey')
self.refresh_button = tk.Button(self.toolbar,text='Refresh',
command=self.refresh)
self.start_button = tk.Button(self.toolbar,text='Start',
command = self.convex_hull)
self.canvas = tk.Canvas(self,width=_w,height=_h)
self.toolbar.pack(side=tk.TOP,fill=tk.BOTH,expand=True)
self.refresh_button.pack(side=tk.LEFT)
self.start_button.pack(side=tk.LEFT)
self.canvas.pack(side=tk.BOTTOM,fill=tk.BOTH,expand=True)
def generate_points(self):
for point_instance in self.points:
yield point_instance
def find_bottom_point(self):
bottom_point = Point(math.inf,math.inf)
for point in self.generate_points():
if point.y < bottom_point.y:
bottom_point = point
return bottom_point
def calculate_angle(self,point1, point2):
if point2.x - point1.x == 0:
return 90
elif point2.x - point1.x > 0:
return math.degrees(math.atan((point2.y - point1.y)/(point2.x - point1.x)))
else:
return 180 - math.degrees(math.atan((point2.y - point1.y)/(point1.x - point2.x)))
def calculate_angels_by_bottom_point(self,bottom_point):
for point in self.generate_points():
if point != bottom_point:
angle = self.calculate_angle(bottom_point,point)
point.angle = angle
def sort_points(self,event_variable):
if self.swaps != 0:
self.swaps = 0
for i in range(len(self.points)-1):
point1 = self.points[i]
point2 = self.points[i + 1]
if point1.angle > point2.angle:
self.points[i], self.points[i + 1] = self.points[i + 1], self.points[i]
self.swaps += 1
if self.swaps == 0:
event_variable.set(1)
self.after(20,self.sort_points,event_variable)
def check_angle(self,point1,point2,point3):
v1 = Point(point1.x - point2.x, point1.y - point2.y)
v2 = Point(point3.x - point2.x, point3.y - point2.y)
det = (v1.x * v2.y) - (v2.x * v1.y)
if det < 0:
return 1
else:
return -1
def draw_line(self,p1,p2,color='yellow'):
return self.canvas.create_line(p1.x,p1.y, p2.x,p2.y, fill='yellow',tags='line')
def clear_red_lines(self,p1,p2):
shapes = self.canvas.find_withtag('line')
for shape in shapes:
if self.canvas.itemcget(shape,'fill') == 'red':
coords = self.canvas.coords(shape)
overlapped = self.canvas.find_overlapping(*coords)
for i in overlapped:
coords2 = self.canvas.coords(i)
if coords == coords2:
self.canvas.delete(i)
self.canvas.delete(shape)
def animated_draw(self,hull):
if self.count != len(self.points):
line = self.draw_line(hull[-2],hull[-1])
check_mark = self.check_angle(hull[-2],hull[-1],self.points[self.count])
if check_mark == -1:
self.canvas.itemconfig(line,fill='red')
self.after(self.delay-100,lambda:self.clear_red_lines(hull[-2],hull[-1]))
hull.pop()
else:
self.canvas.itemconfig(line,fill='green')
hull.append(self.points[self.count])
self.count += 1
self.after(self.delay,self.animated_draw,hull)
def convex_hull(self):
bottom_point = self.find_bottom_point()
self.calculate_angels_by_bottom_point(bottom_point)
event_variable = tk.IntVar(self,value=0)
self.sort_points(event_variable)
self.wait_variable(event_variable)
self.points.pop(0)
self.animated_draw(hull = [bottom_point, self.points[0]])
def generate_listpoints(self,_amount):
'''using a generator for memory purpose'''
for i in range(_amount):
x = random.randint(50, 950)
y = random.randint(50, 550)
yield x,y
def refresh(self):
self.swaps = None
self.count = 1
self.points= []
self.populate_canvas()
def populate_canvas(self):
self.canvas.delete('all')
for x,y in self.generate_listpoints(100):
self.points.append(Point(x, y))#instead of creating throwing instances away.
self.canvas.create_oval(x - self.POINTSIZE,
y - self.POINTSIZE,
x + self.POINTSIZE,
y + self.POINTSIZE,
fill="black")
root = Window(1000,600)
root.mainloop()
Related
I'm currently trying to make two particles in a gaseous state collide, but I'm not too sure how to write a code that will make this happen, I have a rough draft of what I could do but I don't really know how I could properly implent it (lines 43-54 i.e the code after the comment #collision of i with another particle j) after the collision occurs I wanted them to go in the opposite direction with different speedssince I will be computing the kinetic energy depending on the mass of the particles. The goal of the project is to basically show the conservation of kinetic energy of multiple particles of differnet parameters(velocity,mass,direction) moving in gaz. Here's my current code, any help would be greatly appreciated!!:
from tkinter import *
from random import *
myHeight=400
myWidth=600
mySpeed=10
x1=5
y=5
radius1=20
x2=7
y2=4
radius1=20
x=60
width=40
length=10
global particules
particules = []
def initialiseBall(dx,dy,radius,color):
b = [myWidth/2,myHeight/2, dx, dy, radius]
particules.append(b)
k = myCanvas.create_oval(myWidth/2-radius, myHeight/2,\
myWidth/2+radius,myHeight/2+radius,\
width=2,fill=color)
print(k)
def updateBalls():
N = len(particules)
for i in range(N):
particules[i][0] += particules [i][2]
particules[i][1] += particules [i][3]
# collision of i with the walls
if particules[i][0]<0 or particules[i][0]>=myWidth:
particules[i][2] *= -1
if particules[i][1]<0 or particules[i][1]>=myHeight:
particules[i][3] *= -1
#collision of i with another particle j
# for j in range(N):
# if j != i:
# compare the position of i and j
# dij = ...
# if dij ... :
#if collision, compute the normal vector
#change velocities
# if particules[i][1]<=particules[i][1]:
# particules[i][2] *= -1
# r = particules[i][4]
myCanvas.coords(i+1, particules[i][0]-particules[i][4],
particules[i][1]-particules[i][4],
particules[i][0]+particules[i][4],
particules[i][1]+particules[i][4])
def animation ():
updateBalls()
myCanvas.after(mySpeed, animation)
def kineticenergy(mass, velocity):
Ec = 1/2 * mass * velocity ** 2
return Ec
# def averagetrip(number, radius):
# #
# #
# #
# #
mainWindow=Tk()
mainWindow.title('particles reservoir')
myCanvas = Canvas(mainWindow, bg = 'grey', height = myHeight, width = myWidth)
myCanvas.pack(side=TOP)
# create 2 particules
initialiseBall(-1,0, 50, 'red')
initialiseBall(1,0, 50, 'blue')
print(particules)
'''
N = 100
for n in range(N):
initialiseBalle(-1 ,0, randint(5,10), 'red')
'''
animation()
mainWindow.mainloop()
Try this:
from math import sqrt, sin, cos
import tkinter as tk
import time
# For more info about this read: https://stackoverflow.com/a/17985217/11106801
def _create_circle(self, x, y, r, **kwargs):
return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle
WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500
# The coefficient of restitution
# Set to 1 for perfectly elastic collitions
e = 1
class Ball:
def __init__(self, mass:float, r:float, x:float, y:float,
vx:float=0, vy:float=0, **kwargs):
"""
This is a class that defines what a ball is and how it interacts
with other balls.
Arguments:
mass:float # The mass of the ball
------------------------------------------------------
r:float # The radius of the ball, must be >0
------------------------------------------------------
x:float # The x position of the ball
# must be >0 and <WINDOW_WIDTH
y:float # The y position of the ball
# must be >0 and <WINDOW_HEIGHT
------------------------------------------------------
vx:float # The x velocity of the ball
vy:float # The y velocity of the ball
------------------------------------------------------
**kwargs # All of the args to be passed in to `create_circle`
"""
self.m = mass
self.r = r
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.kwargs = kwargs
def display(self, canvas:tk.Canvas) -> int:
"""
Displays the ball on the screen and returns the canvas_id (which
is a normal python int).
"""
canvas_id = canvas.create_circle(self.x, self.y, self.r, **self.kwargs)
return canvas_id
def move(self, all_balls:list, dt:float) -> (float, float):
"""
This moves the ball according to `self.vx` and `self.vy`.
It also checks for collisions with other balls.
Arguments:
all_balls:list # A list of all balls that are in the
# simulation. It can include this ball.
---------------------------------------------------
dt:float # delta time - used to move the balls
Returns:
dx:float # How much the ball has moved in the x direction
dy:float # How much the ball has moved in the y direction
Note: This function isn't optimised in any way. If you optimise
it, it should run faster.
"""
# Check if there are any collitions:
for other, _ in all_balls:
# Skip is `ball` is the same as this ball
if id(other) == id(self):
continue
# Check if there is a collision:
distance_squared = (other.x - self.x)**2 + (other.y - self.y)**2
if distance_squared <= (other.r + self.r)**2:
# Now the fun part - calulating the resultant velocity.
# I am assuming you already know all of the reasoning
# behind the math (if you don't ask physics.stackexchange.com)
# First I will find the normal vector of the balls' radii.
# That is just the unit vector in the direction of the
# balls' radii
ball_radii_vector_x = other.x - self.x
ball_radii_vector_y = other.y - self.y
abs_ball_radii_vector = sqrt(ball_radii_vector_x**2 +\
ball_radii_vector_y**2)
nx = ball_radii_vector_x / abs_ball_radii_vector **2*2
ny = ball_radii_vector_y / abs_ball_radii_vector **2*2
# Now I will calculate the tangent
tx = -ny
ty = nx
""" Only for debug
print("n =", (nx, ny), "\t t =", (tx, ty))
print("u1 =", (self.vx, self.vy),
"\t u2 =", (other.vx, other.vy))
#"""
# Now I will split the balls' velocity vectors to the sum
# of 2 vectors parallel to n and t
# self_velocity = λ*n + μ*t
# other_velocity = a*n + b*t
λ = (self.vx*ty - self.vy*tx) / (nx*ty - ny*tx)
# Sometimes `tx` can be 0 if so we are going to use `ty` instead
try:
μ = (self.vx - λ*nx) / tx
except ZeroDivisionError:
μ = (self.vy - λ*ny) / ty
""" Only for debug
print("λ =", λ, "\t μ =", μ)
#"""
a = (other.vx*ty - other.vy*tx) / (nx*ty - ny*tx)
# Sometimes `tx` can be 0 if so we are going to use `ty` instead
try:
b = (other.vx - a*nx) / tx
except ZeroDivisionError:
b = (other.vy - a*ny) / ty
""" Only for debug
print("a =", a, "\t b =", b)
#"""
self_u = λ
other_u = a
sum_mass_u = self.m*self_u + other.m*other_u
sum_masses = self.m + other.m
# Taken from en.wikipedia.org/wiki/Inelastic_collision
self_v = (e*other.m*(other_u-self_u) + sum_mass_u)/sum_masses
other_v = (e*self.m*(self_u-other_u) + sum_mass_u)/sum_masses
self.vx = self_v*nx + μ*tx
self.vy = self_v*ny + μ*ty
other.vx = other_v*nx + b*tx
other.vy = other_v*ny + b*ty
print("v1 =", (self.vx, self.vy),
"\t v2 =", (other.vx, other.vy))
# Move the ball
dx = self.vx * dt
dy = self.vy * dt
self.x += dx
self.y += dy
return dx, dy
class Simulator:
def __init__(self):
self.balls = [] # Contains tuples of (<Ball>, <canvas_id>)
self.root = tk.Tk()
self.root.resizable(False, False)
self.canvas = tk.Canvas(self.root, width=WINDOW_WIDTH,
height=WINDOW_HEIGHT)
self.canvas.pack()
def step(self, dt:float) -> None:
"""
Steps the simulation as id `dt` seconds have passed
"""
for ball, canvas_id in self.balls:
dx, dy = ball.move(self.balls, dt)
self.canvas.move(canvas_id, dx, dy)
def run(self, dt:float, total_time:float) -> None:
"""
This function keeps steping `dt` seconds until `total_time` has
elapsed.
Arguments:
dt:float # The time resolution in seconds
total_time:float # The number of seconds to simulate
Note: This function is just for proof of concept. It is badly written.
"""
for i in range(int(total_time//dt)):
self.step(dt)
self.root.update()
time.sleep(dt)
def add_ball(self, *args, **kwargs) -> None:
"""
Adds a ball by passing all of the args and keyword args to `Ball`.
It also displays the ball and appends it to the list of balls.
"""
ball = Ball(*args, **kwargs)
canvas_id = ball.display(self.canvas)
self.balls.append((ball, canvas_id))
app = Simulator()
app.add_ball(mass=1, r=10, x=20, y=250, vx=100, vy=0, fill="red")
app.add_ball(mass=1, r=10, x=240, y=250, vx=0, vy=0, fill="blue")
app.add_ball(mass=1, r=10, x=480, y=250, vx=0, vy=0, fill="yellow")
app.run(0.01, 10)
That code has a lot of comments describing how it works. If you still have any questions, ask me. Also the move method isn't optimised. The run method isn't great and can throw an error if it's still running and the user closes the window. I will try to find a better approch for that method. If you find any bugs, please let me know. I will try to fix them.
For the past few days I've been trying to implement the so called "Smart dots" game. I first saw it on Code Bullet youtube channel: https://www.youtube.com/watch?v=BOZfhUcNiqk. Unfortunately it was coded in Processing language, while the only language I barely know is Python. I finished my python version of the game but some bugs have appeared.
The problem is that on the second generation dots that are selected to be the best just stop moving almost instantly. I think that it has something to do with me being bad at OOP and copying the Brain class wrong. Steps(which i use for movement) Jump from zero(that is set at the beginning) to max value(200) at the first or second looping of the main loop. But the problems don't stop there. At the next generation, when i try to set brain step to zero, it just breaks with:
AttributeError: 'NoneType' object has no attribute 'brain'
I tried setting up the new brain manually but i still get the same errors. If anyone who already made this or has time to spare can help me with this error or even project i would appreciate it.
I know the code has a lot of unused things but that's just the product of me trying to fix it
:(
The commented out code is some of the old code i used.
main2.py(main loop):
import pygame
import klase2
pygame.init()
def main():
win = pygame.display.set_mode((klase2.WIN_W, klase2.WIN_H))
clock = pygame.time.Clock()
population = klase2.Population()
dots = population.return_dots(1000)
goal = klase2.Goal()
run = True
while run:
clock.tick(30)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill((255, 255, 255))
goal.draw_goal(win)
for dot in dots:
dot.draw_dot(win)
dot.update_dot()
if population.all_dots_dead():
# natural selection
population.natural_selection()
# mutation
dots = population.mutate_dots()
population.gen += 1
print(population.gen)
pygame.display.update()
main()
kase2(handles all the functions and classes):
import pygame
import numpy as np
from pygame import gfxdraw
import math
import random
pygame.init()
WIN_W = 500
WIN_H = 500
class Brain:
def __init__(self, size):
self.step = 0
self.size = size
self.directionsx = np.array(np.random.uniform(low=-2.5, high=2.5, size=int(self.size / 2)))
self.directionsy = np.array(np.random.uniform(low=-2.5, high=2.5, size=int(self.size / 2)))
def clone(self):
self.size = self.size
self.directionsx = self.directionsx
self.directionsy = self.directionsy
self.step = 0
class Goal:
def __init__(self):
self.x = WIN_W / 2
self.y = 10
self.color = (255, 20, 20)
self.r = 5
def draw_goal(self, win):
pygame.gfxdraw.aacircle(win, int(self.x), int(self.y), self.r, self.color)
pygame.gfxdraw.filled_circle(win, int(self.x), int(self.y), self.r, self.color)
class Dot:
goal = Goal()
def __init__(self):
self.tick = 0
self.goal = Goal()
self.brain = Brain(400)
self.velx = 0
self.vely = 0
self.accx = 0
self.accy = 0
self.x = WIN_W / 2
self.y = WIN_H - 10
self.r = 3
self.color = (0, 0, 0)
self.alive = True
self.velLimit = 5
self.fitness = 0
def draw_dot(self, win):
pygame.gfxdraw.aacircle(win, int(self.x), int(self.y), self.r, self.color)
pygame.gfxdraw.filled_circle(win, int(self.x), int(self.y), self.r, self.color)
def move_dot(self):
if self.brain.size / 2 > self.brain.step:
self.accx = self.brain.directionsx[self.brain.step]
self.accy = self.brain.directionsy[self.brain.step]
self.brain.step += 1
else:
self.alive = False
self.velx += self.accx
self.vely += self.accy
if self.velx > self.velLimit:
self.velx = self.velLimit
elif self.velx < -self.velLimit:
self.velx = -self.velLimit
if self.vely > self.velLimit:
self.vely = self.velLimit
elif self.vely < -self.velLimit:
self.vely = -self.velLimit
self.x += self.velx
self.y += self.vely
def update_dot(self):
if not self.reached_goal():
self.tick += 1
if self.alive:
self.move_dot()
if self.x < 0 + self.r or self.x > WIN_W - self.r or self.y < 0 + self.r or self.y > WIN_H - self.r or self.reached_goal():
self.alive = False
def distance_to_goal(self):
a = abs(self.x - self.goal.x)
b = abs(self.y - self.goal.y)
return math.sqrt(a**2 + b**2)
def reached_goal(self):
if self.distance_to_goal() <= self.r + self.goal.r:
return True
return False
def fitness_dot(self):
if self.reached_goal():
self.fitness = 1 / (self.brain.step)
else:
self.fitness = 1 / (self.distance_to_goal()**2)
return self.fitness
class Population:
def __init__(self):
self.dots = []
self.newDots = []
self.gen = 0
self.mutateChance = 800
self.size = 0
self.fitness_sum = 0
def return_dots(self, size):
self.size = size
for _ in range(size):
self.dots.append(Dot())
return self.dots
def all_dots_dead(self):
for i in range(len(self.dots)):
if self.dots[i].alive:
return False
return True
def sort_dots(self):
self.dots = sorted(self.dots, key=lambda dot: dot.fitness, reverse=True)
def sum_fitness(self):
for dot in self.dots:
self.fitness_sum += dot.fitness_dot()
return self.fitness_sum
def get_parent(self):
rand = random.uniform(0, self.fitness_sum)
running_sum = 0
for dot in self.dots:
running_sum += dot.fitness
if running_sum >= rand:
return dot
def natural_selection(self):
for dot in self.dots:
dot.fitness_dot()
self.sort_dots()
self.newDots.append(self.dots[0])
self.sum_fitness()
for i in range(1, len(self.dots)):
parent = self.get_parent()
self.newDots.append(Dot())
self.newDots[i].brain = parent.brain
self.newDots[i].brain.step = 0
self.dots = self.newDots
def mutate_dots(self):
for i in range(1, len(self.dots)):
rand = random.randint(0, 1000)
if rand > self.mutateChance:
self.dots[i].brain.directionsx = np.array(np.random.uniform(low=-2.5, high=2.5, size=int(self.dots[i].brain.size / 2)))
self.dots[i].brain.directionsy = np.array(np.random.uniform(low=-2.5, high=2.5, size=int(self.dots[i].brain.size / 2)))
return self.dots
# def natural_selection(self):
# self.selectedDots = []
# for dot in self.dots:
# dot.fitness_dot()
# self.sort_dots()
# for i in range(0, int(len(self.dots) / 3)):
# self.selectedDots.append(self.dots[i])
# self.selectedDots[i].tick = 0
# self.selectedDots[i].velx = 0
# self.selectedDots[i].vely = 0
# self.selectedDots[i].accx = 0
# self.selectedDots[i].accy = 0
# self.selectedDots[i].x = WIN_W / 2
# self.selectedDots[i].y = WIN_H - 10
# self.selectedDots[i].alive = True
# self.selectedDots[i].fitness = 0
# self.selectedDots[i].brain.step = 0
# self.selectedDots[i].goal = Goal()
#
# def new_dots(self):
# for i in range(len(self.selectedDots), len(self.dots)):
# self.selectedDots.append(Dot())
# self.dots = self.selectedDots
#
# def mutate_dots(self):
# for i, dot in enumerate(self.dots):
# isMutating = random.randint(0, 1000)
# if self.mutateChance > isMutating and i > int(len(self.dots) / 3) and i < (2 * int((len(self.dots) / 3))):
# for j in range(len(dot.brain.directionsx)):
# isMutatingDir = random.randint(0, 1000)
# if isMutatingDir >= 800:
# dot.brain.directionsx[j] = np.random.uniform(low=-2.5, high=2.5, size=1)
# for j in range(len(dot.brain.directionsy)):
# isMutatingDir = random.randint(0, 1000)
# if isMutatingDir >= 800:
# dot.brain.directionsy[j] = np.random.uniform(low=-2.5, high=2.5, size=1)
# return self.dots
'''
def natural_selection(self):
self.selectedDots = []
for dot in self.dots:
dot.fitness_dot()
self.sort_dots()
self.selectedDots = self.dots[0:int(0.3 * len(self.dots))]
def new_dots(self):
for i in range(len(self.dots) - int(0.3 * len(self.dots))):
self.selectedDots.append(self.dots[i])
self.dots = []
def mutate_dots(self):
for i, selectedDot in enumerate(self.selectedDots):
self.tick = 0
self.x = WIN_W / 2
self.y = WIN_H - 10
self.r = 3
self.alive = True
self.velLimit = 5
self.fitness = 0
self.dots = self.selectedDots
return self.dots
'''
'''
def mutate_dots(self):
for i, selectedDot in enumerate(self.selectedDots):
selectedDot.alive = True
if i >= 1:
isMutating = random.randint(0, 1000)
if isMutating <= self.mutateChance:
for j in range(len(selectedDot.brain.directionsx)):
isMutatingDir = random.randint(0, 1000)
if isMutatingDir >= 800:
selectedDot.brain.directionsx[j] = np.random.uniform(low=-2.5, high=2.5, size=1)
for j in range(len(selectedDot.brain.directionsy)):
isMutatingDir = random.randint(0, 1000)
if isMutatingDir >= 800:
selectedDot.brain.directionsy[j] = np.random.uniform(low=-2.5, high=2.5, size=1)
elif isMutating <= 800:
selectedDot.brain.directionsx = np.array(np.random.uniform(low=-2.5, high=2.5, size=200))
selectedDot.brain.directionsy = np.array(np.random.uniform(low=-2.5, high=2.5, size=200))
self.newDots.append(selectedDot)
return self.newDots
'''
The NoneType error is caused by the get_parent method. It searches for a child dot, but has no return value if the search fails (same effect as return None). This code will get past that error
def get_parent(self):
rand = random.uniform(0, self.fitness_sum)
running_sum = 0
for dot in self.dots:
running_sum += dot.fitness
if running_sum >= rand:
return dot
return self.dots[0] # search failed, return 1st dot
I don't want the code (I really do want the code), but can someone explain to me how I can create the diagonal line to see if there's a gap? I know we have to use vectors, but I don't know how to do that using python
So, using the logic of Separating Axis Theorem that if you cant draw a line in between 2 squares then they are overlapping and colliding. I made something close, its not perfect but its close, I also haven't accounted for rotation of squares but if you find a way to find the vertices/corners of the square, then this could easily work. The way i did it was that i turned the squares into lines and drew a line directly in the middle of the squares and at the normal of the line in between the squares, its a bit confusing but it makes sense once you see it. I then used line intersecting maths to find if they intersect.
import pygame
from pygame.locals import *
from pygame import Vector2
pygame.init()
screen = pygame.display.set_mode((500,500))
#check if 2 lines are intersecting
def LineIntersect(line1, line2):
#the math is from wikipedia
x1 = line1[0].x
y1 = line1[0].y
x2 = line1[1].x
y2 = line1[1].y
x3 = line2[0].x
y3 = line2[0].y
x4 = line2[1].x
y4 = line2[1].y
den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if den == 0:
return
t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den
u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den
if t > 0 and t < 1 and u > 0 and u < 1:
pt = Vector2()
pt.x = x1 + t * (x2 - x1)
pt.y = y1 + t * (y2 - y1)
return pt
return
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#get the lines that make up the square, the outline/perameter
def Lines(self):
lines = []
lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
return lines
#draw a line inbetween the 2 squares
def DrawLineInBetween():
#draw a line between the 2 squares, get gradient
#to avoid divide by zero
if abs(sqr1.x - sqr2.x) == 0:
gradient = "infinity"
else:
#rise over run
#left - right = run
left = sqr1 if sqr1.x < sqr2.x else sqr2
right = sqr1 if left == sqr2 else sqr2
gradient = ((left.y - right.y)/abs(sqr1.x - sqr2.x))
#print("gradient:",gradient)
#get the middle point between the centers of the squares
middle = (max(sqr1.x + sqr1.w//2, sqr2.x + sqr2.w//2) - abs(sqr1.x - sqr2.x)//2,
max(sqr1.y + sqr1.w//2, sqr2.y + sqr2.w//2) - abs(sqr1.y - sqr2.y)//2)
#to avoid divide by 0
if gradient == 0:
point1 = Vector2(middle[0], middle[1] + 100)
point2 = Vector2(middle[0], middle[1] - 100)
elif gradient == "infinity":
point1 = Vector2(middle[0] - 100, middle[1])
point2 = Vector2(middle[0] + 100, middle[1])
else:
#get normal of line
gradient = -1/gradient
#print("normal:",gradient)
point1 = Vector2(middle[0] + 100, middle[1] + int(-100 * gradient))
point2 = Vector2(middle[0] - 100, middle[1] + int(100 * gradient))
#print(point1)
#print(point2)
#print(middle)
pygame.draw.line(screen,(0,255,0),point1,point2,1)
line = (point1, point2)
return line
sqr1 = square(100,100,50)
sqr2 = square(200,100,50)
Clock = pygame.time.Clock()
running = True
key = ""
while running:
screen.fill((0,0,0))
sqr1.Draw(outline=True)
sqr2.Draw()
line = DrawLineInBetween()
for sqr_line in sqr1.Lines():
pt = LineIntersect(line,sqr_line)
if pt:
pygame.draw.circle(screen,(0,255,255),(int(pt.x),int(pt.y)),5)
if key == "s":
sqr1.y += 1
elif key == "w":
sqr1.y -= 1
if key == "d":
sqr1.x += 1
if key == "a":
sqr1.x -= 1
pygame.display.update()
Clock.tick(60)
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit()
running = False
if e.type == MOUSEBUTTONDOWN:
print(e.pos)
if e.type == KEYDOWN:
key = e.unicode
if e.type == KEYUP:
key = ""
doing rotating squares:
added rotation variable in square class, i used this answer to find the corners of the square, then once i have the corners, used the line intersetion.
Here is new class:
#class for sqaure
class square:
def __init__(self,x,y,w):
self.x = x
self.y = y
self.w = w
self.centerx = self.x + w//2
self.centery = self.y + w//2
self.col = (255,0,0)
self.rotation_angle = 0
def Draw(self, outline = False):
if outline:
self.Outline()
else:
pygame.draw.rect(screen,self.col,(self.x,self.y,self.w,self.w))
#this used the normal coordinate of an unrotated square to find new coordinates of rotated sqaure
def GetCorner(self,tempX,tempY):
angle = math.radians(self.rotation_angle)
rotatedX = tempX*math.cos(angle) - tempY*math.sin(angle);
rotatedY = tempX*math.sin(angle) + tempY*math.cos(angle);
x = rotatedX + self.centerx;
y = rotatedY + self.centery;
return Vector2(x,y)
def Outline(self):
for point1, point2 in self.Lines():
pygame.draw.line(screen,sqr2.col,point1,point2,1)
#new lines method, only changed to GetCorner()
def Lines(self):
lines = []
top_left = self.GetCorner(self.x - self.centerx, self.y - self.centery)
top_right = self.GetCorner(self.x + self.w - self.centerx, self.y - self.centery)
bottom_left = self.GetCorner(self.x - self.centerx, self.y + self.w - self.centery)
bottom_right = self.GetCorner(self.x + self.w - self.centerx, self.y + self.w - self.centery)
lines.append((top_left,top_right))
lines.append((top_left,bottom_left))
lines.append((bottom_right,top_right))
lines.append((bottom_right,bottom_left))
return lines
#chnaged to this as rotation rotates around center, so need to update both x and centerx
def Move(self,x =None, y = None):
if x:
self.x += x
self.centerx += x
if y:
self.y += y
self.centery += y
#get the lines that make up the square, the outline/perameter
#def Lines(self):
#lines = []
#lines.append((Vector2(self.x,self.y),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x,self.y),Vector2(self.x,self.y + self.w)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x+self.w,self.y)))
#lines.append((Vector2(self.x + self.w,self.y + self.w),Vector2(self.x,self.y + self.w)))
#return lines
I did a packman game using Python but Im having 2 problems. First my score is not updating, so the score remains 1. Second after a while my packman game crashes it, it shows me 2 error message saying an attribute error: in packman.handle_collide() and self.check_collide().I dnt knw how to fix it.
here is my code:
# Create a SNAPMAN
from livewires import games, color
import random
games.init(screen_width = 200, screen_height = 150, fps = 50)
explosion_files = ["explosion1.bmp",
"explosion2.bmp",
"explosion3.bmp",
"explosion4.bmp",
"explosion5.bmp",
"explosion6.bmp",
"explosion7.bmp",
"explosion8.bmp",
"explosion9.bmp"]
def display_score(score):
display_score = games.Text(value = score + 1, size = 25, color = color.black,
top = 5, right = games.screen.width - 10)
games.screen.add(display_score)
#increase the score
score = score + 1
class Packman(games.Sprite):
"""Create a Gumpy that is controlled by the mouse"""
image = games.load_image("packman.png")
def __init__(self, x = games.mouse.x, y = games.mouse.y):
"""Initialise packman"""
super(Packman,self).__init__(image = Packman.image,
x = games.mouse.x,
y = games.mouse.y)
oldx = games.mouse.x
oldy = games.mouse.y
def display_score(score):
display_score = games.Text(value = score + 1, size = 25, color = color.black,
top = 5, right = games.screen.width - 10)
games.screen.add(display_score)
#increase the score
score = score + 1
def update(self):
"""Move Packman's x and y coordinates"""
oldx = self.x
oldy = self.y
self.x = games.mouse.x
self.y = games.mouse.y
if(self.x < oldx):
self.angle = 180
if(self.x > oldx):
self.angle = 0
self.check_collide()
def check_collide(self):
"""Check if collides with ball"""
for packman in self.overlapping_sprites:
packman.handle_collide()
class Ball(games.Sprite):
""" Create the moving balls"""
def update(self):
"""Change the direction when the ball reached the edge"""
if self.right > games.screen.width or self.left < 0:
self.dx = -self.dx
if self.bottom > games.screen.height or self.top < 0:
self.dy = -self.dy
def handle_collide(self):
"""Something must happen when the ball collides"""
#Explosive sound
sound = games.load_sound("explosion.wav")
sound.play()
explosion = games.Animation(images = explosion_files, x = self.x, y = self.y,
n_repeats = 5, repeat_interval = 5,
is_collideable = False)
games.screen.add(explosion)
self.x = random.randrange(games.screen.width)
self.y = random.randrange(games.screen.height)
display_score(1)
def randomX():
"""Generate random x values"""
rx = random.randrange(games.screen.width)
return rx
def ramdomY():
"""Generate random y values"""
ry = random.randrange(games.screen.width)
return ry
def main():
#Set background
wall_image = games.load_image("wall.jpg", transparent = False)
games.screen.background = wall_image
games.mouse.is_visible = False
games.screen.event_grab = True
#Display score
display_score(0)
#Load and display the balls( red, green and blue)
ball_image = games.load_image("ball_red.png")
ball_red = Ball(image = ball_image,
x = random.randrange(games.screen.width),
y = random.randrange(games.screen.height),
dx = 1, dy = 1)
games.screen.add(ball_red)
ball_image = games.load_image("ball_green.png")
ball_green = Ball(image = ball_image,
x = random.randrange(games.screen.width),
y = random.randrange(games.screen.height),
dx = 1, dy = 1)
games.screen.add(ball_green)
ball_image = games.load_image("ball_blue.png")
ball_blue = Ball(image = ball_image,
x = random.randrange(games.screen.width),
y = random.randrange(games.screen.height),
dx = 1, dy = 1)
games.screen.add(ball_blue)
packman_1 = Packman()
games.screen.add(packman_1)
games.screen.mainloop()
main()
The score is not updating because you are incrementing a local variable of the method display_score. You can make it a class member of Packman, for example:
def __init__(self, x = games.mouse.x, y = games.mouse.y):
"""Initialise packman"""
# do something ...
# initialize the score variable
self._score = 0
And then,
def display_score(self):
display_score = games.Text(value = self._score, size = 25,
color = color.black, top = 5, right = games.screen.width - 10)
games.screen.add(display_score)
#increase the score
self._score += 1 # add one to the current value
You missed the self argument in the definition of the method!
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
I have a compiler error “not defined” although there is a definition
from gasp import *
GRID_SIZE = 30
MARGIN = GRID_SIZE
BACKGROUND_COLOR = color.BLACK # Colors we use
WALL_COLOR = (0.6 * 255, 0.9 * 255, 0.9 * 255)
# The shape of the maze. Each character
# represents a different type of object
# % - Wall
# . - Food
# o - Capsule
# G - Ghost
# P - Chomp
# Other characters are ignored
the_layout = [
"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%",
"%.....%.................%.....%",
"%o%%%.%.%%%.%%%%%%%.%%%.%.%%%o%",
"%.%.....%......%......%.....%.%",
"%...%%%.%.%%%%.%.%%%%.%.%%%...%",
"%%%.%...%.%.........%.%...%.%%%",
"%...%.%%%.%.%%% %%%.%.%%%.%...%",
"%.%%%.......%GG GG%.......%%%.%",
"%...%.%%%.%.%%%%%%%.%.%%%.%...%",
"%%%.%...%.%.........%.%...%.%%%",
"%...%%%.%.%%%%.%.%%%%.%.%%%...%",
"%.%.....%......%......%.....%.%",
"%o%%%.%.%%%.%%%%%%%.%%%.%.%%%o%",
"%.....%........P........%.....%",
"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"]
class Immovable:
def is_a_wall(self):
return False
class Nothing(Immovable):
pass
class Maze:
def __init__(self):
self.have_window = False
self.game_over = False
self.set_layout(the_layout)
set_speed(20)
def set_layout(self, layout):
height = len(layout)
width = len(layout[0])
self.make_window(width, height)
self.make_map(width, height)
max_y = height - 1
for x in range( width ):
for y in range(height):
char = layout[max_y - y][x]
self.make_object((x, y), char)
def make_window(self, width, height):
grid_width = (width -1) * GRID_SIZE
grid_height = (height - 1) * GRID_SIZE
screen_width = 2 * MARGIN + grid_width
screen_height = 2 * MARGIN + grid_height
begin_graphics(screen_width, screen_height,"Chomp",BACKGROUND_COLOR)
def to_screen(self, point):
(x,y) = point
x = x * GRID_SIZE + MARGIN
y = y * GRID_SIZE + MARGIN
return(x,y)
def make_map(self, width, height):
self.width = width
self.height = height
self.map = []
for y in range(width):
new_row = []
for x in range(width):
new_row.append(Nothing())
self.map.append(new_row)
def make_object(self,point,charactor):
(x,y) = point
if charactor == "%":
self.map[y][x] = Wall(self,point)
def finished(self):
return self.game_over
def play(self):
update_when('next_tick')
def done(self):
end_graphics()
self.map = []
def object_at(self,point):
(x,y) = point
if y < 0 or y >= self.height:
return Nothing()
if x < 0 or x >= self.width:
return Nothing()
return self.map[y][x]
class Wall(Immovable):
def __init__(self, maze, point):
self.place = point # Store our position
self.screen_point = maze.to_screen(point)
self.maze = maze # Keep hold of Maze
self.draw()
def draw(self):
(screen_x, screen_y) = self.screen_point
dot_size = GRID_SIZE * 0.2
Circle(self.screen_point, dot_size,
color = WALL_COLOR, filled = 1)
(x, y) = self.place
neighbors = [ (x+1, y), (x-1, y)]
for neighbor in neighbors:
self.check_neighbor(neighbor)
def check_neighbor(self,neighbor):
maze = self.maze
object = maze.object_at(neighbor)
if object.is_a_wall():
here = self.screen_point
there = maze.to_screen(neighbor)
Line(here, there, color = WALL_COLOR,thickness = 2)
def is_a_wall(self):
return True
the_maze = Maze()
while not the_maze.finished():
the_maze.play()
the_maze.done()
I got this error..
Traceback (most recent call last): File "chomp.py", line 110, in
class Wall(Immovable): File "chomp.py", line 124, in Wall
for neighbor in neighbors: NameError: name '
neighbors' is not defined
I spent lot of time still can't find what's wrong, need some help
You never close the function call to Circle() two lines about line 122, that's probably it. You're probably missing an argument based on the trailing comma.
dot_size = GRID_SIZE * 0.2
Circle(self.screen_point, dot_size, # No closing parentheses
(x, y) = self.place
neighbors = [ (x+1, y), (x-1, y)]
for neighbor in neighbors:
self.check_neighbor(neighbor)
Circle(self.screen_point, dot_size,
missing something at the end of that line