====== テトリス Python+Cocos2d ====== {{:youtube:tetris-python-cocos2d.png?300|}} ===== tetris.py ===== """Tetris Cocos2d.""" import random import cocos from cocos.director import director import pyglet def arena_to_pixel(ax, ay): """Convert arena coord to pixel coord.""" px = ax * (Cell.WIDTH + Cell.SPACING) py = ay * (Cell.HEIGHT + Cell.SPACING) return (px, py) class Cell(cocos.sprite.Sprite): """Cell component.""" WIDTH = 32 HEIGHT = 32 SPACING = 1 IMAGE = pyglet.resource.image('square.png') def __init__(self, acoord, color): """Ctor.""" super(Cell, self).__init__(Cell.IMAGE, color=color, anchor=(0, 0)) self.scale_x = Cell.WIDTH / Cell.IMAGE.width self.scale_y = Cell.HEIGHT / Cell.IMAGE.height self.set_arena_position(acoord[0], acoord[1]) def set_arena_position(self, ax, ay): """Set position in arena coord.""" self.ax = ax self.ay = ay self.position = arena_to_pixel(ax, ay) def get_arena_position(self): """Get position in arena coord.""" return self.ax, self.ay class Piece(cocos.cocosnode.CocosNode): """Piece consits with four cells.""" PATTERN = [ [(0, 0), (1, 0), (1, 1), (2, 1)], # S [(1, 0), (2, 0), (0, 1), (1, 1)], # Z [(0, 0), (1, 0), (0, 1), (0, 2)], # L [(0, 0), (1, 0), (1, 1), (1, 2)], # J [(1, 0), (0, 1), (1, 1), (2, 1)], # T [(0, 0), (1, 0), (0, 1), (1, 1)], # O [(0, 0), (1, 0), (2, 0), (3, 0)], # I ] def __init__(self, pattern, position, drop_interval): """Ctor.""" super(Piece, self).__init__() self.ax = 0 self.ay = 0 self.drop_interval = drop_interval self.drop_time = drop_interval self.alive = True for p in pattern: c = Cell(p, (255, 255, 255)) self.add(c) self.set_arena_position(position[0], position[1]) self.clear_input() def update(self, dt): """Update frame.""" # handle rotation self.rotate(self.rotation_input) if self._is_overlapping(): # cancel self.rotate(-self.rotation_input) # move left or right self.move(self.move_input_dx, self.move_input_dy) if self._is_overlapping(): # cancel self.move(-self.move_input_dx, -self.move_input_dy) # free fall self.drop_time -= dt if self.drop_time <= 0: self.move(0, -1) self.drop_time = self.drop_interval if self._is_overlapping(): self.move(0, 1) # cancel drop self._detach_cells() self.alive = False self.clear_input() def set_arena_position(self, ax, ay): """Set position in arena coord.""" self.ax = ax self.ay = ay self.position = arena_to_pixel(self.ax, self.ay) def move(self, dx, dy): """Move by offset.""" self.set_arena_position(self.ax + dx, self.ay + dy) def rotate(self, rotation): """Rotate left (-1) or right (+1).""" if rotation != -1 and rotation != 1: return for c in self.get_children(): cx, cy = c.get_arena_position() new_x = +rotation * cy new_y = -rotation * cx c.set_arena_position(new_x, new_y) def set_drop_interval(self, drop_interval): """Set drop intenval.""" self.drop_interval = drop_interval def push_move_input(self, dx, dy): """Push move value for next update.""" self.move_input_dx += dx self.move_input_dy += dy def push_rotation_input(self, rotation): """Push rotation value for next update.""" # -1 is counter clock-wise (left) # +1 is clock-wise (right) # otherwise is no rotation self.rotation_input = rotation def clear_input(self): """Clear input values.""" self.move_input_dx = 0 self.move_input_dy = 0 self.rotation_input = 0 def _detach_cells(self): """Detach child cells and pass to parent.""" for cell in self.get_children(): (ax, ay) = cell.get_arena_position() ax += self.ax ay += self.ay cell.set_arena_position(ax, ay) self.remove(cell) self.parent.add(cell) def _is_overlapping(self): """Check if overlapping something.""" # check for left or right wall if self.left() < 0 or self.right() >= Arena.WIDTH: return True # check landing on bottom if self.bottom() < 0: return True # check for cells arena_cells = [ c for c in self.parent.get_children() if isinstance(c, Cell) ] for pcell in self.get_children(): px, py = pcell.get_arena_position() px += self.ax py += self.ay for acell in arena_cells: ax, ay = acell.get_arena_position() if px == ax and py == ay: return True return False def is_alive(self): """Return True if this piece is alive (not landing on).""" return self.alive def left(self): """Return left most cell position x.""" children = self.get_children() if len(children) > 0: return self.ax + min([c.get_arena_position()[0] for c in children]) else: return self.ax def right(self): """Return right most cell position x.""" children = self.get_children() if len(children) > 0: return self.ax + max([c.get_arena_position()[0] for c in children]) else: return self.ax def bottom(self): """Return down most cell position .y.""" children = self.get_children() if len(children) > 0: return self.ay + min([c.get_arena_position()[1] for c in children]) else: return self.ay class Arena(cocos.cocosnode.CocosNode): """Arena where cells put on.""" WIDTH = 10 HEIGHT = 20 def __init__(self, acoord): """Ctor.""" super(Arena, self).__init__() self.set_arena_position(acoord[0], acoord[1]) def set_arena_position(self, ax, ay): """Set position in arena coord.""" self.ax = ax self.ay = ay self.position = arena_to_pixel(self.ax, self.ay) class Game(cocos.layer.Layer): """Game player layer.""" is_event_handler = True def __init__(self): """Ctor.""" super(Game, self).__init__() self._setup_fence() self.arena = Arena((1, 1)) self.add(self.arena) self._respawn_piece() self.schedule(self.update) def update(self, dt): """Update frame.""" if self.piece.is_alive(): self.piece.update(dt) else: self.piece.kill() self._cleanup_perfect_lines() self._respawn_piece() def on_key_press(self, key, modifiers): """Handle key press events.""" dx = 0 if key == pyglet.window.key.LEFT: dx = -1 if key == pyglet.window.key.RIGHT: dx = +1 self.piece.push_move_input(dx, 0) rot = 0 if key == pyglet.window.key.UP: rot = -1 if key == pyglet.window.key.DOWN: rot = +1 self.piece.push_rotation_input(rot) if key == pyglet.window.key.SPACE: self.piece.set_drop_interval(0) def _cleanup_perfect_lines(self): """Clean up cells on perfect line.""" line_cell_count = [0] * Arena.HEIGHT cells = [c for c in self.arena.get_children() if isinstance(c, Cell)] for cell in cells: ay = cell.get_arena_position()[1] if ay < Arena.HEIGHT: line_cell_count[ay] += 1 for linum in range(Arena.HEIGHT - 1, -1, -1): if line_cell_count[linum] == Arena.WIDTH: removing_cells = [] for cell in cells: ax, ay = cell.get_arena_position() if ay > linum: cell.set_arena_position(ax, ay - 1) elif ay == linum: removing_cells.append(cell) for cell in removing_cells: cells.remove(cell) cell.kill() def _respawn_piece(self): """Respawn new piece.""" self.piece = Piece(random.choice(Piece.PATTERN), position=(Arena.WIDTH // 2, Arena.HEIGHT), drop_interval=0.5) self.arena.add(self.piece) def _setup_fence(self): """Set up left and right walls, bottom floor.""" fence_color = (88, 88, 188) for i in range(Arena.HEIGHT + 1): left = Cell((0, i), fence_color) right = Cell((Arena.WIDTH + 1, i), fence_color) self.add(left) self.add(right) for i in range(1, Arena.WIDTH + 1): bottom = Cell((i, 0), fence_color) self.add(bottom) game_width = (Arena.WIDTH + 2) * (Cell.WIDTH + Cell.SPACING) - Cell.SPACING game_height = (Arena.HEIGHT + 1) * (Cell.HEIGHT + Cell.SPACING) - Cell.SPACING director.init(width=game_width, height=game_height, caption="Tetris Cocos2d", autoscale=False) scene = cocos.scene.Scene(Game()) director.run(scene)