youtube:tetris-python-cocos2d
テトリス Python+Cocos2d
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)
youtube/tetris-python-cocos2d.txt · 最終更新: 2024/02/29 11:51 by freemikan