Files
pyFun/base_old_0.py
2014-07-07 18:18:56 -07:00

1006 lines
40 KiB
Python

# only for python 2.x
# VERY IMP: Use weakrefs instead of list for fun_Game.list_class,\
# fun_Class.list_instance and fun_Class.list_update_func
from uniquelist import uniquelist # imports uniquelist
import pygame
from pygame.locals import * # imports the constants
import math
from utils import register, KW_EVENTS, fuzzy_match_event_name
from functools import partial
import logging
logging.basicConfig(level=logging.DEBUG)
IS_TEST_MODE = True
# Mouse constants
B_LEFT_CLICK = 1
B_RIGHT_CLICK = 3
B_MIDDLE_CLICK = 2
B_SCROLLUP = 4
B_SCROLLDOWN = 5
# Direction constants
TOP = 1
LEFT = 2
BOTTOM = 3
RIGHT = 4
class fun_Game(object):
list_class = uniquelist()
list_event = [] # event_list of this step
list_event_old = [] # event_list of the previous step
# A sequence of boolean representing the state of every key
list_state_all_buttons = []
# A sequence of boolean representing the state of every key of previous
# step
list_state_all_buttons_old = []
def __init__(self, width, height, fps=30):
pygame.init()
self.fps = fps
self.clock = pygame.time.Clock()
self.width = width
self.height = height
self.test_init()
def update_all_class(self):
for temp_cls in fun_Game.list_class:
for temp_count in temp_cls.list_instance:
temp_count.update()
def _cache_event_list(self):
''' Cache event_list in every step.
All events in fun_Game.list_event have a "type" property which are the usual constants. '''
fun_Game.list_event_old = fun_Game.list_event # move current list to old list
fun_Game.list_state_all_buttons_old = fun_Game.list_state_all_buttons
fun_Game.list_event = pygame.event.get()
# Returns a sequence of boolean representing the state of every key.
# Use key constant values for indexing.
fun_Game.list_state_all_buttons = pygame.key.get_pressed()
# print fun_Game.list_event # debug message
# control access to list_event
# with getter, do something crazy for filtering
# or a filtering function
def test_init(self):
''' Empty function to be overloaded for extra init on test mode. '''
pass
def test_update(self):
''' Empty function to be overloaded for extra draw on test mode. '''
pass
def run(self):
self.screen = pygame.display.set_mode([self.width, self.height])
while True:
# Todo: Add other update-related functions, there should be separate render_all_class()
# Todo: Always exit on event_close : make it default behavior,
# although override-able
self._cache_event_list()
self.update_all_class()
self.test_update()
# VERY IMPORTANT: Do a spare first step so that every variable / list gets initialized well. Start instant creation and execution from second step.
# IMPORTANT: First do all fun_Game related stuff before dealing
# with game objects.
self.clock.tick(self.fps)
# Should do display.update()
pygame.display.flip() # debug statement, should use display.update
# Todo - Add crazy functions to allow other objects and instances finding
# like GM
# Todo - Easy way to take screenshot and save it as pic
# Todo: Game testing module. Lets you compare screenshots and compare values with pixelarray or surface.get_at
# Also allows accessing different config values.
# class fun_Game ends here
class meta_fun_Class(type):
def __new__(cls, name, base, clsdict):
temp_class = type.__new__(cls, name, base, clsdict)
temp_class.__class__ = type
fun_Game.list_class.append(temp_class)
# by default, priority_order and depth depends on "when class was
# defined"
cls.priority_order = cls.depth = len(fun_Game.list_class)
return temp_class
class fun_Class(object):
__metaclass__ = meta_fun_Class
list_instance = uniquelist()
# list_update_func stores the list of functions to call in the given order.
# It can be used for injecting custom user functions (remember order).
# action_draw not added as it depends on depth
list_update_func = uniquelist()
# all the check functions added here. what they do: if event_<event_name>:
# action_<event_name>
list_check_func = uniquelist()
# dict_action_func -> dict of action funcs, key=event_numb like KEYDOWN
dict_action_func = {}
def __init__(self, x, y, img=None):
self.x = x
self.y = y
self.img = img # img should be a fun_Image, but lets do that later
fun_Class.list_instance.append(self)
self.action_create()
def update(self):
'''calls all functions in list_update_func in the given order'''
tmp_func = None
for tmp_func in fun_Class.list_update_func:
tmp_func(self)
def draw(self):
# do some blitting depending on self.img
pass
@classmethod
def register_event(cls, event_name=None, func_to_register=None):
'''
@fun_Class.register_event() # @fun_Class.register_event === @fun_Class.register_event()
def event_key_down(ev):
pass
OR
@fun_Class.register_event(event_name) # event_name=KEYDOWN (or "key_down")
def whatever(ev):
pass
OR
fun_Class.register_event(event_name,func)
'''
if (event_name is None) and (func_to_register is None):
# i.e No param passed, used as decorator (1st step of ex. 1)
logging.info("Nothing passed")
return cls.register_event
elif (not(event_name is None)) and (func_to_register is None):
# i.e. one param is passed, maybe func or (str or int)
logging.info("Event Name only passed")
if isinstance(event_name, int) or isinstance(event_name, str):
# i.e. 1st parm-> str or int, is event name, acts as decorator
# (2nd ex.)
# dont hardcode func name- use metaclass
logging.info("Event Name is event_name")
return partial(cls.register_event, event_name)
elif hasattr(event_name, "__call__"):
# first param is func, actually func_to_register.
# So get event_name from func_name (2nd step of 1st ex.)
logging.info("Event Name is actually func")
func_to_register = event_name
event_name = fuzzy_match_event_name(func_to_register.func_name)
return cls.register_event(event_name, func_to_register)
else:
raise TypeError("event_name must be (str or int) or func")
else:
# Both params present
logging.info("Both param passed")
if isinstance(event_name, str):
event_name = fuzzy_match_event_name(event_name)
event_numb = KW_EVENTS[event_name]
elif isinstance(event_name, int):
event_numb = event_name
else:
raise TypeError("event_name should be str or int")
logging.info("Registering func")
if not (event_numb in cls.dict_action_func):
cls.dict_action_func[event_numb] = []
cls.dict_action_func[event_numb].append(func_to_register)
return func_to_register
@register(list_update_func)
def action_begin_step(self):
pass
@register(list_update_func)
def action_step(self):
tmp = self.event_mouse_click()
if tmp:
print tmp
print(self.mouse_get_focused())
@register(list_update_func)
def action_end_step(self):
pass
# creation and destroy event
def action_create(self):
''' Better to use this function than overloading __init__() '''
pass
def action_destroy(self):
''' Executed when the instance is destroyed '''
pass
def destroy(self):
action_destroy(self)
# checks for all standard events -- functions named like check_<event>
# what they do: if event_<event_name>: action_<event_name>
# so lets define event_<event_name>,action_<event_name>,check_<event_name>
# for each <event_name>
# keyboard handling (later move to keyboard module)
# occurs if the key was held down continously since the last step
def event_key_pressed(self, key_value): # check if it works
if get_event_list(list_to_get=fun_Game.list_state_all_buttons)[key_value] \
and get_event_list(list_to_get=fun_Game.list_state_all_buttons_old)[key_value]\
and (not get_event_list(event_types=KEYUP, further_check_variable_name="key", further_check_value=key_value))\
and (not get_event_list(event_types=KEYUP, further_check_variable_name="key", further_check_value=key_value, list_to_get=fun_Game.list_event_old)):
return True
def action_key_pressed(self, key_value):
pass
def check_key_pressed(self):
pass # Todo: think about how to do it
# occurs if the key is held at the moment (live)
def event_key_pressed_live(self, key_value): # check if it works
''' Returns True if the key (represented by key_code) is currently pressed.
Problem is that this function is live. Returns True if the key is pressed at that moment.
So, for example, two calls in two consecutive steps may return True although the key has been left in the meantime. '''
temp_all_buttons = get_event_list(
list_to_get=fun_Game.list_state_all_buttons) # Returns a sequence of boolean representing the state of every key. Use key constant values for indexing.
if temp_all_buttons[key_value]: # indexing by key_value
return True
def action_key_pressed_live(self, key_value):
pass
def check_key_pressed_live(self):
# Returns a sequence of boolean representing the state of every key.
# Use key constant values for indexing.
temp_all_buttons = get_event_list(
list_to_get=fun_Game.list_state_all_buttons)
# temp represents the key code in numbers as it is the index of
# list_state_all_buttons indirectly
for temp in range(len(temp_all_buttons)):
if temp_all_buttons[temp]:
self.action_key_pressed_live(temp) # temp is the keycode
# occurs if a key press occurs (i.e. pushed down)
# key repetations may occur as the list is cached per step and there might
# be multiple (same key)press per step.
def event_key_down(self, key=None):
if key is None:
return get_event_list(KEYDOWN)
else:
return get_event_list(KEYDOWN, "key", key)
def action_key_down(self, key):
pass
def check_key_down(self):
for temp in get_event_list(KEYDOWN):
action_key_down(self, temp.key)
# probably wierd thing to do - define all <>_key_down as equivalent to
# <>_key_press
event_key_press = event_key_down
action_key_press = action_key_down
check_key_press = check_key_down
# occurs if key is released
def event_key_up(self, key=None):
if key is None:
return get_event_list(KEYUP)
else:
return get_event_list(KEYUP, "key", key)
def action_key_up(self, key):
pass
def check_key_up(self):
for temp in get_event_list(KEYUP):
action_key_up(self, temp.key)
# Todo: Do other key and keyboard related functions (look at GM for related functions)
# Mouse handling (later move it to mouse module)
# Mouse constants (defined at top - move them to a constants module, which will be imported in the global namespace)
# mouse click
def event_mouse_click(self, button=None):
if button is None:
return get_event_list(event_types=MOUSEBUTTONDOWN)
else:
return get_event_list(event_types=MOUSEBUTTONDOWN, further_check_variable_name="button", further_check_value=button)
def action_mouse_click(self, button):
pass
def check_mouse_click(self):
list_temp = get_event_list(event_types=MOUSEBUTTONDOWN)
for temp in list_temp:
action_mouse_click(temp.button)
# mouse get pressed
def event_mouse_get_pressed(self, button=None):
''' event_mouse_get_pressed() -> tuple looking like (1,0,0) i.e. ( left_click_state, middle_click_state, right_click_state ).
Use an index which is (corresponding_constant - 1). For eg event_mouse_get_pressed()[0] returns left click state.
event_mouse_get_pressed(button) -> Returns 0 or 1 depending on pressed or not. '''
if button is None:
return pygame.mouse.get_pressed()
else:
# because the tuple returned has o-based indexing
return pygame.mouse.get_pressed()[button - 1]
# end of mouse handling
# other mouse-based functions
def mouse_get_focused(self):
return pygame.mouse.get_focused()
def mouse_set_visible(self, bool_value):
return pygame.mouse.set_visible(bool_value)
def mouse_get_rel(self):
''' Returns relative movement of the mouse since the last call to this function.
mouse_get_rel() -> (x,y) '''
return pygame.mouse.get_rel()
def mouse_set_cursor_image(self, image=None):
pass # todo: complete after the image part is done
# implement mouse_pos as variable
@property
def mouse_pos(self):
''' mouse_pos -> (x,y)
mouse_pos = [x,y] -> Sets cursor position '''
return pygame.mouse.get_pos()
@mouse_pos.setter
def mouse_pos(self, val):
''' val should be a list [x,y] '''
if not isinstance(val, list):
raise TypeError("val should be a list [x,y]")
pygame.mouse.set_pos(val)
# implement mouse_x as variable
@property
def mouse_x(self):
''' mouse_x -> x
mouse_x = some_x -> Sets cursor x position '''
return pygame.mouse.get_pos()[0] # get_pos -> (x,y)
@mouse_x.setter
def mouse_x(self, x):
pygame.mouse.set_pos([x, mouse_y])
# implement mouse_y as variable
@property
def mouse_y(self):
''' mouse_y -> y
mouse_y = some_y -> Sets cursor y position '''
return pygame.mouse.get_pos()[1] # get_pos -> (x,y)
@mouse_y.setter
def mouse_y(self, y):
pygame.mouse.set_pos([mouse_x, y])
# end of mouse-related function
# other various events
# todo: test after image part is done
def event_intersect_room_boundary(self):
''' Checks if the object (its image bounding box) is intersecting the room boundary, but not totally outside the room.
Returns direction constants like TOP, LEFT, BOTTOM, RIGHT. '''
if (self.x + self.image.bbox.width / 2) > fun_Game.room_width and not (self.x - self.image.bbox.width / 2) > fun_Game.room_width:
return RIGHT # all constants defined at global level
elif (self.y + self.image.bbox.height / 2) > fun_Game.room_height and not (self.y - self.image.bbox.height / 2) > fun_Game.room_height:
return BOTTOM
elif (self.x - self.image.bbox.width / 2) < 0 and not (self.x + self.image.bbox.width / 2) < 0:
return LEFT
elif (self.y - self.image.bbox.height / 2) < 0 and not (self.y + self.image.bbox.height / 2) < 0:
return TOP
def action_intersect_room_boundary(self):
pass
def check_intersect_room_boundary(self):
if event_intersect_room_boundary(self):
action_intersect_room_boundary(self)
def event_outside_room_boundary(self):
''' Checks if the object (its image bounding box) is totally outside room.
Returns direction constants like TOP, LEFT, BOTTOM, RIGHT. '''
if (self.x - self.image.bbox.width / 2) > fun_Game.room_width:
return RIGHT # all constants defined at global level
elif (self.y - self.image.bbox.height / 2) > fun_Game.room_height:
return BOTTOM
elif (self.x + self.image.bbox.width / 2) < 0:
return LEFT
elif (self.y + self.image.bbox.height / 2) < 0:
return TOP
# end of fun_Class
# Other classes
class Circle(object):
def __init__(self, (x, y), radius, color=None):
self.center = [x, y]
self.radius = radius
self.color = color
@property
def center_x(self):
return self.center[0]
@property
def center_y(self):
return self.center[1]
@center_x.setter
def center_x(self, new_x):
self.center[0] = new_x
@center_y.setter
def center_y(self, new_y):
self.center[1] = new_y
@property
def bbox(self):
left = self.center_x - self.radius
top = self.center_y - self.radius
width = self.radius * 2
height = self.radius * 2
return pygame.Rect(left, top, width, height)
# todo: remember about the colour. document - center is list unlike tuple
# in case of rect.
def __repr__(self):
return "{0.__class__}( center={0.center}, radius={0.radius}, color={0.color})".format(self)
def copy(self):
return Circle((self.center_x, self.center_y), self.radius, self.color)
def move(self, move_x, move_y):
return Circle((self.center_x + move_x, self.center_y + move_y), self.radius, self.color)
def move_ip(self, move_x, move_y):
self.center_x += move_x
self.center_y += move_y
def inflate(self, inflate_radius):
return Circle((self.center_x, self.center_y), self.radius + inflate_radius, self.color)
def inflate_ip(self, inflate_radius):
self.radius += inflate_radius
def is_inside(self, another_circle):
''' Checks if this circle is fully inside another_circle '''
return distance(self.center, another_circle.center) < self.radius
def is_inside_mutual(self, another_circle):
''' Checks if this circle is fully inside another_circle and also the opposite.
2x Faster than using is_inside for both of them. '''
return (distance(self.center, another_circle.center) < max(self.radius, another_circle.radius))
def collide_circle(self, another_circle):
''' Checks if this circle intersects with another_circle. '''
return (distance(self.center, another_circle.center) <= (self.radius + another_circle.radius))
def collide_point(self, (point_x, point_y)):
''' Checks if (point_x, point_y) is inside this circle. '''
return (distance(self.center, (point_x, point_y)) <= self.radius)
def collide_list_circle(self, list_circle):
''' Checks if this circle intersects with any of the circles in circle_list. '''
for circle in list_circle:
if collide_circle(self, circle):
return True
else: # else of the for loop
return False
def collide_list_all_circle(self, list_circle):
''' Checks if this circle intersects with all of the circles in circle_list. '''
for circle in list_circle:
if not collide_circle(self, circle):
return False
else: # else of the for loop
return True
def collide_rect(self, another_rect):
''' Checks if this circle collides with another_rect. '''
return not ((self.center_x + self.radius) < another_rect.left or (self.center_x - self.radius) > another_rect.right or (self.center_y + self.radius) < another_rect.top or (self.center_y - self.radius) > another_rect.bottom)
def collide_list_rect(self, list_rect):
''' Returns True if collides with any of the rect in the list. '''
for temp_rect in list_rect:
if collide_rect(self, temp_rect):
return True
else:
return False
def collide_list_all_rect(self, list_rect):
''' Returns True if collides with all the rect in the list. '''
for temp_rect in list_rect:
if not collide_rect(self, temp_rect):
return False
else:
return True
@classmethod
# create a circle with same center and radius=sqrt(w^2+h^2)
def get_circumcircle_from_rect(cls, another_rect):
import math
return cls(another_rect.center, math.sqrt(another_rect.width ** 2 + another_rect.height ** 2) / 2)
@classmethod
# radius of incircle is same as the min of width and height (as it's not a
# square)
def get_incircle_from_rect(cls, another_rect):
import math
return cls(another_rect.center, min(another_rect.width, another_rect.height))
# union -needs to be a circle *within* which the union of the bounding box of all the circles can be fit.
# warning about all the union functions: The circles returned are not
# necessarily the most optimum union circle, but it will surely contain
# the others.
def _union(self, circle_1, circle_2):
''' self.__union((new_center_x,new_center_y),new_radius) -> ([x,y],radius) which are the resultant values. '''
return self.circumcircle_from_rect(circle_1.bbox.union(circle_2.bbox))
def union(self, new_circle):
''' Returns a new circle which is the union of these two circles '''
return self._union(self, new_circle)
def union_ip(self, new_circle):
''' Turns this circle into a circle which is a union of these two circles. '''
temp_circle = self._union(self, new_circle)
self.center, self.radius = temp_circle.center, temp_circle.radius
# list_circle_params : [((x1,y1),radius_1), ((x2,y2),radius_2), ...]
def _unionall(self, circle_1, seq_circle):
''' returns ([x,y],radius) for the resultant union of all circles.
sequence_circle must be a sequence of circles. '''
return self.circumcircle_from_rect(circle_1.bbox.unionall([temp_circle.bbox for temp_circle in seq_circle]))
def unionall(self, seq_circle): # seq_circle: sequence of circles
return self._unionall(self, seq_circle)
def unionall_ip(self, seq_circle):
''' Turns this circle into a circle which is a union of this circles with all circles in seq_circle'''
temp_circle = self._unionall(self, seq_circle)
self.center, self.radius = temp_circle.center, temp_circle.radius
# draw functions
def draw(self, surface, (x, y)=(None, None), color=None, width=1, use_antialiasing=False):
''' Draws this Circle on surface. It must be of width and height greater than or equal to its bbox.
(x,y)->co-ordinates whether the center of the Circle should be drawn on the surface.
width argument doesnt work with antialiasing. width=0 makes it filled.
antialiasing may break in later versions as the backend pygame.gfxdraw is experimental.
color falls back first to Circle.color (if present) and then to blue color.'''
if (x, y) == (None, None):
(x, y) = self.center
if not color:
if self.color:
color = self.color
else:
color = pygame.Color("blue")
if not use_antialiasing:
pygame.draw.circle(surface, color, (x, y), self.radius, width)
else:
pygame.gfxdraw.aacircle(surface, x, y, self.radius, color)
def get_surface(self, surface=None, flags=0):
''' Returns a new surface with dimensions of (Circle.bbox.width,Circle.bbox.width) and other properties as surface.
This Circle can be safely drawn on it.'''
if surface:
return pygame.Surface((self.bbox.width, self.bbox.height), flags, surface)
else:
return pygame.Surface((self.bbox.width, self.bbox.height), flags)
def get_surface_drawn(self, (x, y)=(None, None), color=None, width=1, use_antialiasing=False, surface=None, flags=0):
''' Returns a surface which has the tiltedRect drwan on it. '''
temp_surf = self.get_surface(surface, flags)
self.draw(temp_surf, (x, y), color, width, use_antialiasing)
return temp_surf
# end of Circle class
# start of tiltedRect class
class tiltedRect(pygame.Rect):
# make a metaclass which wraps a lot of functions just like move is defined below.
# functions to be wrapped - move,inflate
# Think about them - clamp,clip,union,fit,contains,collide
# the metaclass can probably also change the __str__ of the class.
# About drawing - use pygame.transform.rotate to get a rotated surface to
# be drawed.
def __init__(self, (left, top), (width, height), angle, centered_coords=False, color=None):
''' Angle in degrees.
centered_coords -> whether the (left,top) is actually intended to be (center_x,center_y) or not.
I.e. if centered_coords==True the (left,top) co-ordinates are used as (center_x,center_y) co-ordinates of the tiltedRect'''
self.angle = angle
self.color = color
if centered_coords:
left = left - width / 2
top = top - height / 2
super(tiltedRect, self).__init__((left, top), (width, height))
def __copy__(self):
return tiltedRect((self.left, self.top), (self.width, self.height), self.angle)
def __repr__(self):
return "{0.__class__.__name__}((top={0.top}, left={0.left}), (width={0.width}, height={0.height}), angle={0.angle}, color={0.color})".format(self)
@classmethod
def create_from_rect(cls, another_rect, angle):
return cls((another_rect.left, another_rect.top), (another_rect.width, another_rect.height), angle)
def get_rect(self):
return pygame.Rect((left, top), (width, height))
def move(self, x, y):
return self.create_from_rect(super(tiltedRect, self).move(x, y), self.angle)
def inflate(self, x, y):
return self.create_from_rect(super(tiltedRect, self).inflate(x, y), self.angle)
def normalize(self): # not sure what this one does - ask others
return self.create_from_rect(super(tiltedRect, self).normalize(), self.angle)
def rotate(self, some_angle):
temp_return = self.__copy__()
temp_return.angle += some_angle
return temp_return
def rotate_ip(self, some_angle):
self.angle += some_angle
# generalise the parameters to accept x1,y1,x2,y2,rot_angle
def rotate_point_relative(self, x, y, theta=None):
''' x,y -> Absolute co-ords of the point to be rotated wrt to the rectangles center. self.centerx,self.centery are internally substracted from them.
theta -> angle (in degrees) by which the point is to be rotated. Default value is self.angle.
Returns absolute positions of the points after rotation. '''
if theta is None:
theta = self.angle
return self.rotate_point(x, y, self.centerx, self.centery, theta)
@classmethod
# to be allowed as rotate_point(class)
def rotate_point(cls, x, y, origin_x, origin_y, theta):
''' x,y -> Absolute co-ords of the point to be rotated. origin_x,origin_y are internally substracted from them.
(origin_x,origin_y) -> Absolute co-ords of the origin wrt to which the point will be rotated
theta -> angle (in degrees) by which the point is to be rotated.
Returns absolute positions of the points after rotation. '''
import math
x_diff = x - origin_x
y_diff = y - origin_y
# -y_diff because of the inverted nature of y co-ordinate system in pygame
y_diff = -y_diff
r = math.sqrt(x_diff ** 2 + y_diff ** 2)
initial_angle = math.atan2(y_diff, x_diff)
final_angle = initial_angle + math.radians(theta)
rotated_x_diff = r * math.cos(final_angle)
rotated_y_diff = r * math.sin(final_angle)
rotated_y_diff = -rotated_y_diff # - used for same reason as above
rotated_x = rotated_x_diff + origin_x
rotated_y = rotated_y_diff + origin_y
return (rotated_x, rotated_y)
def rotate_point_polar(self, r, initial_angle, theta=None):
''' theta and initial_angle in radians.
initial_angle ->angle r makes with horizontal.
theta -> angle to be rotated by. '''
if theta is None:
theta = self.angle
final_angle = initial_angle + theta
rotated_x = r * math.cos(final_angle)
rotated_y = r * math.sin(final_angle)
return (rotated_x, rotated_y)
@property
def bottomleft_rotated(self):
temp = self.bottomleft
return self.rotate_point_relative(temp[0], temp[1])
@property
def topleft_rotated(self):
temp = self.topleft
return self.rotate_point_relative(temp[0], temp[1])
@property
def bottomright_rotated(self):
temp = self.bottomright
return self.rotate_point_relative(temp[0], temp[1])
@property
def topright_rotated(self):
temp = self.topright
return self.rotate_point_relative(temp[0], temp[1])
@property
def midleft_rotated(self):
temp = self.midleft
return self.rotate_point_relative(temp[0], temp[1])
@property
def midright_rotated(self):
temp = self.midright
return self.rotate_point_relative(temp[0], temp[1])
@property
def midtop_rotated(self):
temp = self.midtop
return self.rotate_point_relative(temp[0], temp[1])
@property
def midbottom_rotated(self):
temp = self.midbottom
return self.rotate_point_relative(temp[0], temp[1])
# related to bounding box
@property
def bbox(self):
list_x = [self.topleft_rotated[0], self.topright_rotated[0],
self.bottomleft_rotated[0], self.bottomright_rotated[0]]
list_y = [self.topleft_rotated[1], self.topright_rotated[1],
self.bottomleft_rotated[1], self.bottomright_rotated[1]]
min_x = floor(min(list_x))
max_x = ceil(max(list_x))
min_y = floor(min(list_y))
max_y = floor(max(list_y))
return pygame.Rect((min_x, min_y), (max_x - min_x, max_y - min_y))
# corners
@property
def corners(self):
''' List of actual (rotated) corners. '''
return [self.topleft_rotated, self.topright_rotated, self.bottomright_rotated, self.bottomleft_rotated]
@property
def corners_relative(self):
return [(temp[0] - self.centerx, temp[1] - self.centery) for temp in self.corners]
# circles related to the rect
def get_circumcircle(self):
Circle.get_circumcircle_from_rect(self.bbox)
def get_incircle(self):
Circle.get_incircle_from_rect(self.bbox)
# collision events
def collide_point(self, *args):
''' collidepoint(x,y) ->
collidepoint((x,y)) -> '''
temp_len = len(args)
temp_x = None
temp_y = None
if temp_len == 2:
temp_x = args[0]
temp_y = args[1]
elif temp_len == 1:
if len(args[0]) == 2:
temp_x, temp_y = args[0]
else:
raise TypeError("Arguments are of incorrect type")
else:
raise TypeError("Arguments are of incorrect type")
if temp_x is None or temp_y is None:
raise TypeError("argument must contain two numbers")
return pygame.Rect((self.left, self.top), (self.width, self.height)).collidepoint(self.rotate_point_relative(temp_x, temp_y))
def collide_rect(self, rect):
''' Tests if it collides with rect, which should be instance of rect class. '''
# First test:whether bbox collides with rect. If not, no collision.
if not self.bbox.colliderect(rect):
return False
else:
# Todo: Else sure test: Need to do line class first. Test if any line
# of titedRect collides with any line of (instead, the whole) rect.
pass
# draw functions
def draw(self, surface, (x, y)=(None, None), color=None, width=1, use_antialiasing=False, blend=True):
''' Draws this tiltedRect on surf_to_draw_on. It must be of width and height greater or equal to its bbox.
(x,y)->co-ordinates whether the lefttop point of its bbox should be drawn on the surface.
width argument doesnt work with antialiasing. width=0 makes it filled.
blend argument works only with antialiasing. The boolean blend argument set to true will blend the shades with existing shades instead of overwriting them.
color falls back first to tiltedRect.color (if present) and then to blue color.'''
if (x, y) == (None, None):
(x, y) = (0, 0)
if not color:
if self.color:
color = self.color
else:
color = pygame.Color("blue")
iter_corners = [(temp[0] + x, temp[1] + y)
for temp in self.corners_relative]
if not use_antialiasing:
pygame.draw.polygon(surface, color, iter_corners, width)
else:
pygame.draw.aalines(surface, color, True, iter_corners, blend)
def get_surface(self, surface=None, flags=0):
''' Returns a new surface with dimensions of (tiltedRect.bbox.width,tiltedRect.bbox.width) and other properties as surface.
This tiltedRect can be safely drawn on it.'''
if surface:
return pygame.Surface((self.bbox.width, self.bbox.height), flags, surface)
else:
return pygame.Surface((self.bbox.width, self.bbox.height), flags)
def get_surface_drawn(self, (x, y)=(None, None), color=None, width=1, use_antialiasing=False, blend=True, surface=None, flags=0):
''' Returns a surface which has the tiltedRect drwan on it. '''
temp_surf = self.get_surface(surface, flags)
self.draw(temp_surf, (x, y), color, width, use_antialiasing, blend)
return temp_surf
def collide_tiltedRect(self, another_tiltedRect):
'''Checks whether this tiltedRect collides with another_titltedrect, which must be a instance of titledRect. '''
# Rotates both tiltedRect by -another_tiltedRect.angle. So, another_tiltedRect becomes a Rect called temp_Rect.
# Then check if the rotated version of this tiltedRect
# (temp_tiltedRect) collides with the obtained Rect.
temp_tiltedRect = self.rotate(-another_tiltedRect.angle)
temp_Rect = another_tiltedRect.get_rect()
return temp_tiltedRect.colliderect(temp_Rect)
# functions outside all classes
# list_to_get is attached to a constant list here
def get_event_list(event_types=None, further_check_variable_name=None, further_check_value=None, list_to_get=None):
''' Get the required events from the event_list.
get_event_list([event_types],[further_check_variable_name],further_check_value=None,list_to_get=fun_Game.list_event):
event_types (optional): event_name|list[event_name]
get_event_list() -> returns whole event_list(to be precise, list_to_get)
get_event_list(events) -> returns event_list(to be precise, list_to_get) filtered to contain only "events"
get_event_list([events],further_check_variable_name="var_name",further_check_value=value) ->
returns sub-list containing those elements of get_event_list([events]) for which var_name=value
Potential candidates for list_to_get -> fun_Game.list_event, fun_Game.list_event_old, fun_Game.list_state_all_buttons, fun_Game.list_state_all_buttons_old'''
if list_to_get is None:
list_to_get = fun_Game.list_event_old
if further_check_variable_name:
further_check_enabled = True
else:
further_check_enabled = False
if event_types:
if not hasattr(event_types, "__iter__"):
# if not iterable, make a list out of it
event_types = [event_types]
def func_filter():
list_return = []
if not event_types:
return list_to_get
# all events in fun_Game.list_event have a "type" property
for temp_1 in list_to_get:
for temp_2 in event_types:
if temp_1.type == temp_2:
list_return.append(temp_1)
return list_return
# returns part of temp_list which passes further_check
def further_check_filter(temp_list):
list_return = []
if further_check_enabled:
for temp in temp_list:
# may raise error if further_check_variable_name is not present
# for all members of the list
if getattr(temp, further_check_variable_name) == further_check_value:
list_return.append(temp)
else:
list_return = temp_list
return list_return
# returns elements common in fun_Game.list_events and events which are
# accepted after further_check
return further_check_filter(func_filter())
# end of get_event_list()
# Maybe make a non-square rooted function for comparison (faster)
def distance((x1, y1), (x2, y2)):
import math
return(math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2))
# following constants are required for the next function
UNION = 1
INTERSECTION = 2
DIFFERENCE = 3
ADD = 4
# for each member (usually objects) in the list, it is replaced by one of
# its variables
MAKE_IT_VARIABLE_LIST = 5
def operation_on_lists(operation, list1, list2=None, variable_name=None):
''' operation_on_lists(operation, list1, list2) -> list
Does operation involving list1 and list2.
variable_name is required only for MAKE_IT_VARIABLE_LIST operation. It is a string representing the variable name
Valid operations are UNION, INTERSECTION, DIFFERENCE, ADD, MAKE_IT_VARIABLE_list '''
def intersection():
if list2 is None:
raise TypeError("second argument i.e. list2 should be a list")
list_return = []
for temp1 in list1:
for temp2 in list2:
if temp1 == temp2:
list_return.append(temp1)
return list_return
def union():
if list2 is None:
raise TypeError("second argument i.e. list2 should be a list")
list_return = uniquelist()
list_return.extend(list1)
list_return.extend(list2)
return list_return
def add():
if list2 is None:
raise TypeError("second argument i.e. list2 should be a list")
list_return = []
list_return.append(list1)
list_return.append(list2)
return list_return
def difference():
if list2 is None:
raise TypeError("second argument i.e. list2 should be a list")
list_return = []
list_intersection = intersection()
for temp1 in list1:
if temp1 not in list_intersection:
list_return.append(temp1)
return list_return
# for each member (usually objects) in the list, it is replaced by one of
# its variables
def make_it_variable_list():
if variable_name is None:
raise TypeError("variable_name should be a string")
if not list2 is None:
raise TypeError("list2 should be None")
return_list = [getattr(temp, variable_name) for temp in list1]
if operation == UNION:
return union()
elif operation == INTERSECTION:
return intersection()
elif operation == DIFFERENCE:
return difference()
elif operation == ADD:
return add()
elif operation == MAKE_IT_VARIABLE_LIST:
return make_it_variable_list()
else:
raise ValueError(
"Invalid operation name. Valid operations are UNION, INTERSECTION, DIFFERENCE.")
# end of operation_on_lists()
# Todo: see below
# 1. Allow rect-like and circle-like constructs wherever rect and circle are expected.
# 2. Do all sort of type-checking.
# 3.Use sprites and groups in pygame as containers
# Imp: Allow debugging with event handlers and others with @debug
# Design tips I will use later
# IMP: always derive classes from object. Else, in python 2.x, they become old-style class which sucks.
# use tuples instead of lists wherver possible because they are usually faster to create
# learn about generators
# named tuples, arrays are better. use more of them. named tuples can be accessed.
# refactor code into proper modules as-well-as use better named functions and variables
# use enumerate() for getting the index and value of elements while looping through lists. This mistake done atleast 1 time.
# use xrange instead of range. It produces one at a time. range is replaced by xrange in python 3.x
# use reversed() for backward loops, sorted() for sorted order.
# instead of zip(), use izip().
# use key instead of comparator
# use iter(iter_type,sentinel_value) instead of for loops
# partial() makes a function with more number of argument to less number of argument
# use for/else instead of exit based on flags
# look at dict.iteritems() and dict.setdefaults
# learn defaultdicts and collections module