Files
2014-07-12 17:21:06 -07:00

465 lines
16 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_class, meta_accessor_class, register_action
# import utils
import logging
from copy import deepcopy
from math_utils import atan2_inv, distance, vel_add, direction
import event
import sys
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()
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()
@property
def w(self):
return self.width
@w.setter
def w(self, val):
self.width = val
@property
def h(self):
return self.height
@h.setter
def h(self, val):
self.height = val
# @classmethod
# def _register_class_(cls, class_to_register):
# ''' Registers class_to_register to its list_class '''
# cls.list_class.append(class_to_register)
@classmethod
def register_class(cls, cls_to_register):
cls.list_class.append(cls_to_register)
dispatch_events_class = classmethod(event.dispatch_events_class)
@classmethod
def for_all_instance(cls, func_to_run, flatten):
''' Note: flatten can be "level-instance" or "level-class" or False'''
return_list = []
for tmp_cls in cls.list_class:
return_item = tmp_cls.for_all_instance(
func_to_run, flatten=flatten)
if flatten == "level-instance" or flatten == "level-class":
return_list.extend(return_item)
else:
return_list.append(return_item)
return return_list
@classmethod
def for_all_class(cls, func_to_run, flatten):
''' Note: flatten can be True or False'''
return_list = []
for tmp_class in cls.list_class:
return_item = func_to_run(tmp_class)
if flatten:
return_list.extend(return_item)
else:
return_list.append(return_item)
return return_list
@staticmethod
def collect_self_clear_rect(self):
_list_clear_rect = []
_list_rendered_sprite = []
# perf hog: search for better way to remove list items iteratively
for tmp_old_sprite in self.list_rendered_sprite:
if (not tmp_old_sprite.get("still_valid")):
# returns bbox of all list_rendered_sprite that are not still_valid, after removing them from that list
# so, list now contains only of the older sprites still_valid
tmp_clear_rect_bbox = tmp_old_sprite["bbox"]
_list_clear_rect.append(tmp_clear_rect_bbox)
else:
_list_rendered_sprite.append(tmp_old_sprite)
if ("still_valid" in tmp_old_sprite):
tmp_old_sprite.pop("still_valid")
# in next step :
# _list_rendered_sprite.append(self.list_dirty_sprite)
# self.list_dirty_sprite=[]
self.list_rendered_sprite = _list_rendered_sprite
return _list_clear_rect
@staticmethod
def collect_self_rendered_sprite(self):
return self.list_rendered_sprite
@staticmethod
def collect_self_dirty_sprite(self):
return self.list_dirty_sprite
@staticmethod
def move_dirty_to_rendered(self):
self.list_rendered_sprite.extend(self.list_dirty_sprite)
self.list_dirty_sprite = []
return None
def blit_all_class(self):
# step 1: collect all clear rects
# i.e. remove last_rendered_rects which are not still_valid and return them
# also move to_be_rendered_sprites into that list and clear dirty list
list_clear_rect = self.for_all_instance(
self.collect_self_clear_rect, flatten="level-instance")
list_rendered_sprite = self.for_all_instance(
self.collect_self_rendered_sprite, flatten="level-instance")
list_dirty_sprite = self.for_all_instance(
self.collect_self_dirty_sprite, flatten="level-instance")
# step 2: check if any clear_rect intersects with last_rendered_rects,
# return a list of dirty overlay for those last_rendered_rects.
# also if it is contained (totally within), mark clear_rect for removal
list_dirty_overlay_sprite = []
list_cancelled_clear_rect = []
for tmp_old_sprite in list_rendered_sprite:
for tmp_clear_rect in list_clear_rect:
tmp_intersect_rect = tmp_clear_rect.clip(
tmp_old_sprite["bbox"])
if tmp_intersect_rect.w > 0 and tmp_intersect_rect.h > 0:
# make blit funcs capable of taking a clip rect, will be
# appplied before blitting
copy_old_sprite = deepcopy(tmp_old_sprite)
copy_old_sprite["clip_rect"] = tmp_intersect_rect
list_dirty_overlay_sprite.append(copy_old_sprite)
if tmp_old_sprite["bbox"].contains(tmp_clear_rect):
# Cancel clear rect
list_cancelled_clear_rect.append(tmp_clear_rect)
else:
# No explicit blit
pass
# step 3: for every clear_rect not marked for removal, do the
# clear_rects.
# print "Clear_rects= ",len(list_clear_rect)-len(list_cancelled_clear_rect)
# i = 0
for tmp_clear_rect in list_clear_rect:
# convert to pygame rect
if not (tmp_clear_rect in list_cancelled_clear_rect):
self.screen.fill(pygame.Color(0, 0, 0, 1), tmp_clear_rect)
# i += 1
# print "i=", i
# step 4: do blit for all dirty_sprite and dirty_overlay
for tmp_dirty_sprite in list_dirty_sprite:
tmp_dirty_sprite["sprite"].blit(self.screen)
# print "Dirty_overlays= ",len(list_dirty_overlay_sprite)
# print list_dirty_overlay_sprite
for tmp_overlay_sprite in list_dirty_overlay_sprite:
new_overlay_sprite = tmp_overlay_sprite[
"sprite"].__class__(**tmp_overlay_sprite["config"])
new_overlay_sprite.blit(
self.screen, clip_rect=tmp_overlay_sprite["clip_rect"])
# step 5: update display
list_display_update = [tmp_dirty_sprite["bbox"]
for tmp_dirty_sprite in list_dirty_sprite] + list_clear_rect
pygame.display.update(list_display_update)
# if not (list_dirty_sprite[0]["sprite"].bbox in list_display_update):
# print("problem")
# step 6: cleaning jobs
self.for_all_instance(self.move_dirty_to_rendered, flatten=False)
# pygame.display.flip()
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])
frame_no = 0
while True:
# Todo: Always exit on event_close : make it default behavior,
# although override-able
frame_no += 1
# print frame_no
event.collect_events()
self.dispatch_events_class()
self.blit_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)
# 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
# print(fun_Game)
meta_fun_Class = meta_accessor_class
@register_class(fun_Game)
class fun_Class(object):
# think about this: priority_order and depth
# by default, depends on "when class was defined"
# cls.priority_order = cls.depth = len(fun_Game.list_class)
__metaclass__ = meta_fun_Class
# Below lines moved to meta_fun_Class
# list_instance = uniquelist()
# list_check_func = uniquelist()
# dict_action_func -> dict of action funcs, key=event_numb like KEYDOWN
# dict_action_func = {}
def __init__(self, x=None, y=None):
# use sprite as a general term, img->one kind of sprite, rect, circle
self.x_start = x
self.y_start = y
self.center = [x, y]
self.x_prev = x
self.y_prev = y
self.vel_x, self.vel_y, self.accln_x, self.accln_y = 0, 0, 0, 0
if (hasattr(self, "sprite_defaults") and self.sprite_defaults.get("type")):
if (x is None) or (y is None):
raise AttributeError(
"fun_Class with sprite_defaults must have x,y")
self.sprite = self.sprite_defaults.get("type").from_container_obj(
container_obj=self, **self.sprite_defaults["params"])
self.list_dirty_sprite = []
# list_rendered_sprite => sprites that are effectively on the
# screen now
self.list_rendered_sprite = []
else:
self.sprite = None
self.list_instance.append(self)
# think about self.action_create()
self.push_event_local(event.Event("Instance_create"))
@property
def center(self):
return self._center
@center.setter
def center(self, val):
if not hasattr(self, "_center"):
self._center = [None, None]
self._center[0] = val[0]
self._center[1] = val[1]
@property
def x(self):
return self.center[0]
@x.setter
def x(self, val):
self.center[0] = val
@property
def y(self):
return self.center[1]
@y.setter
def y(self, val):
self.center[1] = val
@property
def vel(self):
return math.hypot(self.vel_x, self.vel_y)
@vel.setter
def vel(self, val):
self.vel_x = val * math.cos(math.radians(self.vel_direction))
self.vel_y = -val * math.sin(math.radians(self.vel_direction))
@property
def vel_direction(self):
return atan2_inv(self.vel_y, self.vel_x)
@vel_direction.setter
def vel_direction(self, val):
self.vel_x = self.vel * math.cos(math.radians(val))
self.vel_y = -self.vel * math.sin(math.radians(val))
@property
def accln(self):
return math.sqrt(self.accln_x ** 2 + self.accln_y ** 2)
@accln.setter
def accln(self, val):
self.accln_x = val * math.cos(math.radians(self.accln_direction))
self.accln_y = -val * math.sin(math.radians(self.accln_direction))
@property
def accln_direction(self):
return atan2_inv(self.accln_y, self.accln_x)
@accln_direction.setter
def accln_direction(self, val):
self.accln_x = self.accln * math.cos(math.radians(val))
self.accln_y = -self.accln * math.sin(math.radians(val))
def distance(self, x, y):
point_ = (x, y)
return distance(self.center, point_)
def vel_add(self, vel_mag, vel_direction):
''' vel_mag -> Magnitude of vel.
vel_direction -> direction in degrees.
Adds to current velocity and also returns new (vel_mag,vel_dir).'''
new_vel_mag, new_vel_dir = vel_add(
(self.vel, self.vel_direction), (vel_mag, vel_direction))
self.vel_set(new_vel_mag, new_vel_dir)
return self
def vel_set(self, vel_mag, vel_dir):
self.vel_x = vel_mag * math.cos(math.radians(vel_dir))
self.vel_y = -vel_mag * math.sin(math.radians(vel_dir))
return self
def direction(self, x, y):
point_ = (x, y)
return direction(self.center, point_)
@property
def list_event_local(self):
if not hasattr(self, "_list_event_local"):
self._list_event_local = []
return self._list_event_local
@list_event_local.setter
def list_event_local(self, value):
self._list_event_local = value
dispatch_events_instance = classmethod(event.dispatch_events_instance)
dispatch_events_self = event.dispatch_events_self
push_event_local = event.push_event_local
_push_event_local_single = event._push_event_local_single
dispatch_events_local = event.dispatch_events_local
clear_event_local = event.clear_event_local
@classmethod
def register_action(cls, event_name=None, func_to_register=None):
global register_action
register_action(event_name, func_to_register, cls)
@classmethod
def for_all_instance(cls, func_to_run, flatten):
return_list = []
for tmp_instance in cls.list_instance:
return_item = func_to_run(tmp_instance)
if flatten == "level-instance":
return_list.extend(return_item)
else:
return_list.append(return_item)
return return_list
def collide(self, *args, **kwargs):
return self.sprite.collide(*args, **kwargs)
# always use selfie,not selfie_depreceated most compatible
# make this classmethod,selfie-ridden register_actions as test
# @register_action()
def action_end_step_default(self, ev):
if not(self.x is None) and not(self.y is None):
# Set x_prev, y_prev
self.x_prev = self.x
self.y_prev = self.y
# Accelerate velocity
self.vel_x += self.accln_x
self.vel_y += self.accln_y
# Move x and y
self.x += self.vel_x
self.y += self.vel_y
def action_begin_step(self, ev):
pass
def action_draw_default(self, ev):
# print "Drawing"
self.sprite.draw()
def action_quit(self, ev):
# should be made better / overloaded
# IMP: will crash unless weakrefs are used
# pygame.quit()
sys.exit()
# end of fun_Class
# Other classes
# Todo: see below
# Collision helper event_collision funcs (Done, for circle)
# Allow collision with "all"
# More collision func like GM
# More extended draw funcs
# Make img sprite
# Use rects retuned from blit to display.update
#
# 1. (Think) 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
# use weakrefs as much as possible
# 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