差分
このページの2つのバージョン間の差分を表示します。
| 両方とも前のリビジョン前のリビジョン次のリビジョン | 前のリビジョン | ||
| youtube:tetris-python-cocos2d [2024/02/28 18:26] – 削除 - 外部編集 (不明な日付) 127.0.0.1 | youtube:tetris-python-cocos2d [2024/02/29 11:51] (現在) – freemikan | ||
|---|---|---|---|
| 行 1: | 行 1: | ||
| + | ====== テトリス Python+Cocos2d ====== | ||
| + | |||
| + | {{: | ||
| + | |||
| + | |||
| + | ===== tetris.py ===== | ||
| + | |||
| + | |||
| + | <file python> | ||
| + | """ | ||
| + | |||
| + | import random | ||
| + | import cocos | ||
| + | from cocos.director import director | ||
| + | import pyglet | ||
| + | |||
| + | |||
| + | def arena_to_pixel(ax, | ||
| + | """ | ||
| + | px = ax * (Cell.WIDTH + Cell.SPACING) | ||
| + | py = ay * (Cell.HEIGHT + Cell.SPACING) | ||
| + | return (px, py) | ||
| + | |||
| + | |||
| + | class Cell(cocos.sprite.Sprite): | ||
| + | """ | ||
| + | |||
| + | WIDTH = 32 | ||
| + | HEIGHT = 32 | ||
| + | SPACING = 1 | ||
| + | IMAGE = pyglet.resource.image(' | ||
| + | |||
| + | def __init__(self, | ||
| + | """ | ||
| + | super(Cell, self).__init__(Cell.IMAGE, | ||
| + | self.scale_x = Cell.WIDTH / Cell.IMAGE.width | ||
| + | self.scale_y = Cell.HEIGHT / Cell.IMAGE.height | ||
| + | self.set_arena_position(acoord[0], | ||
| + | |||
| + | def set_arena_position(self, | ||
| + | """ | ||
| + | self.ax = ax | ||
| + | self.ay = ay | ||
| + | self.position = arena_to_pixel(ax, | ||
| + | |||
| + | def get_arena_position(self): | ||
| + | """ | ||
| + | return self.ax, self.ay | ||
| + | |||
| + | |||
| + | class Piece(cocos.cocosnode.CocosNode): | ||
| + | """ | ||
| + | |||
| + | 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, | ||
| + | """ | ||
| + | super(Piece, | ||
| + | |||
| + | 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], | ||
| + | self.clear_input() | ||
| + | |||
| + | def update(self, | ||
| + | """ | ||
| + | # 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, | ||
| + | if self._is_overlapping(): | ||
| + | # cancel | ||
| + | self.move(-self.move_input_dx, | ||
| + | |||
| + | # free fall | ||
| + | self.drop_time -= dt | ||
| + | if self.drop_time <= 0: | ||
| + | self.move(0, | ||
| + | self.drop_time = self.drop_interval | ||
| + | if self._is_overlapping(): | ||
| + | self.move(0, | ||
| + | self._detach_cells() | ||
| + | self.alive = False | ||
| + | |||
| + | self.clear_input() | ||
| + | |||
| + | def set_arena_position(self, | ||
| + | """ | ||
| + | self.ax = ax | ||
| + | self.ay = ay | ||
| + | self.position = arena_to_pixel(self.ax, | ||
| + | |||
| + | def move(self, dx, dy): | ||
| + | """ | ||
| + | self.set_arena_position(self.ax + dx, self.ay + dy) | ||
| + | |||
| + | def rotate(self, | ||
| + | """ | ||
| + | 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, | ||
| + | |||
| + | def set_drop_interval(self, | ||
| + | """ | ||
| + | self.drop_interval = drop_interval | ||
| + | |||
| + | def push_move_input(self, | ||
| + | """ | ||
| + | self.move_input_dx += dx | ||
| + | self.move_input_dy += dy | ||
| + | |||
| + | def push_rotation_input(self, | ||
| + | """ | ||
| + | # -1 is counter clock-wise (left) | ||
| + | # +1 is clock-wise (right) | ||
| + | # otherwise is no rotation | ||
| + | self.rotation_input = rotation | ||
| + | |||
| + | def clear_input(self): | ||
| + | """ | ||
| + | self.move_input_dx = 0 | ||
| + | self.move_input_dy = 0 | ||
| + | self.rotation_input = 0 | ||
| + | |||
| + | def _detach_cells(self): | ||
| + | """ | ||
| + | for cell in self.get_children(): | ||
| + | (ax, ay) = cell.get_arena_position() | ||
| + | ax += self.ax | ||
| + | ay += self.ay | ||
| + | cell.set_arena_position(ax, | ||
| + | self.remove(cell) | ||
| + | self.parent.add(cell) | ||
| + | |||
| + | def _is_overlapping(self): | ||
| + | """ | ||
| + | # 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, | ||
| + | ] | ||
| + | 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 self.alive | ||
| + | |||
| + | def left(self): | ||
| + | """ | ||
| + | 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): | ||
| + | """ | ||
| + | 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): | ||
| + | """ | ||
| + | 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): | ||
| + | """ | ||
| + | |||
| + | WIDTH = 10 | ||
| + | HEIGHT = 20 | ||
| + | |||
| + | def __init__(self, | ||
| + | """ | ||
| + | super(Arena, | ||
| + | self.set_arena_position(acoord[0], | ||
| + | |||
| + | def set_arena_position(self, | ||
| + | """ | ||
| + | self.ax = ax | ||
| + | self.ay = ay | ||
| + | self.position = arena_to_pixel(self.ax, | ||
| + | |||
| + | |||
| + | class Game(cocos.layer.Layer): | ||
| + | """ | ||
| + | |||
| + | is_event_handler = True | ||
| + | |||
| + | def __init__(self): | ||
| + | """ | ||
| + | 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, | ||
| + | """ | ||
| + | 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, | ||
| + | """ | ||
| + | dx = 0 | ||
| + | if key == pyglet.window.key.LEFT: | ||
| + | dx = -1 | ||
| + | if key == pyglet.window.key.RIGHT: | ||
| + | dx = +1 | ||
| + | self.piece.push_move_input(dx, | ||
| + | |||
| + | 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): | ||
| + | """ | ||
| + | line_cell_count = [0] * Arena.HEIGHT | ||
| + | cells = [c for c in self.arena.get_children() if isinstance(c, | ||
| + | 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, | ||
| + | elif ay == linum: | ||
| + | removing_cells.append(cell) | ||
| + | for cell in removing_cells: | ||
| + | cells.remove(cell) | ||
| + | cell.kill() | ||
| + | |||
| + | def _respawn_piece(self): | ||
| + | """ | ||
| + | self.piece = Piece(random.choice(Piece.PATTERN), | ||
| + | | ||
| + | | ||
| + | self.arena.add(self.piece) | ||
| + | |||
| + | def _setup_fence(self): | ||
| + | """ | ||
| + | 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=" | ||
| + | autoscale=False) | ||
| + | scene = cocos.scene.Scene(Game()) | ||
| + | director.run(scene) | ||
| + | </ | ||
