예제 #1
0
class Board:
    """The game board.  This class maintains a Grid for the pieces on the
    board and a Grid for the frozen blocks on the board."""

    def __init__(self, parent_node, ncolumns, nrows, pos, scale):
        self.parent_node = parent_node
        self.piece_grid = None  # set_size will set these
        self.frozen_grid = None
        self.set_size(ncolumns, nrows, pos, scale)

    def set_size(self, ncolumns, nrows, pos, scale):
        self.ncolumns = ncolumns
        self.nrows = nrows
        self.pos = pos
        self.scale = float(scale)
        self.size = Point2D(ncolumns, nrows)*self.scale
        self.block_size = Point2D(self.scale, self.scale)
        if self.piece_grid:
            row_offset = nrows - self.piece_grid.nrows
            self.piece_grid.grow(ncolumns, nrows)
            self.frozen_grid.grow(ncolumns, nrows)
            pieces = set(value for cr, value in self.piece_grid.get_blocks())
            for piece in pieces:
                c, r = piece.cr
                piece.cr = (c, r + row_offset)
                piece.update_blocks()
                piece.update_manip()
            for cr, value in self.frozen_grid.get_blocks():
                value.pos = self.get_nw_point(cr)
                value.size = self.block_size
        else:
            self.piece_grid = Grid(ncolumns, nrows)  # contains Piece references
            self.frozen_grid = Grid(ncolumns, nrows)  # contains Node references

    def destroy(self):
        for cr, value in self.frozen_grid.get_blocks():
            value.unlink()

    def get_nw_point(self, (c, r)):
        return self.pos + Point2D(c, r)*self.scale
예제 #2
0
class Game:
    """The main game controller.  This class holds the Board and the list of
    Pieces, and causes the Pieces to fall with each clock tick.  When the fall
    of a Piece is stopped by blocks below, the Piece "freezes": the blocks of
    the Piece are copied onto the board and the Piece object is destroyed.
    Pieces appear in a "starting zone" at the top where they cannot be
    manipulated; if a piece freezes in the starting zone, the game ends."""

    def __init__(self, parent_node, app, scale, section_pattern,
                 tick_interval, ticks_per_drop, pieces_per_drop, shapes):
        self.width, self.height = parent_node.size.x, parent_node.size.y
        self.app = app

        self.node = create_node(parent_node, 'div')
        self.section_node = create_node(self.node, 'div', sensitive=False)

        # Create a dummy game board; it will be resized in self.set_scale().
        self.board = Board(self.node, 1, 1, Point2D(0, 0), scale)
        self.board_rect = create_node(self.node, 'rect',
            color='a0a0a0', strokewidth=4, sensitive=False)
        self.dissolving = None

        self.starting_zone = None
        self.starting_zone_node = create_node(parent_node, 'rect',
            opacity=0, fillcolor='ff0000', fillopacity=0.2)
        self.can_add_pieces = True  # For debouncing taps on the starting zone.

        # Normally, the starting zone can be touched to request more pieces;
        # however, this feature is disabled because of an exhaust hose in the
        # c-base multitouch table that causes extraneous touches along the top.
        # set_handler(self.starting_zone_node, avg.CURSORDOWN, self.handle_down)

        self.pieces = []
        self.interval_id = None
        self.ticks_to_next_drop = 1
        self.section_nodes = None
        self.set_difficulty(scale, section_pattern, tick_interval,
                            ticks_per_drop, pieces_per_drop, shapes)

    def set_difficulty(self, scale, section_pattern, tick_interval,
                       ticks_per_drop, pieces_per_drop, shapes):
        self.set_scale(scale)
        self.set_section_pattern(section_pattern)
        self.tick_interval = tick_interval
        self.ticks_per_drop = ticks_per_drop
        self.pieces_per_drop = pieces_per_drop
        self.shapes = shapes

    def set_scale(self, scale):
        # Resize the game board.
        ncolumns, nrows = int(self.width/scale), int(self.height/scale)
        board_width = ncolumns*scale
        self.board.set_size(ncolumns, nrows,
            Point2D((self.width - board_width)/2, self.height % scale), scale)
        self.board_rect.pos = self.board.pos
        self.board_rect.size = self.board.size
        if self.dissolving:
            self.dissolving.grow(self.board.ncolumns, self.board.nrows)
        else:
            self.dissolving = Grid(self.board.ncolumns, self.board.nrows)

        # Set up the starting zone.
        self.starting_zone = Grid(self.board.ncolumns, STARTING_ZONE_NROWS,
            ['X'*self.board.ncolumns]*STARTING_ZONE_NROWS)
        self.starting_zone_node.pos = self.board.pos
        self.starting_zone_node.size = \
            Point2D(self.board.ncolumns, STARTING_ZONE_NROWS)*scale

    def handle_down(self, event):
        if self.can_add_pieces:
            self.starting_zone_node.fillopacity = 0.4
            self.add_piece(self.board.get_cr(event.pos)[0])
            self.can_add_pieces = False  # Ignore extraneous double-taps.
            set_timeout(300, self.reset_starting_zone)

    def reset_starting_zone(self):
        self.starting_zone_node.fillopacity = 0.2
        self.can_add_pieces = True

    def set_section_pattern(self, section_pattern):
        if self.section_nodes:
            for node in self.section_nodes:
                node.unlink()

        self.section_nodes = []
        self.sections = []
        s = 0

        for r in reversed(range(STARTING_ZONE_NROWS, self.board.nrows)):
            nsections = section_pattern[s]
            s = (s + 1) % len(section_pattern)

            # If the resolution is too low, 4 sections are no fun.
            if self.width < 1200 and nsections > 3:
                nsections = 3

            section_min = 0
            for i in range(nsections):
                section_max = int(self.board.ncolumns*float(i + 1)/nsections)
                section_ncolumns = section_max - section_min

                section = Grid(self.board.ncolumns, self.board.nrows)
                for j in range(section_min, section_max):
                    section.put((j, r), 'X')
                self.sections.append(section)

                self.section_nodes.append(create_node(self.section_node, 'rect',
                    pos=self.board.get_nw_point((section_min, r)),
                    size=Point2D(section_ncolumns, 1)*self.board.scale,
                    opacity=0.2,
                    color='ffffff',
                    sensitive=False))

                section_min = section_max

    def destroy(self):
        self.pause()
        for piece in self.pieces:
            piece.destroy()
        for node in self.section_nodes:
            node.unlink()
        self.board.destroy()
        self.node.unlink()
        self.board_rect.unlink()
        self.section_node.unlink()
        self.starting_zone_node.unlink()

    def run(self):
        self.pause()
        self.interval_id = set_interval(self.tick_interval, self.tick)

    def pause(self):
        if self.interval_id:
            clear_interval(self.interval_id)
            self.interval_id = None

    def tick(self):
        """Advance the game by one step."""
        self.fall_and_freeze()
        self.delete_dissolving()
        self.mark_dissolving()
        if (not list(self.dissolving.get_blocks()) and
            self.board.frozen_grid.overlaps_any(self.starting_zone, (0, 0))):
            self.app.end_game()
            return

        self.ticks_to_next_drop -= 1
        if (self.ticks_to_next_drop <= 0 or
            not list(self.board.piece_grid.get_blocks())):
            self.ticks_to_next_drop = self.ticks_per_drop
            for p in range(self.pieces_per_drop):
                if not self.add_piece():
                    print 'end_game: add_piece failed'
                    self.app.end_game()
                    return

        self.app.tick()

    def fall_and_freeze(self):
        """Classify all pieces as suspended (by a player grab), stopped (by
        frozen blocks), or falling; move falling pieces down by one step and
        freeze all stopped pieces."""

        # 1. Determine what supports each piece.  Pieces can be supported by
        # other pieces, by frozen blocks, or by the bottom edge of the board.
        supported_pieces = dict((piece, set()) for piece in self.pieces)
        stopped = set()
        for piece in self.pieces:
            for cr, value in piece.get_blocks():
                cr_below = add_cr(cr, (0, 1))
                if not self.board.piece_grid.in_bounds(cr_below):
                    stopped.add(piece)  # at bottom edge
                else:
                    piece_below = self.board.piece_grid.get(cr_below)
                    frozen_below = self.board.frozen_grid.get(cr_below)
                    if piece_below:  # supported by a Piece
                        supported_pieces[piece_below].add(piece)
                    elif frozen_below:  # supported by a frozen block
                        stopped.add(piece)

        # 2. Stop pieces supported by the board, and pieces they support.
        stopped = transitive_closure(stopped, supported_pieces)

        # 3. Suspend pieces that are grabbed, and pieces they support.
        suspended = set(piece for piece in self.pieces if piece.is_grabbed())
        suspended = transitive_closure(suspended, supported_pieces)

        # 4. Freeze all stopped pieces.
        for piece in stopped:
            if piece not in suspended:
                self.board.freeze_piece(piece)
                self.pieces.remove(piece)

        # 5. Move down all falling pieces.
        for piece in self.pieces:
            if piece not in suspended:
                piece.move_to(add_cr(piece.cr, (0, 1)))

    def delete_dissolving(self):
        """Delete any frozen blocks that are dissolving."""
        self.board.delete_frozen(self.dissolving)
        self.dissolving.clear()

    def mark_dissolving(self):
        """Mark dissolving blocks (to be deleted on the next tick)."""
        for section in self.sections:
            if self.board.frozen_grid.overlaps_all(section, (0, 0)):
                self.dissolving.put_all(section, (0, 0))
        self.board.highlight_dissolving(self.dissolving)

    def add_piece(self, c=None):
        """Place a new Piece with a randomly selected shape at a random
        rotation and position somewhere along the top of the game board."""
        columns = (c is None) and range(self.board.ncolumns) or [c]

        # Permute the shapes, rotations, and possible positions, so that we
        # get a random result, but eventually try them all.
        for shape in shuffled(self.shapes):
            for rotation in shuffled(range(4)):
                for c in shuffled(columns):
                    grid = shape.get_rotated(rotation)
                    min_r = min(r for (c, r), value in grid.get_blocks())
                    cr = (c - grid.ncolumns/2, -min_r)
                    if self.board.can_put_piece(None, cr, grid):
                        self.pieces.append(
                            Piece(self.node, self.board, cr, grid))
                        return True
        return False  # couldn't find anywhere to put a new piece