Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoe Lee <joe@jotaro.com>2009-08-16 16:26:53 (GMT)
committer Joe Lee <joe@jotaro.com>2009-08-19 04:11:36 (GMT)
commit4c1615a587bd9208da1f05964603cf066bc40cbc (patch)
tree6a63585e161d3d241ffe2e6f7fe3a43ba9ad7387
parentd8c65e40b2ec986e803953fe467c3f5af3173071 (diff)
Separated drawing code, refactored animation.
-rw-r--r--gridwidget.py970
-rw-r--r--implodegame.py29
2 files changed, 533 insertions, 466 deletions
diff --git a/gridwidget.py b/gridwidget.py
index 3936840..99b61f4 100644
--- a/gridwidget.py
+++ b/gridwidget.py
@@ -91,28 +91,23 @@ _KEY_MAP = {
gtk.keysyms.KP_Page_Up : 'undo',
}
-
-# Animation modes.
-ANIMATE_NONE = 0
-ANIMATE_SHRINK = 1
-ANIMATE_FALL = 2
-ANIMATE_SLIDE = 3
-ANIMATE_ZOOM = 4
-ANIMATE_WIN = 5
-
-# A list of the animation stages in order, along with time on-screen (in
-# seconds per tick).
-_ANIM_TIME_LIST = (
- (ANIMATE_SHRINK, 0.1),
- (ANIMATE_FALL, 0.1),
- (ANIMATE_SLIDE, 0.1),
- (ANIMATE_ZOOM, 0.1),
-)
-_ANIM_MODES = [x[0] for x in _ANIM_TIME_LIST]
-_ANIM_TIMES = dict(_ANIM_TIME_LIST)
-
-# Win animation time on screen (in seconds per tick).
-_WIN_ANIM_TIME = 0.04
+# Removal animation stages.
+_ANIM_STAGE_NONE = 0
+_ANIM_STAGE_SHRINK = 1
+_ANIM_STAGE_FALL = 2
+_ANIM_STAGE_SLIDE = 3
+_ANIM_STAGE_ZOOM = 4
+
+_ANIM_STAGES = [
+ _ANIM_STAGE_NONE,
+ _ANIM_STAGE_SHRINK,
+ _ANIM_STAGE_FALL,
+ _ANIM_STAGE_SLIDE,
+ _ANIM_STAGE_ZOOM,
+]
+
+# Animation time scaling factor (in seconds per tick).
+_ANIM_SCALE = 0.04
# Animation timer interval (in msec)
_TIMER_INTERVAL = 20
@@ -153,8 +148,10 @@ class GridWidget(gtk.DrawingArea):
| gtk.gdk.KEY_PRESS_MASK)
self.set_flags(gtk.CAN_FOCUS)
- self._drawer = GridDrawer(get_size_func=self._get_size,
- invalidate_rect_func=self._invalidate_rect)
+ self._board_drawer = BoardDrawer(self._get_size, self._invalidate_rect)
+ self._win_drawer = WinDrawer(self._get_size, self._invalidate_rect)
+ self._removal_drawer = RemovalDrawer(self._get_size, self._invalidate_rect)
+ self._set_current_drawer(self._board_drawer)
def _get_size(self):
return (self.allocation.width, self.allocation.height)
@@ -163,52 +160,52 @@ class GridWidget(gtk.DrawingArea):
if self.window:
self.window.invalidate_rect(rect, True)
- # NOTE: There is a lot of forwarding to the drawer here, which may benefit
- # from better separation of responsibilities.
-
def set_board(self, board):
- self._drawer.set_board(board)
-
- def set_removal_block_set(self, value):
- self._drawer.set_removal_block_set(value)
-
- def set_animation_mode(self, value):
- self._drawer.set_animation_mode(value)
-
- def set_animation_mode(self, value):
- self._drawer.set_animation_mode(value)
-
- def set_animation_percent(self, value):
- self._drawer.set_animation_percent(value)
+ self._board_drawer.set_board(board)
def set_win_draw_flag(self, value):
- self._drawer.set_win_draw_flag(value)
+ drawing_win = self.get_win_draw_flag()
+ if value != drawing_win:
+ if value:
+ self._set_current_drawer(self._win_drawer)
+ else:
+ self._set_current_drawer(self._board_drawer)
+ self._invalidate_board()
+
+ def _invalidate_board(self):
+ (width, height) = self._get_size()
+ self._invalidate_rect(gtk.gdk.Rectangle(0, 0, width, height))
def get_win_draw_flag(self):
- return self._drawer.get_win_draw_flag()
+ return (self._current_drawer is self._win_drawer)
def get_win_color(self):
- return self._drawer.get_win_color()
+ return self._win_drawer.get_win_color()
def set_win_state(self, draw_flag, win_color):
- self._drawer.set_win_state(draw_flag, win_color)
-
- def get_animation_length(self):
- return self._drawer.get_animation_length()
+ if not draw_flag:
+ self._set_current_drawer(self._board_drawer)
+ else:
+ self._set_current_drawer(self._win_drawer)
+ self._win_drawer.set_win_state(draw_flag, win_color)
def select_center_cell(self):
- self._drawer.select_center_cell()
+ self._board_drawer.select_center_cell()
@_log_errors
def do_button_press_event(self, event):
# Ignore mouse clicks while animating.
- if self._drawer.get_animation_mode() != ANIMATE_NONE:
- return
+ if self._is_animating():
+ return True
+ # Ignore double- and triple-clicks.
+ if event.type != gtk.gdk.BUTTON_PRESS:
+ return True
self.grab_focus()
- self._drawer.set_mouse_selection(event.x, event.y)
- selected_cell = self._drawer.get_selected_cell()
+ self._board_drawer.set_mouse_selection(event.x, event.y)
+ selected_cell = self._board_drawer.get_selected_cell()
if selected_cell is not None:
self.emit('piece-selected', *selected_cell)
+ return True
@_log_errors
def do_key_press_event(self, event):
@@ -217,15 +214,15 @@ class GridWidget(gtk.DrawingArea):
self.emit('new-key-pressed', 0)
return True
# Ignore key presses while animating.
- if self._drawer.get_animation_mode() != ANIMATE_NONE:
+ if self._is_animating():
return False
- if not self._drawer.board_is_valid():
- self._drawer.set_selected_cell(None)
+ if not self._board_drawer.board_is_valid():
+ self._board_drawer.set_selected_cell(None)
return False
else:
- selected_cell = self._drawer.get_selected_cell()
+ selected_cell = self._board_drawer.get_selected_cell()
if selected_cell is None:
- self._drawer.select_center_cell()
+ self._board_drawer.select_center_cell()
return True
else:
if action == 'select':
@@ -244,19 +241,22 @@ class GridWidget(gtk.DrawingArea):
'right' : ( 1, 0)}
if action in offsets:
offset = offsets[action]
- return self._drawer.move_selected_cell(*offset)
+ return self._board_drawer.move_selected_cell(*offset)
else:
return False
@_log_errors
def do_motion_notify_event(self, event):
+ # Ignore mouse motion while animating.
+ if self._is_animating():
+ return
if event.is_hint:
(x, y, state) = event.window.get_pointer()
else:
x = event.x
y = event.y
state = event.state
- self._drawer.set_mouse_selection(x, y)
+ self._board_drawer.set_mouse_selection(x, y)
@_log_errors
def do_expose_event(self, event):
@@ -266,58 +266,111 @@ class GridWidget(gtk.DrawingArea):
event.area.width,
event.area.height)
cr.clip()
+ cr.set_antialias(cairo.ANTIALIAS_NONE)
(width, height) = self.window.get_size()
- self._drawer.draw(cr, width, height)
+ self._current_drawer.draw(cr, width, height)
@_log_errors
def do_size_allocate(self, allocation):
super(GridWidget, self).do_size_allocate(self, allocation)
- self._drawer.init_board_layout(allocation.width, allocation.height)
+ self._current_drawer.resize(allocation.width, allocation.height)
+
+ def _set_current_drawer(self, drawer):
+ self._current_drawer = drawer
+ (width, height) = self._get_size()
+ self._current_drawer.resize(width, height)
+
+ def _is_animating(self):
+ return (self._current_drawer is not self._board_drawer)
+
+ def get_removal_anim(self, board, contiguous, end_anim_func):
+ self._set_current_drawer(self._removal_drawer)
+ self._removal_drawer.init(board, contiguous)
+ self._removal_drawer.set_anim_time(0.0)
+ start_time = time.time()
+
+ def update_func(start_time_ref=[start_time]):
+ delta = time.time() - start_time_ref[0]
+ length = self._removal_drawer.get_anim_length()
+ if delta > length:
+ if not self._removal_drawer.next_stage():
+ return False
+ start_time_ref[0] = time.time()
+ delta = 0.0
+ self._removal_drawer.set_anim_time(delta)
+ return True
+
+ def local_end_anim_func(anim_stopped):
+ self._set_current_drawer(self._board_drawer)
+ end_anim_func(anim_stopped)
- def start_removal_anim(self, end_anim_func, contiguous):
- anim = RemovalAnim(self, end_anim_func, contiguous)
- anim.start()
- return anim
+ return Anim(update_func, local_end_anim_func)
- def start_win_anim(self, end_anim_func):
- anim = WinAnim(self, end_anim_func)
- anim.start()
- return anim
+ def get_win_anim(self, end_anim_func):
+ self._set_current_drawer(self._win_drawer)
+ self._win_drawer.init()
+ length = self._win_drawer.get_anim_length()
+ start_time = time.time()
+
+ def update_func():
+ delta = time.time() - start_time
+ self._win_drawer.set_anim_time(min(delta, length))
+ return (delta <= length)
+
+ def local_end_anim_func(anim_stopped):
+ self._win_drawer.set_anim_time(length)
+ end_anim_func(anim_stopped)
+
+ return Anim(update_func, local_end_anim_func)
+
+
+class Anim(object):
+ """Manages an animation."""
+ def __init__(self, update_func, end_anim_func):
+ """update_func is a function returns True if the animation should
+ continue, False otherwise. end_anim_func is a function that takes a
+ boolean indicating whether the animation was stopped prematurely."""
+ self._update_func = update_func
+ self._end_anim_func = end_anim_func
+ self._animating = False
+
+ def start(self):
+ self._animating = True
+ self._update_func()
+ gobject.timeout_add(_TIMER_INTERVAL, self._timer)
+
+ def stop(self):
+ if self._animating:
+ self._end_anim(anim_stopped=True)
+
+ def _timer(self):
+ if not self._animating:
+ return False
+ if self._update_func():
+ return True
+ self._end_anim(anim_stopped=False)
+ return False
+
+ def _end_anim(self, anim_stopped):
+ self._animating = False
+ self._end_anim_func(anim_stopped=anim_stopped)
# NOTE: We separate the drawing/interaction code from the GTK widget code so
# that we can reuse the drawing in a widget that draws more on top; apparently
# GTK doesn't like overlapping widgets.
-class GridDrawer(object):
- """Object to manage drawing/animation of game board."""
+class BoardDrawer(object):
+ """Object to manage drawing of the game board."""
def __init__(self, get_size_func, invalidate_rect_func, *args, **kwargs):
- super(GridDrawer, self).__init__(*args, **kwargs)
+ super(BoardDrawer, self).__init__(*args, **kwargs)
self._board = None
self._board_width = 0
self._board_height = 0
- self._removal_block_set = set()
- self._animation_percent = 0.0
- self._animation_mode = ANIMATE_NONE
self._selected_cell = None
self._contiguous_map = {}
- # Game animation variables.
- self._animation_coords = []
- self._animation_frames = {}
- self._animation_lengths = {}
-
- # Winning animation variables.
- self._win_coords = []
- self._win_starts = []
- self._win_ends = []
- self._win_length = 0
- self._win_size = (0,0)
- self._win_transform = None
- self._win_draw_flag = False
- self._win_color = 0
-
# Drawing offset and scale.
self._board_transform = None
@@ -330,7 +383,7 @@ class GridDrawer(object):
self._recalc_board_dimensions()
self._recalc_contiguous_map()
(width, height) = self._get_size_func()
- self.init_board_layout(width, height)
+ self.resize(width, height)
if self._selected_cell is not None:
# If a cell is selected, clamp it to new board boundaries.
(x, y) = self._selected_cell
@@ -339,49 +392,6 @@ class GridDrawer(object):
self._selected_cell = (x, y)
self._invalidate_board()
- def set_removal_block_set(self, value):
- self._removal_block_set = value
- self._recalc_game_animation_frames()
-
- def set_animation_mode(self, value):
- self._animation_mode = value
- if value == ANIMATE_WIN:
- self._recalc_win_animation_frames()
- self._invalidate_board()
-
- def get_animation_mode(self):
- return self._animation_mode
-
- def set_animation_percent(self, value):
- self._animation_percent = value
- self._recalc_animation_coords()
-
- def set_win_draw_flag(self, value):
- if self._win_draw_flag != value:
- self._win_draw_flag = value
- self._invalidate_board()
-
- def get_win_draw_flag(self):
- return self._win_draw_flag
-
- def get_win_color(self):
- return self._win_color
-
- def set_win_state(self, draw_flag, win_color):
- self._win_draw_flag = draw_flag
- if draw_flag:
- self._recalc_win_animation_frames()
- self._win_color = win_color
- self._invalidate_board()
-
- def get_animation_length(self):
- if self._animation_mode == ANIMATE_NONE:
- return 0.0
- if self._animation_mode == ANIMATE_WIN:
- return self._win_length
- else:
- return self._animation_lengths[self._animation_mode]
-
def _recalc_contiguous_map(self):
self._contiguous_map = {}
if self._board is None:
@@ -433,6 +443,11 @@ class GridDrawer(object):
self._invalidate_selection(old_selection)
self._invalidate_selection(self._selected_cell)
+ def _invalidate_board(self):
+ (width, height) = self._get_size_func()
+ rect = gtk.gdk.Rectangle(0, 0, width, height)
+ self._invalidate_rect_func(rect)
+
def _invalidate_selection(self, selection_coord):
contiguous = self._contiguous_map.get(selection_coord, None)
if contiguous is not None and len(contiguous) >= 3:
@@ -461,11 +476,6 @@ class GridDrawer(object):
int(max_y2 - min_y2))
self._invalidate_rect_func(rect)
- def _invalidate_board(self):
- (width, height) = self._get_size_func()
- rect = gtk.gdk.Rectangle(0, 0, width, height)
- self._invalidate_rect_func(rect)
-
def _display_to_cell(self, x, y):
# Converts from display coordinate to a cell coordinate.
return self._board_transform.inverse_transform(x, y)
@@ -474,115 +484,169 @@ class GridDrawer(object):
# Converts from a cell coordinate to a display coordinate.
return self._board_transform.transform(x, y)
- def _recalc_win_animation_frames(self):
- r = random.Random()
- r.seed()
- (tiles, width, height) = self._get_win_tiles()
- tiles = self._reorder_win_tiles(r, tiles, width, height)
- self._win_starts = self._get_win_starts(tiles, width, height)
- self._win_ends = self._get_win_ends(tiles)
- self._win_length = self._get_win_length()
- self._win_size = (width, height)
- self._win_color = r.randint(1, 5)
- (width, height) = self._get_size_func()
- self._recalc_win_transform(width, height)
+ def resize(self, width, height):
+ if not self.board_is_valid():
+ self._board_transform = _BoardTransform()
+ else:
+ self._board_transform = _BoardTransform()
+ self._board_transform.setup(width,
+ height,
+ self._board_width,
+ self._board_height)
- def _get_win_tiles(self):
- # Returns a list of ending tile coordinates making up the smiley face,
- # as well as the width and height in tiles.
- data = [list(x.strip()) for x in _SMILEY.strip().splitlines()]
- height = len(data)
- widths = set([len(x) for x in data])
- assert len(widths) == 1
- width = widths.pop()
- assert width > 0
- assert height > 0
- tiles = []
- for i in range(height):
- for j in range(width):
- if data[i][j] == 'x':
- # Invert y axis because we use the board tile engine to
- # display, which uses cartesian coordinates instead of
- # display coordinates.
- tiles.append((j, height - i - 1))
- return (tiles, width, height)
+ def draw(self, cr, width, height):
+ # Draws the widget.
+ _draw_background(cr, width, height)
+ cr.save()
+ self._board_transform.set_up_cairo(cr)
+ self._draw_board(cr)
+ cr.restore()
- def _reorder_win_tiles(self, r, tiles, width, height):
- # Re-sorts tiles by several randomly chosen criteria.
- def radial(coord):
- (x, y) = coord
- x = float(x) / width - 0.5
- y = float(y) / height - 0.5
- return 2 * math.sqrt(x * x + y * y)
- def x(coord):
- return float(coord[0]) / width
- def y(coord):
- return float(coord[1]) / height
- def angle(coord):
- (x, y) = coord
- x = float(x) / width - 0.5
- y = float(y) / height - 0.5
- angle = math.atan2(y, x)
- return (angle / math.pi + 1) / 2
- funcs = [radial, x, y, angle]
- r.shuffle(funcs)
- invs = [r.choice((-1, 1)), r.choice((-1, 1))]
- pairs = []
- w = r.random()
- for coord in tiles:
- score = funcs[0](coord) * invs[0] + funcs[1](coord) * invs[1] * w
- pairs.append((score, coord))
- pairs.sort()
- # Re-interleave pairs, if desired.
- if r.randint(0, 1):
- index1 = int(len(pairs) / 2)
- list1 = pairs[:index1]
- list2 = pairs[index1:]
- if r.randint(0, 1):
- list2.reverse()
- pairs = _interleave(list1, list2)
- return [x[1] for x in pairs]
+ def _draw_board(self, cr):
+ # Draws the game board on the widget, where each unit corresponds to
+ # a cell on the board.
+ self._draw_blocks(cr)
+ self._draw_selected(cr)
+ self._draw_selected_dot(cr)
- def _get_win_starts(self, tiles, width, height):
- # Returns a list of starting coordinates for tiles.
- starts = []
- assert width > 0
- assert height > 0
- start_x = width / 2.0 - 0.5
- start_y = height / 2.0 - 0.5
- for (i, (x, y)) in enumerate(tiles):
- starts.append((i, start_x, start_y, 0.0))
- #starts.append((i, x, y, 0.0))
- return starts
+ def _draw_blocks(self, cr):
+ if not self.board_is_valid():
+ return
- def _get_win_ends(self, tiles):
- # Returns a list of ending coordinates for the tiles in the unit
- # square.
- ends = []
- for (i, (x, y)) in enumerate(tiles):
- ends.append((i + 8, x, y, 1.0))
- return ends
+ value_map = self._board.get_value_map()
+ for (coord, value) in value_map.items():
+ self._draw_block(cr, coord[0], coord[1], value)
+
+ def _draw_selected(self, cr):
+ # Draws a white background to selected blocks, then redraws blocks
+ # on top.
+ if (self._selected_cell is None
+ or self._selected_cell not in self._contiguous_map):
+ return
+ contiguous = self._contiguous_map[self._selected_cell]
+ value = self._board.get_value(*self._selected_cell)
+ cr.set_source_rgb(*_SELECTED_COLOR)
+ for (x, y) in contiguous:
+ self._draw_square(cr, x, y, _SELECTED_MARGIN)
+ for (x, y) in contiguous:
+ self._draw_block(cr, x, y, value)
+
+ def _draw_block(self, cr, x, y, value):
+ # Draws the block at the given grid cell.
+ assert value is not None
+ c = color.colors[value]
+ cr.set_source_rgb(*c)
+ self._draw_square(cr, x, y, -_BLOCK_GAP)
+
+ def _draw_square(self, cr, x, y, margin):
+ # Draws a square in the given grid cell with the given margin.
+ x1 = float(x) - margin
+ y1 = float(y) - margin
+ size = 1.0 + margin * 2
+ cr.rectangle(x1, y1, size, size)
+ cr.fill()
+
+ def _draw_selected_dot(self, cr):
+ if self._selected_cell is None:
+ return
+ # Draws a dot indicating the selected cell.
+ cr.set_source_rgb(*_SELECTED_COLOR)
+
+ (x, y) = self._selected_cell
+ cr.arc(x + 0.5, y + 0.5, _SELECTED_DOT_RADIUS, 0, math.pi * 2.0)
+ cr.fill()
+
+ def _recalc_board_dimensions(self):
+ if self.board_is_valid():
+ self._board_width = self._board.width
+ self._board_height = self._board.height
+ else:
+ self._board_width = 1
+ self._board_height = 1
+
+ def board_is_valid(self):
+ # Returns True if the board is set and has valid dimensions (>=1).
+ return (self._board is not None
+ and not self._board.is_empty())
- def _get_win_length(self):
- # Returns the length of the win animation based on the existing
- # values for start and end (in "ticks").
- return (len(self._win_starts) + 8)
- def _recalc_game_animation_frames(self):
+class RemovalDrawer(object):
+ """Object to manage the drawing of the animation of removing blocks."""
+
+ def __init__(self, get_size_func, invalidate_rect_func, *args, **kwargs):
+ super(RemovalDrawer, self).__init__(*args, **kwargs)
+ self._board = None
+ self._board_width = 0
+ self._board_height = 0
+ self._removal_block_set = set()
+ self._anim_time = 0.0
+ self._anim_stage = _ANIM_STAGE_SHRINK
+
+ # Game animation variables.
+ self._anim_coords = []
+ self._anim_frames = {}
+ self._anim_lengths = {}
+
+ # Drawing offset and scale.
+ self._board_transform = None
+
+ # Callback functions set by owner.
+ self._get_size_func = get_size_func
+ self._invalidate_rect_func = invalidate_rect_func
+
+ def init(self, board, removal_block_set):
+ self._board = board
+ self._recalc_board_dimensions()
+ (width, height) = self._get_size_func()
+ self.resize(width, height)
+ self._removal_block_set = removal_block_set
+ self._recalc_game_anim_frames()
+ self._anim_stage = _ANIM_STAGE_SHRINK
+ self._invalidate_board()
+
+ def next_stage(self):
+ """Sets the current animation stage; returns False if there are no
+ more stages, True otherwise."""
+ stage = self._anim_stage + 1
+ while stage < len(self._anim_lengths) and not self._anim_lengths[stage]:
+ stage += 1
+ if stage == len(self._anim_lengths):
+ return False
+ self._anim_stage = stage
+ self._invalidate_board()
+ return True
+
+ def set_anim_time(self, value):
+ """Sets the time passed for the current stage."""
+ self._anim_time = value
+ self._recalc_anim_coords()
+ self._invalidate_board()
+
+ def get_anim_length(self):
+ """Returns the length of the current stage in seconds."""
+ return self._anim_lengths[self._anim_stage]
+
+ def _invalidate_board(self):
+ (width, height) = self._get_size_func()
+ rect = gtk.gdk.Rectangle(0, 0, width, height)
+ self._invalidate_rect_func(rect)
+
+ def _recalc_game_anim_frames(self):
if not self.board_is_valid():
- self._animation_frames = {}
+ self._anim_frames = {}
+ self._anim_lengths = {}
return
frames = {}
- value_map = self._board.get_value_map()
lengths = {}
# Calculate starting coords.
starting_frame = []
+ value_map = self._board.get_value_map()
for ((i, j), value) in value_map.items():
starting_frame.append((i, j, 1.0, value))
- frames[ANIMATE_NONE] = (self._board_transform, starting_frame)
- lengths[ANIMATE_NONE] = 0.0
+ frames[_ANIM_STAGE_NONE] = (self._board_transform, starting_frame)
+ lengths[_ANIM_STAGE_NONE] = 0.0
# Calculate shrinking coords.
shrinking_frame = []
@@ -591,11 +655,11 @@ class GridDrawer(object):
shrinking_frame.append((i, j, 0.0, value))
else:
shrinking_frame.append((i, j, scale, value))
- frames[ANIMATE_SHRINK] = (self._board_transform, shrinking_frame)
+ frames[_ANIM_STAGE_SHRINK] = (self._board_transform, shrinking_frame)
if len(self._removal_block_set) > 0:
- lengths[ANIMATE_SHRINK] = 1.0
+ lengths[_ANIM_STAGE_SHRINK] = 3 * _ANIM_SCALE
else:
- lengths[ANIMATE_SHRINK] = 0.0
+ lengths[_ANIM_STAGE_SHRINK] = 0.0
# Calculate falling coords.
falling_frame = []
@@ -610,11 +674,11 @@ class GridDrawer(object):
else:
falling_frame.append((coord[0], coord[1], scale, value))
max_change = max(max_change, j - coord[1])
- frames[ANIMATE_FALL] = (self._board_transform, falling_frame)
+ frames[_ANIM_STAGE_FALL] = (self._board_transform, falling_frame)
if max_change > 0:
- lengths[ANIMATE_FALL] = 1.0
+ lengths[_ANIM_STAGE_FALL] = 3 * _ANIM_SCALE
else:
- lengths[ANIMATE_FALL] = 0.0
+ lengths[_ANIM_STAGE_FALL] = 0.0
# Calculate sliding coords.
sliding_frame = []
@@ -627,11 +691,11 @@ class GridDrawer(object):
max_change = max(max_change, i - slide_map[i])
else:
sliding_frame.append((i, j, scale, value))
- frames[ANIMATE_SLIDE] = (self._board_transform, sliding_frame)
+ frames[_ANIM_STAGE_SLIDE] = (self._board_transform, sliding_frame)
if max_change > 0:
- lengths[ANIMATE_SLIDE] = 1.0
+ lengths[_ANIM_STAGE_SLIDE] = 3 * _ANIM_SCALE
else:
- lengths[ANIMATE_SLIDE] = 0.0
+ lengths[_ANIM_STAGE_SLIDE] = 0.0
# Calculate zooming coords.
zooming_frame = sliding_frame
@@ -641,7 +705,7 @@ class GridDrawer(object):
if (board_width2 == self._board_width
and board_height2 == self._board_height):
zooming_transform = self._board_transform
- lengths[ANIMATE_ZOOM] = 0.0
+ lengths[_ANIM_STAGE_ZOOM] = 0.0
else:
(width, height) = self._get_size_func()
zooming_transform = _BoardTransform()
@@ -649,54 +713,27 @@ class GridDrawer(object):
height,
board_width2,
board_height2)
- lengths[ANIMATE_ZOOM] = 1.0
- frames[ANIMATE_ZOOM] = (zooming_transform, zooming_frame)
-
- self._animation_frames = frames
- self._animation_lengths = lengths
-
- def _recalc_animation_coords(self):
- if self._animation_mode == ANIMATE_WIN:
- self._recalc_win_animation_coords()
- self._invalidate_board() # XXX Limit to win animation?
- elif self._animation_mode == ANIMATE_NONE or not self.board_is_valid():
- self._animation_coords = []
- else:
- self._recalc_game_animation_coords()
- self._invalidate_board()
+ lengths[_ANIM_STAGE_ZOOM] = 3 * _ANIM_SCALE
+ frames[_ANIM_STAGE_ZOOM] = (zooming_transform, zooming_frame)
- def _recalc_win_animation_coords(self):
- clamped_percent = max(0.0, min(1.0, self._animation_percent))
- t = clamped_percent * self._win_length
- coords = []
- for i in range(len(self._win_starts)):
- (s_time, s_x, s_y, s_scale) = self._win_starts[i]
- (e_time, e_x, e_y, e_scale) = self._win_ends[i]
- delta_time = e_time - s_time
- w = max(0.0, min(1.0, (t - s_time) / delta_time))
- inv_w = (1.0 - w)
- x = s_x * inv_w + e_x * w
- y = s_y * inv_w + e_y * w
- scale = s_scale * inv_w + e_scale * w
- coords.append((x, y, scale))
- self._win_coords = coords
+ self._anim_frames = frames
+ self._anim_lengths = lengths
- def _recalc_game_animation_coords(self):
- modes = [ANIMATE_NONE,
- ANIMATE_SHRINK,
- ANIMATE_FALL,
- ANIMATE_SLIDE,
- ANIMATE_ZOOM]
- mode = self._animation_mode
- prev_mode = modes[modes.index(mode, 1) - 1]
+ def _recalc_anim_coords(self):
+ stage = self._anim_stage
+ prev_stage = _ANIM_STAGES[_ANIM_STAGES.index(stage, 1) - 1]
+ (start_transform, start_coords) = self._anim_frames[prev_stage]
+ (end_transform, end_coords ) = self._anim_frames[stage]
- w = float(min(max(self._animation_percent, 0.0), 1.0))
+ length = self.get_anim_length()
+ if length == 0.0:
+ w = 0.0
+ else:
+ w = float(min(1.0, max(0.0, self._anim_time / length)))
inv_w = (1.0 - w)
- (start_transform, start_coords) = self._animation_frames[prev_mode]
- (end_transform, end_coords ) = self._animation_frames[mode]
if start_coords is end_coords:
- self._animation_coords = start_coords
+ self._anim_coords = start_coords
else:
coords = []
for i in range(len(start_coords)):
@@ -706,14 +743,14 @@ class GridDrawer(object):
y = (y1 * inv_w + y2 * w)
s = (s1 * inv_w + s2 * w)
coords.append((x, y, s, color1))
- self._animation_coords = coords
+ self._anim_coords = coords
if start_transform is end_transform:
self._board_transform = start_transform
else:
self._board_transform.tween(start_transform, end_transform, w)
- def init_board_layout(self, width, height):
+ def resize(self, width, height):
if not self.board_is_valid():
self._board_transform = _BoardTransform()
else:
@@ -722,98 +759,20 @@ class GridDrawer(object):
height,
self._board_width,
self._board_height)
- self._recalc_win_transform(width, height)
-
- def _recalc_win_transform(self, width, height):
- if self._win_size == (0, 0):
- return
- self._win_transform = _BoardTransform()
- self._win_transform.setup(width,
- height,
- self._win_size[0],
- self._win_size[1])
def draw(self, cr, width, height):
# Draws the widget.
-
- cr.set_source_rgb(*_BG_COLOR)
- cr.rectangle(0, 0, width, height)
- cr.fill()
-
+ _draw_background(cr, width, height)
cr.save()
- if self._animation_mode == ANIMATE_NONE:
- if self._win_draw_flag:
- self._win_transform.set_up_cairo(cr)
- self._draw_win(cr)
- else:
- self._board_transform.set_up_cairo(cr)
- self._draw_board(cr)
- elif self._animation_mode in (ANIMATE_SHRINK,
- ANIMATE_FALL,
- ANIMATE_SLIDE,
- ANIMATE_ZOOM):
- self._board_transform.set_up_cairo(cr)
- self._animate_board(cr)
- elif self._animation_mode == ANIMATE_WIN:
- self._win_transform.set_up_cairo(cr)
- self._draw_animated_win(cr)
+ self._board_transform.set_up_cairo(cr)
+ self._animate_board(cr)
cr.restore()
def _animate_board(self, cr):
- self._animate_blocks(cr)
-
- def _draw_board(self, cr):
- # Draws the game board on the widget, where each unit corresponds to
- # a cell on the board.
- self._draw_blocks(cr)
- self._draw_selected(cr)
- self._draw_selected_dot(cr)
-
- def _draw_win(self, cr):
- for (time, x, y, scale) in self._win_ends:
- if scale > 0.0:
- self._draw_scaled_block(cr, x, y, self._win_color, scale)
-
- def _draw_animated_win(self, cr):
- value = 1
- for (x, y, scale) in self._win_coords:
- if scale > 0.0:
- self._draw_scaled_block(cr, x, y, self._win_color, scale)
-
- def _animate_blocks(self, cr):
- for (x, y, scale, value) in self._animation_coords:
+ for (x, y, scale, value) in self._anim_coords:
if scale > 0.0:
self._draw_scaled_block(cr, x, y, value, scale)
- def _draw_blocks(self, cr):
- if not self.board_is_valid():
- return
-
- value_map = self._board.get_value_map()
- for (coord, value) in value_map.items():
- self._draw_block(cr, coord[0], coord[1], value)
-
- def _draw_selected(self, cr):
- # Draws a white background to selected blocks, then redraws blocks
- # on top.
- if (self._selected_cell is None
- or self._selected_cell not in self._contiguous_map):
- return
- contiguous = self._contiguous_map[self._selected_cell]
- value = self._board.get_value(*self._selected_cell)
- cr.set_source_rgb(*_SELECTED_COLOR)
- for (x, y) in contiguous:
- self._draw_square(cr, x, y, _SELECTED_MARGIN)
- for (x, y) in contiguous:
- self._draw_block(cr, x, y, value)
-
- def _draw_block(self, cr, x, y, value):
- # Draws the block at the given grid cell.
- assert value is not None
- c = color.colors[value]
- cr.set_source_rgb(*c)
- self._draw_square(cr, x, y, -_BLOCK_GAP)
-
def _draw_scaled_block(self, cr, x, y, value, scale):
c = color.colors[value]
cr.set_source_rgb(*c)
@@ -828,16 +787,6 @@ class GridDrawer(object):
cr.rectangle(x1, y1, size, size)
cr.fill()
- def _draw_selected_dot(self, cr):
- if self._selected_cell is None:
- return
- # Draws a dot indicating the selected cell.
- cr.set_source_rgb(*_SELECTED_COLOR)
-
- (x, y) = self._selected_cell
- cr.arc(x + 0.5, y + 0.5, _SELECTED_DOT_RADIUS, 0, math.pi * 2.0)
- cr.fill()
-
def _recalc_board_dimensions(self):
if self.board_is_valid():
self._board_width = self._board.width
@@ -852,92 +801,205 @@ class GridDrawer(object):
and not self._board.is_empty())
-class WinAnim(object):
- """Manages the animation of a winning smiley."""
- def __init__(self, grid, end_anim_func):
- self._grid = grid
- self._end_anim_func = end_anim_func
- self._start_time = 0
- self._animating = False
+class WinDrawer(object):
+ """Object to manage the drawing of the win animation."""
- def start(self):
- self._start_time = time.time()
- self._animating = True
- self._grid.set_animation_mode(ANIMATE_WIN)
- self._grid.set_animation_percent(0.0)
- gobject.timeout_add(_TIMER_INTERVAL, self._timer)
+ def __init__(self, get_size_func, invalidate_rect_func, *args, **kwargs):
+ super(WinDrawer, self).__init__(*args, **kwargs)
- def stop(self):
- if self._animating:
- self._end_anim(anim_stopped=True)
+ self._anim_time = 0.0
- def _timer(self):
- if not self._animating:
- return False
- delta = time.time() - self._start_time
- total = _WIN_ANIM_TIME * self._grid.get_animation_length()
- if total > 0:
- percent = float(delta) / total
- if percent < 1.0:
- self._grid.set_animation_percent(percent)
- return True
- self._end_anim(anim_stopped=False)
- return False
+ self._win_coords = []
+ self._win_starts = []
+ self._win_ends = []
+ self._anim_length = 0
+ self._win_size = (0,0)
+ self._win_transform = None
+ self._win_color = 0
- def _end_anim(self, anim_stopped):
- self._animating = False
- self._grid.set_animation_mode(ANIMATE_NONE)
- self._end_anim_func(anim_stopped=anim_stopped)
+ (tiles, width, height) = self._get_win_tiles()
+ self._win_size = (width, height)
+ # Callback functions set by owner.
+ self._get_size_func = get_size_func
+ self._invalidate_rect_func = invalidate_rect_func
-class RemovalAnim(object):
- """Manages the animation of removing a piece."""
- def __init__(self, grid, end_anim_func, contiguous):
- self._grid = grid
- self._end_anim_func = end_anim_func
- self._start_time = 0
- self._animation_mode = 0
- self._animating = False
- self._contiguous = contiguous
+ def set_anim_time(self, t):
+ if self._anim_time != t:
+ self._anim_time = t
+ self._recalc_anim_coords()
+ self._invalidate_board()
- def start(self):
- self._start_time = time.time()
- self._animation_mode = 0
- self._grid.set_removal_block_set(self._contiguous)
- self._grid.set_animation_mode(_ANIM_MODES[0])
- self._grid.set_animation_percent(0.0)
- self._animating = True
- gobject.timeout_add(_TIMER_INTERVAL, self._timer)
+ def _recalc_anim_coords(self):
+ t = max(0.0, min(self._anim_length, self._anim_time))
+ coords = []
+ for i in range(len(self._win_starts)):
+ (s_time, s_x, s_y, s_scale) = self._win_starts[i]
+ (e_time, e_x, e_y, e_scale) = self._win_ends[i]
+ delta_time = e_time - s_time
+ w = max(0.0, min(1.0, (t - s_time) / delta_time))
+ inv_w = (1.0 - w)
+ x = s_x * inv_w + e_x * w
+ y = s_y * inv_w + e_y * w
+ scale = s_scale * inv_w + e_scale * w
+ coords.append((x, y, scale))
+ self._win_coords = coords
- def stop(self):
- if self._animating:
- self._end_anim(anim_stopped=True)
+ def get_win_color(self):
+ return self._win_color
- def _timer(self):
- if not self._animating:
- return False
- delta = time.time() - self._start_time
- total = (_ANIM_TIMES[_ANIM_MODES[self._animation_mode]]
- * self._grid.get_animation_length())
- if total > 0:
- percent = float(delta) / total
- if percent < 1.0:
- self._grid.set_animation_percent(percent)
- return True
- self._animation_mode += 1
- if self._animation_mode >= len(_ANIM_MODES):
- self._end_anim(anim_stopped=False)
- return False
- else:
- self._grid.set_animation_mode(_ANIM_MODES[self._animation_mode])
- self._grid.set_animation_percent(0.0)
- self._start_time = time.time()
- return True
+ def set_win_state(self, draw_flag, win_color):
+ if draw_flag:
+ self.init()
+ self._win_color = win_color
+ self.set_anim_time(self.get_anim_length())
- def _end_anim(self, anim_stopped):
- self._animating = False
- self._grid.set_animation_mode(ANIMATE_NONE)
- self._end_anim_func(anim_stopped=anim_stopped)
+ def get_anim_length(self):
+ """Returns the length of the win animation (in seconds)."""
+ return self._anim_length
+
+ def init(self):
+ r = random.Random()
+ r.seed()
+ (tiles, width, height) = self._get_win_tiles()
+ tiles = self._reorder_win_tiles(r, tiles, width, height)
+ self._win_starts = self._get_win_starts(tiles, width, height)
+ self._win_ends = self._get_win_ends(tiles)
+ self._anim_length = self._get_win_length(tiles)
+ self._win_size = (width, height)
+ self._win_color = r.randint(1, 5)
+ (width, height) = self._get_size_func()
+ self.resize(width, height)
+
+ def _invalidate_board(self):
+ (width, height) = self._get_size_func()
+ rect = gtk.gdk.Rectangle(0, 0, width, height)
+ self._invalidate_rect_func(rect)
+
+ def _get_win_tiles(self):
+ # Returns a list of ending tile coordinates making up the smiley face,
+ # as well as the width and height in tiles.
+ data = [list(x.strip()) for x in _SMILEY.strip().splitlines()]
+ height = len(data)
+ widths = set([len(x) for x in data])
+ assert len(widths) == 1
+ width = widths.pop()
+ assert width > 0
+ assert height > 0
+ tiles = []
+ for i in range(height):
+ for j in range(width):
+ if data[i][j] == 'x':
+ # Invert y axis because we use the board tile engine to
+ # display, which uses cartesian coordinates instead of
+ # display coordinates.
+ tiles.append((j, height - i - 1))
+ return (tiles, width, height)
+
+ def _reorder_win_tiles(self, r, tiles, width, height):
+ # Re-sorts tiles by several randomly chosen criteria.
+ def radial(coord):
+ (x, y) = coord
+ x = float(x) / width - 0.5
+ y = float(y) / height - 0.5
+ return 2 * math.sqrt(x * x + y * y)
+ def x(coord):
+ return float(coord[0]) / width
+ def y(coord):
+ return float(coord[1]) / height
+ def angle(coord):
+ (x, y) = coord
+ x = float(x) / width - 0.5
+ y = float(y) / height - 0.5
+ angle = math.atan2(y, x)
+ return (angle / math.pi + 1) / 2
+ funcs = [radial, x, y, angle]
+ r.shuffle(funcs)
+ invs = [r.choice((-1, 1)), r.choice((-1, 1))]
+ pairs = []
+ w = r.random()
+ for coord in tiles:
+ score = funcs[0](coord) * invs[0] + funcs[1](coord) * invs[1] * w
+ pairs.append((score, coord))
+ pairs.sort()
+ # Re-interleave pairs, if desired.
+ if r.randint(0, 1):
+ index1 = int(len(pairs) / 2)
+ list1 = pairs[:index1]
+ list2 = pairs[index1:]
+ if r.randint(0, 1):
+ list2.reverse()
+ pairs = _interleave(list1, list2)
+ return [x[1] for x in pairs]
+
+ def _get_win_starts(self, tiles, width, height):
+ # Returns a list of starting coordinates for tiles.
+ starts = []
+ assert width > 0
+ assert height > 0
+ start_x = width / 2.0 - 0.5
+ start_y = height / 2.0 - 0.5
+ for (i, (x, y)) in enumerate(tiles):
+ starts.append((i * _ANIM_SCALE, start_x, start_y, 0.0))
+ #starts.append((i, x, y, 0.0))
+ return starts
+
+ def _get_win_ends(self, tiles):
+ # Returns a list of ending coordinates for the tiles in the unit
+ # square.
+ ends = []
+ for (i, (x, y)) in enumerate(tiles):
+ ends.append(((i + 8) * _ANIM_SCALE, x, y, 1.0))
+ return ends
+
+ def _get_win_length(self, tiles):
+ # Returns the length of the win animation for the given set of tiles
+ # (in seconds).
+ return (len(tiles) + 8) * _ANIM_SCALE
+
+ def resize(self, width, height):
+ if self._win_size == (0, 0):
+ return
+ self._win_transform = _BoardTransform()
+ self._win_transform.setup(width,
+ height,
+ self._win_size[0],
+ self._win_size[1])
+
+ def draw(self, cr, width, height):
+ # Draws the widget.
+ _draw_background(cr, width, height)
+ cr.save()
+ self._win_transform.set_up_cairo(cr)
+ self._draw_win(cr)
+ cr.restore()
+
+ def _draw_win(self, cr):
+ value = 1
+ for (x, y, scale) in self._win_coords:
+ if scale > 0.0:
+ self._draw_scaled_block(cr, x, y, self._win_color, scale)
+
+ def _draw_scaled_block(self, cr, x, y, value, scale):
+ c = color.colors[value]
+ cr.set_source_rgb(*c)
+ inset = 0.5 + scale * (_BLOCK_GAP - 0.5)
+ self._draw_square(cr, x, y, -inset)
+
+ def _draw_square(self, cr, x, y, margin):
+ # Draws a square in the given grid cell with the given margin.
+ x1 = float(x) - margin
+ y1 = float(y) - margin
+ size = 1.0 + margin * 2
+ cr.rectangle(x1, y1, size, size)
+ cr.fill()
+
+
+def _draw_background(cr, width, height):
+ # Draws the board background using the given cairo context and width/height.
+ cr.set_source_rgb(*_BG_COLOR)
+ cr.rectangle(0, 0, width, height)
+ cr.fill()
class _BoardTransform(object):
diff --git a/implodegame.py b/implodegame.py
index fe4229b..9cbfc4b 100644
--- a/implodegame.py
+++ b/implodegame.py
@@ -35,7 +35,7 @@ class ImplodeGame(gtk.EventBox):
def __init__(self, *args, **kwargs):
super(ImplodeGame, self).__init__(*args, **kwargs)
self._animate = True
- self._current_anim = None
+ self._anim = None
self._board = None
self._undo_stack = []
@@ -45,7 +45,6 @@ class ImplodeGame(gtk.EventBox):
#self._random.seed(0)
self._difficulty = 0
self._size = (8, 6)
- self._contiguous = None
self._seed = 0
self._fragmentation = 0
@@ -164,11 +163,15 @@ class ImplodeGame(gtk.EventBox):
self._stop_animation()
contiguous = self._board.get_contiguous(x, y)
if len(contiguous) >= 3:
- self._contiguous = contiguous
+ def remove_func(anim_stopped=False):
+ self._remove_contiguous(contiguous, anim_stopped)
if self._animate:
- self._current_anim = self._grid.start_removal_anim(self._remove_contiguous, contiguous)
+ self._anim = self._grid.get_removal_anim(self._board,
+ contiguous,
+ remove_func)
+ self._anim.start()
else:
- self._remove_contiguous()
+ remove_func()
def _undo_key_pressed_cb(self, widget, dummy):
self.undo()
@@ -183,13 +186,14 @@ class ImplodeGame(gtk.EventBox):
self.new_game()
def _stop_animation(self):
- if self._current_anim is not None:
- self._current_anim.stop()
+ if self._anim is not None:
+ self._anim.stop()
- def _remove_contiguous(self, anim_stopped=False):
+ def _remove_contiguous(self, contiguous, anim_stopped=False):
+ # Removes the given set of contiguous blocks from the board.
self._redo_stack = []
self._undo_stack.append(self._board.clone())
- self._board.clear_pieces(self._contiguous)
+ self._board.clear_pieces(contiguous)
self._board.drop_pieces()
self._board.remove_empty_columns()
@@ -198,12 +202,13 @@ class ImplodeGame(gtk.EventBox):
if self._board.is_empty():
if self._animate and not anim_stopped:
- self._current_anim = self._grid.start_win_anim(self._init_win)
+ self._anim = self._grid.get_win_anim(self._init_win)
+ self._anim.start()
else:
self._init_win()
else:
- contiguous = self._board.get_all_contiguous()
- if len(contiguous) == 0:
+ all_contiguous = self._board.get_all_contiguous()
+ if len(all_contiguous) == 0:
self._init_lose()
def _init_win(self, anim_stopped=False):