Skip to content
This repository has been archived by the owner on Aug 24, 2018. It is now read-only.

Mykeal Kenny #87

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions INIT.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
INIT.txt
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ pygame = "*"
[dev-packages]

[requires]
python_version = "3"
python_version = "3.6"
147 changes: 108 additions & 39 deletions src/ball.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

from pygame.math import Vector2


class Ball:
"""
base class for bouncing objects
"""

def __init__(self, bounds, position, velocity, color, radius):
self.position = position
self.velocity = velocity
Expand All @@ -15,48 +17,115 @@ def __init__(self, bounds, position, velocity, color, radius):

def update(self):
# bounce at edges. TODO: Fix sticky edges
if self.position.x < 0 + self.radius or self.position.x > self.bounds[0] - self.radius: # screen width
# screen width
if (
self.position.x < 0 + self.radius
or self.position.x > self.bounds[0] - self.radius
):
self.velocity.x *= -1
if self.position.y < 0 + self.radius or self.position.y > self.bounds[1] - self.radius: # screen height
# screen height
if (
self.position.y < 0 + self.radius
or self.position.y > self.bounds[1] - self.radius
):
self.velocity.y *= -1
self.position += self.velocity

def draw(self, screen, pygame):
# cast x and y to int for drawing
pygame.draw.circle(screen, self.color, [int(self.position.x), int(self.position.y)], self.radius)

# class BouncingBall(???):
# """
# ball effected by gravity
# """
# # TODO:

# class RainbowBall(???):
# """
# Ball that changes colors
# """
# # TODO:

# class BouncingRainbow(???):
# """
# Ball that changes color and is affected by gravity
# """
# # TODO:

# class KineticBall(???):
# """
# A ball that collides with other collidable balls using simple elastic circle collision
# """
# # TODO:

# class KineticBouncing(???):
# """
# A ball that collides with other collidable balls using simple elastic circle collision
# And is affected by gravity
# """


# class AllTheThings(???):
# """
# A ball that does everything!
# """
pygame.draw.circle(
screen,
self.color,
[int(self.position.x), int(self.position.y)],
self.radius,
)


class BouncingBall(Ball):
"""
ball effected by gravity
"""

GRAVITY = .1

def update(self):
# add gravity then call normal updates
self.velocity.y += self.GRAVITY
super().update()


class RainbowBall(Ball):
def update(self):
r = (self.color[0] + 1) % 256
g = (self.color[1] + 10) % 256
b = (self.color[2] - 4) % 256
self.color = [r, g, b]
super().update()


class BouncingRainbow(BouncingBall, RainbowBall):
pass


class KineticBall(Ball):
"""
A ball that collides with other collidable balls using simple elastic circle collision
"""

def __init__(self, mass, object_list, bounds, position, velocity, color, radius):
self.object_list = object_list
self.mass = mass
super().__init__(bounds, position, velocity, color, radius)

def collide(self, object, relative_vector):
# print('bang!')

# We can imagine the point of reflection as a wall tangent to the collision
tangent = math.atan2(relative_vector.y, relative_vector.x)
# Get the angle of travel for both
angle1 = 0.5 * math.pi - math.atan2(self.velocity.y, self.velocity.x)
angle2 = 0.5 * math.pi - math.atan2(object.velocity.y, object.velocity.x)
# The angles of travel are updated to be two times the tangent minus the current angle
angle1 = 2 * tangent - angle1
angle2 = 2 * tangent - angle2

# Exchange speed
# Get velocity of other particle
object_speed = object.velocity.length()
self_speed = self.velocity.length()

# Update with new angle and opposing particle's speed
self.velocity = Vector2(math.sin(angle1), math.cos(angle1)) * object_speed
object.velocity = Vector2(math.sin(angle2), math.cos(angle2)) * self_speed

# Help sticky problem
# TODO: This is not efficient or accurate, we should calculate the correct distance and move there exactly
angle = 0.5 * math.pi + tangent
while relative_vector.length() <= self.radius + object.radius:
self.position.x += math.sin(angle)
self.position.y -= math.cos(angle)
object.position.x -= math.sin(angle)
object.position.y += math.cos(angle)
relative_vector = self.position - object.position

def update(self):
# Warning!: This is a primitive method of collision detection
# Consider time complexity when adding more of this type
index = self.object_list.index(self)
for object in self.object_list[index + 1 :]: # TODO: Check effeciency
# Don't collide with non kinetic (a class is also a subclass of itself)
if issubclass(type(object), KineticBall) and object != self:
relative_vector = self.position - object.position
if relative_vector.length() <= self.radius + object.radius:
# Objects are in collision range, so collide
self.collide(object, relative_vector)

super().update()


class KineticBouncing(BouncingBall, KineticBall):
pass


class AllTheThings(BouncingBall, KineticBall, RainbowBall):
pass
56 changes: 52 additions & 4 deletions src/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pygame.math import Vector2
from pygame import Rect


class Block:
"""
Base class for square or rectangular object
Expand All @@ -22,10 +23,57 @@ def update(self):

def set_rectangle(self, position, width, height):
# Creates a rectangle of the given width and height centered at the x/y coordinates
return pygame.Rect(position.x - (width/2),
position.y - (height/2),
width,
height)
return pygame.Rect(
position.x - (width / 2), position.y - (height / 2), width, height
)

def draw(self, screen, pygame):
pygame.draw.rect(screen, self.color, self.rectangle)


class AlsoABlock(Block):
# This class inherits from Block and has no other code, so it is identical!
pass


class RainbowBlock(Block):
# This class overrides the update method to add new functionality.
def update(self):
r = (self.color[0] + 3) % 256
g = (self.color[1] + 2) % 256
b = (self.color[2] - 1) % 256
self.color = [r, g, b]
super().update()


class PulsingBlock(Block):
# This class overrides the init function to add new variables, before calling the parent
# init function. It uses the extra variables in an override of update to add new
# functionality.
# This rectangle increases in size by pulse_rate, until it reaches pulse_size, then
# it contracts to the original size
def __init__(self, pulse_size, pulse_rate, bounds, position, width, height, color):
self.base_width = width
self.base_height = height
self.width = width
self.height = height
self.pulse_size = pulse_size
self.pulse_rate = pulse_rate
self.is_expanding = True
super().__init__(bounds, position, width, height, color)

def update(self):
self.width += self.pulse_rate
self.height += self.pulse_rate
self.rectangle = self.set_rectangle(self.position, self.width, self.height)
if (
self.width >= self.base_width + self.pulse_size
or self.width <= self.base_width
):
self.pulse_rate *= -1
super().update()


class PulsingRainbow(RainbowBlock, PulsingBlock):
# Multiple inheritance
pass
105 changes: 80 additions & 25 deletions src/draw.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,115 @@
import pygame #TODO: Fix intellisense
import pygame
import random

from pygame.math import Vector2

from ball import *
from block import *

SCREEN_SIZE = [640, 480]
BACKGROUND_COLOR = [255, 255, 255]
BACKGROUND_COLOR = [20, 20, 20]

def debug_create_balls(object_list):

def debug_create_objects(object_list):
ball = Ball(SCREEN_SIZE, Vector2(50, 50), Vector2(3, 3), [255, 0, 0], 10)
object_list.append(ball)

# TODO: Create other ball types for testing

def debug_create_blocks(object_list):
block = Block(SCREEN_SIZE, Vector2(100,100), 20, 20, [0,255,0])
object_list.extend((block, ))

bouncing_ball = BouncingBall(
SCREEN_SIZE, Vector2(100, 100), Vector2(3, 0), [0, 0, 255], 8
)
object_list.append(bouncing_ball)

rainbow_ball = RainbowBall(
SCREEN_SIZE, Vector2(400, 300), Vector2(3, -2), [0, 255, 0], 8
)
object_list.append(rainbow_ball)

bouncing_rainbow_ball = BouncingRainbow(
SCREEN_SIZE, Vector2(300, 150), Vector2(-4, 0), [0, 255, 0], 8
)
object_list.append(bouncing_rainbow_ball)

for i in range(0, 2):
kinetic = KineticBall(
1,
object_list,
SCREEN_SIZE,
Vector2(
random.randint(20, SCREEN_SIZE[0] - 20),
random.randint(20, SCREEN_SIZE[1] - 20),
),
Vector2(4 * random.random() - 2, 4 * random.random() - 2),
[255, 10, 0],
20,
)
object_list.append(kinetic)

for i in range(0, 10):
kinetic = KineticBouncing(
1,
object_list,
SCREEN_SIZE,
Vector2(
random.randint(20, SCREEN_SIZE[0] - 20),
random.randint(20, SCREEN_SIZE[1] - 20),
),
Vector2(4 * random.random() - 2, 4 * random.random() - 2),
[10, 255, 0],
20,
)
object_list.append(kinetic)

for i in range(0, 10):
kinetic = AllTheThings(
1,
object_list,
SCREEN_SIZE,
Vector2(
random.randint(20, SCREEN_SIZE[0] - 20),
random.randint(20, SCREEN_SIZE[1] - 20),
),
Vector2(4 * random.random() - 2, 4 * random.random() - 2),
[random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)],
random.randint(3, 20),
)
object_list.append(kinetic)


def main():
pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

object_list = [] # list of objects of all types in the toy

debug_create_balls(object_list)
debug_create_blocks(object_list)

while True: # TODO: Create more elegant condition for loop
object_list = [] # list of objects of all types in the toy

debug_create_objects(object_list)

while True: # TODO: Create more elegant condition for loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
# Logic Loop
sys.exit()
# Logic Updates
for event in pygame.event.get():
if event.type == pygame.KEYDOWN: #TODO: Get working
if event.type == pygame.KEYDOWN: # TODO: Get working
if event.key == pygame.K_SPACE:
# TODO: Add behavior when button pressed
pass

for ball in object_list:
ball.update()
# Draw Loop

# Draw Updates
screen.fill(BACKGROUND_COLOR)
for ball in object_list:
ball.draw(screen, pygame)

clock.tick(60)
pygame.display.flip()

# Close everything down
pygame.quit()



if __name__ == "__main__":
main()