Ejemplo n.º 1
0
class Talpa(SeatedGame):
    """A Talpa game table implementation.  Invented in 2010 by Arty Sandler.
    """

    def __init__(self, server, table_name):

        super(Talpa, self).__init__(server, table_name)

        self.game_display_name = "Talpa"
        self.game_name = "talpa"
        self.seats = [Seat("Red"), Seat("Blue")]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RTalpa^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Talpa-specific stuff.
        self.size = 8
        self.turn = None
        self.red = self.seats[0]
        self.red.data.seat_str = "^RRed/Vertical^~"
        self.blue = self.seats[1]
        self.blue.data.seat_str = "^BBlue/Horizontal^~"
        self.resigner = None
        self.layout = None

        # Like in most connection games, there is no difference between pieces
        # of a given color, so we save time and create our singleton pieces
        # here.
        self.rp = Piece("^R", "x", "X")
        self.rp.data.owner = self.red
        self.bp = Piece("^B", "o", "O")
        self.bp.data.owner = self.blue

        # Initialize the starting layout.
        self.init_layout()

    def init_layout(self):

        # Create the layout and fill it with pieces.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.size)

        for i in range(self.size):
            for j in range(self.size):
                if (i + j) % 2:
                    self.layout.place(self.rp, i, j, update=False)
                else:
                    self.layout.place(self.bp, i, j, update=False)

        self.layout.update()

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name, self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_str):

        if not size_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        size = int(size_str)

        if size < MIN_SIZE or size > MAX_SIZE:
            self.tell_pre(player, "Size must be between %d and %d inclusive.\n" % (MIN_SIZE, MAX_SIZE))
            return False

        # Size must be even.
        if size % 2:
            self.tell_pre(player, "Size must be even.\n")
            return False

        # Valid!
        self.size = size
        self.bc_pre("^R%s^~ has set the board size to ^C%d^~.\n" % (player, size))
        self.init_layout()

    def move(self, player, src_bits, dst_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        src_c, src_r = src_bits
        dst_c, dst_r = dst_bits

        # Are they valid?
        if not self.layout.is_valid(src_r, src_c) or not self.layout.is_valid(dst_r, dst_c):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is it an orthogonal move?
        c_delta = abs(src_c - dst_c)
        r_delta = abs(src_r - dst_r)
        if c_delta > 1 or r_delta > 1 or (c_delta and r_delta):
            self.tell_pre(player, "You can only make a single-space orthogonal move.\n")
            return False

        # Is there a piece for this player in the source location?
        src_loc = self.layout.grid[src_r][src_c]
        if not src_loc or src_loc.data.owner != seat:
            self.tell_pre(player, "You must have a piece in the source location.\n")
            return False

        # Is there a piece for the other player in the destination location?
        dst_loc = self.layout.grid[dst_r][dst_c]
        if not dst_loc or dst_loc.data.owner == seat:
            self.tell_pre(player, "Your opponent must have a piece in the destination location.\n")
            return False

        # Phew.  Success.  Make the capture.
        src_str = "%s%s" % (COLS[src_c], src_r + 1)
        dst_str = "%s%s" % (COLS[dst_c], dst_r + 1)
        self.bc_pre("%s moves a piece from ^C%s^~ to ^G%s^~.\n" % (self.get_sp_str(seat), src_str, dst_str))
        self.layout.move(src_r, src_c, dst_r, dst_c, True)

        return True

    def has_capture(self, seat):

        # We loop through the board, checking each piece to see if it's for
        # this player.  If so, we check its four adjacencies to see if one
        # of them is for the other player.  If so, there's still a valid
        # capture this player can make.
        for r in range(self.size):
            for c in range(self.size):
                loc = self.layout.grid[r][c]
                if loc and loc.data.owner == seat:
                    for r_delta, c_delta in CONNECTION_DELTAS:
                        new_r = r + r_delta
                        new_c = c + c_delta
                        if self.layout.is_valid(new_r, new_c):
                            dst = self.layout.grid[new_r][new_c]
                            if dst and dst.data.owner != seat:
                                return True

        # We never found a valid capture.
        return False

    def remove(self, player, remove_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't remove; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to remove.\n")
            return False

        c, r = remove_bits

        # Is it within bounds?
        if not self.layout.is_valid(r, c):
            self.tell_pre(player, "Your remove is out of bounds.\n")
            return False

        # Does this player have a piece there?
        loc = self.layout.grid[r][c]
        if not loc or loc.data.owner != seat:
            self.tell_pre(player, "You must have a piece there to remove.\n")
            return False

        # Do they have a valid capture instead?
        if self.has_capture(seat):
            self.tell_pre(player, "You have a capture left.\n")
            return False

        # All right, remove the piece.
        loc_str = "%s%s" % (COLS[c], r + 1)
        self.bc_pre("%s removes a piece from ^R%s^~.\n" % (self.get_sp_str(seat), loc_str))
        self.layout.remove(r, c, True)

        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if self.state.get() == "need_players" and self.red.player and self.blue.player and self.active:
            self.bc_pre(
                "%s: ^C%s^~; %s: ^C%s^~\n"
                % (self.red.data.seat_str, self.red.player_name, self.blue.data.seat_str, self.blue.player_name)
            )
            self.state.set("playing")
            self.turn = self.red
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    if len(command_bits) == 2:
                        self.set_size(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid size command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r"):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf"):

                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("move", "play", "mv", "pl"):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 2:
                        made_move = self.move(player, move_bits[0], move_bits[1])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("remove", "re"):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.remove(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid remove command.\n")
                    handled = True

                elif primary in ("resign",):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  Change turns and send the board to listeners.
                        self.turn = self.next_seat(self.turn)
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.red:
            return self.blue
        elif self.resigner == self.blue:
            return self.red

        # Like most connection games, we will do a recursive check.  Unlike
        # most connection games, we're looking for a lack of pieces, not
        # their existence.  In addition, if both players won at the same
        # time, the mover loses.  We need two distinct adjacency maps since
        # we're looking at blank spaces, not pieces of a given color, and
        # those blank spaces can be used by either side.
        self.blue.data.adjacency_map = []
        self.red.data.adjacency_map = []
        for i in range(self.size):
            self.blue.data.adjacency_map.append([None] * self.size)
            self.red.data.adjacency_map.append([None] * self.size)

        self.red.data.won = False
        self.blue.data.won = False

        for i in range(self.size):
            if not self.red.data.won and not self.layout.grid[0][i]:
                self.recurse(self.red, 0, i)
            if not self.blue.data.won and not self.layout.grid[i][0]:
                self.recurse(self.blue, i, 0)

        # Handle the double-win state (mover loses) first.
        if self.red.data.won and self.blue.data.won:
            if self.turn == self.red:
                return self.blue
            else:
                return self.red

        # Now, normal winning states.
        elif self.red.data.won:
            return self.red
        elif self.blue.data.won:
            return self.blue

        # No winner.
        return None

    def recurse(self, seat, row, col):

        # Bail if this seat's already won.
        if seat.data.won:
            return

        # Bail if we're off the board.
        if not self.layout.is_valid(row, col):
            return

        # Bail if we've been here.
        if seat.data.adjacency_map[row][col]:
            return

        # Bail if there's a piece here.
        if self.layout.grid[row][col]:
            return

        # All right.  Empty and we haven't been here.  Mark.
        seat.data.adjacency_map[row][col] = True

        # Did we hit the winning side for this player?
        if seat == self.blue and col == self.size - 1:
            seat.data.won = True
            return
        elif seat == self.red and row == self.size - 1:
            seat.data.won = True
            return

        # Not a win yet.  Recurse over adjacencies.
        for r_delta, c_delta in CONNECTION_DELTAS:
            self.recurse(seat, row + r_delta, col + c_delta)

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Talpa, self).show_help(player)
        player.tell_cc("\nTALPA SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("             ^!size^. <size>,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nTALPA PLAY:\n\n")
        player.tell_cc("          ^!move^. <ln> <ln2>, ^!mv^.     Move from <ln> to <ln2> (letter number).\n")
        player.tell_cc("               ^!remove^. <ln> ^!re^.     Remove piece at <ln> (letter number).\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 2
0
class Redstone(SeatedGame):
    """A Redstone game table implementation.  Invented in 2012 by Mark Steere.
    """
    def __init__(self, server, table_name):

        super(Redstone, self).__init__(server, table_name)

        self.game_display_name = "Redstone"
        self.game_name = "redstone"
        self.seats = [
            Seat("Black"),
            Seat("White"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RRedstone^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Redstone-specific stuff.
        self.height = 19
        self.width = 19
        self.turn = None
        self.black = self.seats[0]
        self.black.data.seat_str = "^KBlack^~"
        self.black.data.made_move = False
        self.white = self.seats[1]
        self.white.data.seat_str = "^WWhite^~"
        self.white.data.made_move = False
        self.resigner = None
        self.layout = None

        # Like most abstracts, Redstone doesn't need to differentiate between
        # the pieces on the board.
        self.bp = Piece("^K", "x", "X")
        self.bp.data.owner = self.black
        self.black.data.piece = self.bp
        self.wp = Piece("^W", "o", "O")
        self.wp.data.owner = self.white
        self.white.data.piece = self.wp
        self.rp = Piece("^R", "r", "R")
        self.rp.data.owner = None

        # Initialize the starting layout.
        self.init_layout()

    def init_layout(self):

        # Create the layout.  Empty, so easy.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.width, self.height)

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name,
                                                self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_bits):

        # Is there an 'x' in the middle of a single argument?
        if len(size_bits) == 1:
            size_bits[0] = size_bits[0].lower()
            if "x" in size_bits[0]:
                size_bits = size_bits[0].split("x")

        width = size_bits[0]
        # If there's a single element, height == width.
        if len(size_bits) == 1:
            height = width
        elif len(size_bits) == 2:
            width = size_bits[0]
            height = size_bits[1]
        else:
            self.tell_pre(player, "Invalid size command.\n")
            return

        if not width.isdigit() or not height.isdigit():
            self.tell_pre(player, "You didn't even send numbers!\n")
            return

        w = int(width)
        h = int(height)

        if w < MIN_SIZE or w > MAX_SIZE or h < MIN_SIZE or h > MAX_SIZE:
            self.tell_pre(
                player,
                "Width and height must be between %d and %d inclusive.\n" %
                (MIN_SIZE, MAX_SIZE))
            return

        # Valid!
        self.width = w
        self.height = h
        self.bc_pre("^R%s^~ has set the board size to ^C%d^Gx^C%d^~.\n" %
                    (player, w, h))
        self.init_layout()

    def recurse_capture(self, seat, row, col, visited):

        # If it's a dud coordinate, bail.
        if not self.layout.is_valid(row, col):
            return None

        # If we've been here, bail.
        if visited[row][col]:
            return None

        # If it's an empty space, then it's a liberty.
        pos = self.layout.grid[row][col]
        if not pos:
            return []

        # If it's a piece of the other player, bail.
        if pos.data.owner != seat:
            return None

        # Okay.  New piece, right color.  Mark it visited.
        visited[row][col] = True

        # Recurse on adjacencies.  If any return an empty list, this group has
        # a liberty and we return an empty list as well; otherwise we return a
        # concatenation of pieces found further on, for easy removal.
        return_list = [(row, col)]
        for r_delta, c_delta in CONNECTION_DELTAS:
            result = self.recurse_capture(seat, row + r_delta, col + c_delta,
                                          visited)
            if result == []:

                # Liberty!  Bail.
                return []
            elif result:

                # Found a subgroup with no liberties.  Extend.
                return_list.extend(result)

        # We never found a liberty.  Return the list of pieces.
        return return_list

    def move_is_capture(self, piece, row, col):

        # Tentatively place the piece here.
        self.layout.place(piece, row, col, update=False)

        # Build the visitation list.
        visited = []
        for r in range(self.height):
            visited.append([None] * self.width)

        # If this piece is not a redstone, we check its own liberties.  We
        # can quickly bail if this succeeds.
        if piece != self.rp:
            if self.recurse_capture(piece.data.owner, row, col, visited):
                self.layout.remove(row, col, update=False)
                return True

        # Now we check the liberties of all four adjacent locations, assuming
        # there's a piece there and it's not a redstone.
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                pos = self.layout.grid[new_r][new_c]
                if pos and pos != self.rp:

                    # We have to rebuild the visited list for each piece we
                    # check, because of the recursive "bail fast" method we use
                    # for detecting liberties.  This should be improved.
                    visited = []
                    for r in range(self.height):
                        visited.append([None] * self.width)

                    if self.recurse_capture(pos.data.owner, new_r, new_c,
                                            visited):

                        # Bail.
                        self.layout.remove(row, col, update=False)
                        return True

        # We never found a capture.  Remove and return.
        self.layout.remove(row, col, update=False)
        return False

    def move(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is there a piece already there?
        if self.layout.grid[row][col]:
            self.tell_pre(player, "There is already a piece there.\n")
            return False

        # Is it a capturing move?
        piece = seat.data.piece
        if self.move_is_capture(piece, row, col):
            self.tell_pre(player, "That would cause a capture.\n")
            return False

        # Valid.  Put a piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        self.layout.place(piece, row, col, True)

        # Update the board.
        self.bc_pre("%s places a piece at ^C%s^~.\n" %
                    (self.get_sp_str(seat), move_str))

        seat.data.made_move = True
        return True

    def capture(self, row, col):

        # If for some reason this is called and there's not a redstone at this
        # location, bail.
        if self.layout.grid[row][col] != self.rp:
            return -1

        # All right.  Check all four adjacencies; if they no longer have a
        # liberty, capture them.  Note that we come up with the list first,
        # and /then/ execute the captures, as doing them as we find them may
        # give groups liberties during the removal process.

        visited = []
        for r in range(self.height):
            visited.append([None] * self.width)

        capture_list = []
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc != self.rp:
                    captures = self.recurse_capture(loc.data.owner, new_r,
                                                    new_c, visited)
                    if captures:
                        capture_list.extend(
                            [x for x in captures if x not in capture_list])

        # Remove all pieces in the capture list.
        for capture_r, capture_c in capture_list:
            self.layout.remove(capture_r, capture_c, update=False)

        self.layout.update()

        # Return the number of pieces captured.
        return len(capture_list)

    def red(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is there a piece already there?
        if self.layout.grid[row][col]:
            self.tell_pre(player, "There is already a piece there.\n")
            return False

        # Is it not a capturing move?
        piece = self.rp
        if not self.move_is_capture(piece, row, col):
            self.tell_pre(player, "That would not cause a capture.\n")
            return False

        # Valid.  Put the piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        self.layout.place(piece, row, col, True)

        # Redstones by definition make captures.
        capture_count = self.capture(row, col)

        self.bc_pre("%s places a ^Rredstone^~ at ^C%s^~, ^Ycapturing %s^~.\n" %
                    (self.get_sp_str(seat), move_str,
                     get_plural_str(capture_count, "stone")))

        seat.data.made_move = True
        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.black.player
                and self.white.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" %
                        (self.black.data.seat_str, self.black.player_name,
                         self.white.data.seat_str, self.white.player_name))
            self.state.set("playing")
            self.turn = self.black
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    self.set_size(player, command_bits[1:])
                    handled = True

                elif primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):

                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in (
                        "move",
                        "play",
                        "mv",
                        "pl",
                ):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.move(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                if primary in (
                        "redstone",
                        "red",
                        "r",
                ):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.red(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid red command.\n")
                    handled = True

                elif primary in ("resign", ):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  Switch turns.
                        self.turn = self.next_seat(self.turn)
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.white:
            return self.black
        elif self.resigner == self.black:
            return self.white

        # If one player has no pieces left, the other player won.  If neither
        # player has a piece, mover wins.
        found_white = False
        found_black = False
        for r in range(self.height):
            for c in range(self.width):
                loc = self.layout.grid[r][c]
                if loc:
                    if loc.data.owner == self.black:
                        found_black = True
                    elif loc.data.owner == self.white:
                        found_white = True

        if not found_black and self.black.data.made_move:
            if not found_white and self.white.data.made_move:

                # Mover wins.
                return self.turn
            else:
                return self.white
        elif not found_white and self.white.data.made_move:
            return self.black

        # No winner yet.
        return None

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Redstone, self).show_help(player)
        player.tell_cc("\nREDSTONE SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "             ^!size^. <size>,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nREDSTONE PLAY:\n\n")
        player.tell_cc(
            "      ^!move^. <ln>, ^!play^., ^!mv^., ^!pl^.     Make move <ln> (letter number).\n"
        )
        player.tell_cc(
            "                  ^!red^. <ln>, ^!r^.     Place redstone at <ln> (letter number).\n"
        )
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 3
0
class Breakthrough(SeatedGame):
    """A Breakthrough game table implementation.  Invented in 2000 by Dan Troyka.
    """

    def __init__(self, server, table_name):

        super(Breakthrough, self).__init__(server, table_name)

        self.game_display_name = "Breakthrough"
        self.game_name = "breakthrough"
        self.seats = [
            Seat("Black"),
            Seat("White")
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RBreakthrough^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Breakthrough-specific stuff.
        self.width = 8
        self.height = 8
        self.turn = None
        self.black = self.seats[0]
        self.white = self.seats[1]
        self.resigner = None
        self.layout = None

        # We cheat and create a black and white piece here.  Breakthrough
        # doesn't differentiate between pieces, so this allows us to do
        # comparisons later.
        self.bp = Piece("^K", "b", "B")
        self.bp.data.owner = self.black
        self.wp = Piece("^W", "w", "W")
        self.wp.data.owner = self.white

        self.init_board()

    def init_board(self):

        # Create the layout and the pieces it uses as well.  Note that we
        # can be ultra-lazy with the pieces, as Breakthrough doesn't distinguish
        # between them, so there's no reason to create a bunch of identical
        # bits.
        self.layout = SquareGridLayout()
        self.layout.resize(self.width, self.height)
        last_row = self.height - 1
        next_last_row = last_row - 1

        for i in range(self.width):
            self.layout.place(self.bp, 0, i, update=False)
            self.layout.place(self.bp, 1, i, update=False)
            self.layout.place(self.wp, next_last_row, i, update=False)
            self.layout.place(self.wp, last_row, i, update=False)

        # Set the piece counts.
        self.white.data.piece_count = self.width * 2
        self.black.data.piece_count = self.width * 2
        self.layout.update()

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str() + "\n")

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def get_turn_str(self):

        if not self.turn:
            return ("The game has not yet started.\n")

        if self.turn == self.black:
            player = self.seats[0].player
            color_msg = "^KBlack^~"
        else:
            player = self.seats[1].player
            color_msg = "^WWhite^~"

        return ("It is ^Y%s^~'s turn (%s)." % (player, color_msg))

    def move(self, player, src, dst):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if self.turn != seat:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        src_c, src_r = src
        dst_c, dst_r = dst
        src_str = "%s%s" % (COLS[src_c], src_r + 1)
        dst_str = "%s%s" % (COLS[dst_c], dst_r + 1)

        # Make sure they're all in range.
        if (not self.layout.is_valid(src_r, src_c) or
         not self.layout.is_valid(dst_r, dst_c)):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Does the player even have a piece there?
        src_loc = self.layout.grid[src_r][src_c]
        if not src_loc or src_loc.data.owner != self.turn:
            self.tell_pre(player, "You don't have a piece at ^C%s^~.\n" % src_str)
            return False

        # Is the destination within range?
        if self.turn == self.black:
            row_delta = 1
        else:
            row_delta = -1

        if src_r + row_delta != dst_r:
            self.tell_pre(player, "You can't move from ^C%s^~ to row ^R%d^~.\n" % (src_str, dst_r + 1))
            return False
        if abs(src_c - dst_c) > 1:
            self.tell_pre(player, "You can't move from ^C%s^~ to column ^R%s^~.\n" % (src_str, COLS[dst_c]))
            return False

        # Okay, this is actually (gasp) a potentially legitimate move.  If
        # it's a move forward, it only works if the forward space is empty.
        if src_c == dst_c and self.layout.grid[dst_r][dst_c]:
            self.tell_pre(player, "A straight-forward move can only be into an empty space.\n")
            return False

        # Otherwise, it must not have one of the player's own pieces in it.
        dst_loc = self.layout.grid[dst_r][dst_c]
        if src_loc == dst_loc:
            self.tell_pre(player, "A diagonal-forward move cannot be onto your own piece.\n")
            return False

        additional_str = ""
        opponent = self.seats[0]
        if seat == self.seats[0]:
            opponent = self.seats[1]
        if dst_loc:
            # It's a capture.
            additional_str = ", capturing one of ^R%s^~'s pieces" % (opponent.player)
            opponent.data.piece_count -= 1
        self.bc_pre("^Y%s^~ moves a piece from ^C%s^~ to ^G%s^~%s.\n" % (seat.player, src_str, dst_str, additional_str))

        # Make the move on the layout.
        self.layout.move(src_r, src_c, dst_r, dst_c, True)

        return ((src_r, src_c), (dst_r, dst_c))

    def tick(self):

        # If both seats are full and the game is active, autostart.
        if (self.state.get() == "need_players" and self.seats[0].player
           and self.seats[1].player and self.active):
            self.state.set("playing")
            self.bc_pre("^KBlack^~: ^R%s^~; ^WWhite^~: ^Y%s^~\n" %
               (self.seats[0].player, self.seats[1].player))
            self.turn = self.black
            self.send_board()

    def set_size(self, player, size_bits):

        # Is there an 'x' in the middle of a single argument?
        if len(size_bits) == 1:
            size_bits[0] = size_bits[0].lower()
            if "x" in size_bits[0]:
                size_bits = size_bits[0].split("x")

        width = size_bits[0]
        # If there's a single element, height == width.
        if len(size_bits) == 1:
            height = width
        elif len(size_bits) == 2:
            width = size_bits[0]
            height = size_bits[1]
        else:
            self.tell_pre(player, "Invalid size command.\n")
            return

        if not width.isdigit() or not height.isdigit():
            self.tell_pre(player, "Invalid size command.\n")
            return

        w = int(width)
        h = int(height)

        if w < MIN_WIDTH or w > MAX_WIDTH:
            self.tell_pre(player, "Width must be between %d and %d inclusive.\n" % (MIN_WIDTH, MAX_WIDTH))
            return

        if h < MIN_HEIGHT or h > MAX_HEIGHT:
            self.tell_pre(player, "Height must be between %d and %d inclusive.\n" % (MIN_HEIGHT, MAX_HEIGHT))
            return

        # Valid!
        self.width = w
        self.height = h
        self.bc_pre("^R%s^~ has set the board size to ^C%d^Gx^C%d^~.\n" % (player, w, h))
        self.init_board()

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if self.turn != seat:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("^R%s^~ is resigning from the game.\n" % player)
        return True

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz",):

                    self.set_size(player, command_bits[1:])
                    handled = True

                if primary in ("done", "ready", "d", "r",):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):

                    self.state.set("setup")
                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("move", "play", "mv", "pl",):

                    invalid = False
                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 2:
                        made_move = self.move(player, move_bits[0], move_bits[1])
                    else:
                        invalid = True

                    if invalid:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("resign",):

                    if self.resign(player):
                        made_move = True

                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()
                    if winner:
                        self.resolve(winner)
                        self.finish()
                    else:

                        # Nope.  Switch turns...
                        self.turn = self.next_seat(self.turn)

                        # ...show everyone the board, and keep on.
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # If someone resigned, this is the easiest thing ever.  Same if
        # they lost all their pieces.
        if self.resigner == self.white or self.seats[1].data.piece_count == 0:
            return self.seats[0].player_name
        elif self.resigner == self.black or self.seats[1].data.piece_count == 0:
            return self.seats[1].player_name

        # Aw, we have to do work.  If black has a piece on the last row,
        # they win; if white has a piece on the first row, they win.
        if self.bp in self.layout.grid[-1]:
            return self.seats[0].player_name
        if self.wp in self.layout.grid[0]:
            return self.seats[1].player_name

        # ...that wasn't really much work, but there's no winner yet.
        return None

    def resolve(self, winner):
        self.send_board()
        self.bc_pre("^C%s^~ wins!\n" % winner)

    def show_help(self, player):

        super(Breakthrough, self).show_help(player)
        player.tell_cc("\nBREAKTHROUGH SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("    ^!size^. <size> | <w> <h>, ^!sz^.     Set board to <size>x<size>/<w>x<h>.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nBREAKTHROUGH PLAY:\n\n")
        player.tell_cc("          ^!move^. <ln> <ln2>, ^!mv^.     Move from <ln> to <ln2> (letter number).\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 4
0
class Redstone(SeatedGame):
    """A Redstone game table implementation.  Invented in 2012 by Mark Steere.
    """

    def __init__(self, server, table_name):

        super(Redstone, self).__init__(server, table_name)

        self.game_display_name = "Redstone"
        self.game_name = "redstone"
        self.seats = [
            Seat("Black"),
            Seat("White"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RRedstone^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Redstone-specific stuff.
        self.height = 19
        self.width = 19
        self.turn = None
        self.black = self.seats[0]
        self.black.data.seat_str = "^KBlack^~"
        self.black.data.made_move = False
        self.white = self.seats[1]
        self.white.data.seat_str = "^WWhite^~"
        self.white.data.made_move = False
        self.resigner = None
        self.layout = None

        # Like most abstracts, Redstone doesn't need to differentiate between
        # the pieces on the board.
        self.bp = Piece("^K", "x", "X")
        self.bp.data.owner = self.black
        self.black.data.piece = self.bp
        self.wp = Piece("^W", "o", "O")
        self.wp.data.owner = self.white
        self.white.data.piece = self.wp
        self.rp = Piece("^R", "r", "R")
        self.rp.data.owner = None

        # Initialize the starting layout.
        self.init_layout()

    def init_layout(self):

        # Create the layout.  Empty, so easy.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.width, self.height)

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name, self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_bits):

        # Is there an 'x' in the middle of a single argument?
        if len(size_bits) == 1:
            size_bits[0] = size_bits[0].lower()
            if "x" in size_bits[0]:
                size_bits = size_bits[0].split("x")

        width = size_bits[0]
        # If there's a single element, height == width.
        if len(size_bits) == 1:
            height = width
        elif len(size_bits) == 2:
            width = size_bits[0]
            height = size_bits[1]
        else:
            self.tell_pre(player, "Invalid size command.\n")
            return

        if not width.isdigit() or not height.isdigit():
            self.tell_pre(player, "You didn't even send numbers!\n")
            return

        w = int(width)
        h = int(height)

        if w < MIN_SIZE or w > MAX_SIZE or h < MIN_SIZE or h > MAX_SIZE:
            self.tell_pre(player, "Width and height must be between %d and %d inclusive.\n" % (MIN_SIZE, MAX_SIZE))
            return

        # Valid!
        self.width = w
        self.height = h
        self.bc_pre("^R%s^~ has set the board size to ^C%d^Gx^C%d^~.\n" % (player, w, h))
        self.init_layout()

    def recurse_capture(self, seat, row, col, visited):

        # If it's a dud coordinate, bail.
        if not self.layout.is_valid(row, col):
            return None

        # If we've been here, bail.
        if visited[row][col]:
            return None

        # If it's an empty space, then it's a liberty.
        pos = self.layout.grid[row][col]
        if not pos:
            return []

        # If it's a piece of the other player, bail.
        if pos.data.owner != seat:
            return None

        # Okay.  New piece, right color.  Mark it visited.
        visited[row][col] = True

        # Recurse on adjacencies.  If any return an empty list, this group has
        # a liberty and we return an empty list as well; otherwise we return a
        # concatenation of pieces found further on, for easy removal.
        return_list = [(row, col)]
        for r_delta, c_delta in CONNECTION_DELTAS:
            result = self.recurse_capture(seat, row + r_delta, col + c_delta, visited)
            if result == []:

                # Liberty!  Bail.
                return []
            elif result:

                # Found a subgroup with no liberties.  Extend.
                return_list.extend(result)

        # We never found a liberty.  Return the list of pieces.
        return return_list

    def move_is_capture(self, piece, row, col):

        # Tentatively place the piece here.
        self.layout.place(piece, row, col, update=False)

        # Build the visitation list.
        visited = []
        for r in range(self.height):
            visited.append([None] * self.width)

        # If this piece is not a redstone, we check its own liberties.  We
        # can quickly bail if this succeeds.
        if piece != self.rp:
            if self.recurse_capture(piece.data.owner, row, col, visited):
                self.layout.remove(row, col, update=False)
                return True

        # Now we check the liberties of all four adjacent locations, assuming
        # there's a piece there and it's not a redstone.
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                pos = self.layout.grid[new_r][new_c]
                if pos and pos != self.rp:

                    # We have to rebuild the visited list for each piece we
                    # check, because of the recursive "bail fast" method we use
                    # for detecting liberties.  This should be improved.
                    visited = []
                    for r in range(self.height):
                        visited.append([None] * self.width)

                    if self.recurse_capture(pos.data.owner, new_r, new_c, visited):

                        # Bail.
                        self.layout.remove(row, col, update=False)
                        return True

        # We never found a capture.  Remove and return.
        self.layout.remove(row, col, update=False)
        return False

    def move(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is there a piece already there?
        if self.layout.grid[row][col]:
            self.tell_pre(player, "There is already a piece there.\n")
            return False

        # Is it a capturing move?
        piece = seat.data.piece
        if self.move_is_capture(piece, row, col):
            self.tell_pre(player, "That would cause a capture.\n")
            return False

        # Valid.  Put a piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        self.layout.place(piece, row, col, True)

        # Update the board.
        self.bc_pre("%s places a piece at ^C%s^~.\n" % (self.get_sp_str(seat), move_str))

        seat.data.made_move = True
        return True

    def capture(self, row, col):

        # If for some reason this is called and there's not a redstone at this
        # location, bail.
        if self.layout.grid[row][col] != self.rp:
            return -1

        # All right.  Check all four adjacencies; if they no longer have a
        # liberty, capture them.  Note that we come up with the list first,
        # and /then/ execute the captures, as doing them as we find them may
        # give groups liberties during the removal process.

        visited = []
        for r in range(self.height):
            visited.append([None] * self.width)

        capture_list = []
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc != self.rp:
                    captures = self.recurse_capture(loc.data.owner, new_r, new_c, visited)
                    if captures:
                        capture_list.extend([x for x in captures if x not in capture_list])

        # Remove all pieces in the capture list.
        for capture_r, capture_c in capture_list:
            self.layout.remove(capture_r, capture_c, update=False)

        self.layout.update()

        # Return the number of pieces captured.
        return len(capture_list)

    def red(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is there a piece already there?
        if self.layout.grid[row][col]:
            self.tell_pre(player, "There is already a piece there.\n")
            return False

        # Is it not a capturing move?
        piece = self.rp
        if not self.move_is_capture(piece, row, col):
            self.tell_pre(player, "That would not cause a capture.\n")
            return False

        # Valid.  Put the piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        self.layout.place(piece, row, col, True)

        # Redstones by definition make captures.
        capture_count = self.capture(row, col)

        self.bc_pre("%s places a ^Rredstone^~ at ^C%s^~, ^Ycapturing %s^~.\n" % (self.get_sp_str(seat), move_str, get_plural_str(capture_count, "stone")))

        seat.data.made_move = True
        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.black.player and
           self.white.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" % (self.black.data.seat_str, self.black.player_name, self.white.data.seat_str, self.white.player_name))
            self.state.set("playing")
            self.turn = self.black
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    self.set_size(player, command_bits[1:])
                    handled = True

                elif primary in ("done", "ready", "d", "r",):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):

                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("move", "play", "mv", "pl",):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.move(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                if primary in ("redstone", "red", "r",):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.red(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid red command.\n")
                    handled = True

                elif primary in ("resign",):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  Switch turns.
                        self.turn = self.next_seat(self.turn)
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.white:
            return self.black
        elif self.resigner == self.black:
            return self.white

        # If one player has no pieces left, the other player won.  If neither
        # player has a piece, mover wins.
        found_white = False
        found_black = False
        for r in range(self.height):
            for c in range(self.width):
                loc = self.layout.grid[r][c]
                if loc:
                    if loc.data.owner == self.black:
                        found_black = True
                    elif loc.data.owner == self.white:
                        found_white = True

        if not found_black and self.black.data.made_move:
            if not found_white and self.white.data.made_move:

                # Mover wins.
                return self.turn
            else:
                return self.white
        elif not found_white and self.white.data.made_move:
            return self.black

        # No winner yet.
        return None

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Redstone, self).show_help(player)
        player.tell_cc("\nREDSTONE SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("             ^!size^. <size>,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nREDSTONE PLAY:\n\n")
        player.tell_cc("      ^!move^. <ln>, ^!play^., ^!mv^., ^!pl^.     Make move <ln> (letter number).\n")
        player.tell_cc("                  ^!red^. <ln>, ^!r^.     Place redstone at <ln> (letter number).\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 5
0
class SquareOust(SeatedGame):
    """A Square Oust game table implementation.  Invented in 2007 by Mark Steere.
    """
    def __init__(self, server, table_name):

        super(SquareOust, self).__init__(server, table_name)

        self.game_display_name = "Square Oust"
        self.game_name = "square_oust"
        self.seats = [
            Seat("Black"),
            Seat("White"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RSquare Oust^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Square Oust-specific stuff.
        self.height = 11
        self.width = 11
        self.turn = None
        self.black = self.seats[0]
        self.black.data.seat_str = "^KBlack^~"
        self.black.data.groups = []
        self.black.data.made_move = False
        self.white = self.seats[1]
        self.white.data.seat_str = "^WWhite^~"
        self.white.data.groups = []
        self.white.data.made_move = False
        self.resigner = None
        self.layout = None
        self.move_was_capture = False

        # Initialize the starting layout.
        self.init_layout()

    def init_layout(self):

        # Create the layout.  Empty, so easy.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.width, self.height)

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name,
                                                self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_bits):

        # Is there an 'x' in the middle of a single argument?
        if len(size_bits) == 1:
            size_bits[0] = size_bits[0].lower()
            if "x" in size_bits[0]:
                size_bits = size_bits[0].split("x")

        width = size_bits[0]
        # If there's a single element, height == width.
        if len(size_bits) == 1:
            height = width
        elif len(size_bits) == 2:
            width = size_bits[0]
            height = size_bits[1]
        else:
            self.tell_pre(player, "Invalid size command.\n")
            return

        if not width.isdigit() or not height.isdigit():
            self.tell_pre(player, "You didn't even send numbers!\n")
            return

        w = int(width)
        h = int(height)

        if w < MIN_SIZE or w > MAX_SIZE or h < MIN_SIZE or h > MAX_SIZE:
            self.tell_pre(
                player,
                "Width and height must be between %d and %d inclusive.\n" %
                (MIN_SIZE, MAX_SIZE))
            return

        # Valid!
        self.width = w
        self.height = h
        self.bc_pre("^R%s^~ has set the board size to ^C%d^Gx^C%d^~.\n" %
                    (player, w, h))
        self.init_layout()

    def get_new_piece(self, seat):

        if seat == self.black:
            p = Piece("^K", "x", "X")
        else:
            p = Piece("^W", "o", "O")
        p.data.owner = seat
        p.data.adjacencies = []
        p.data.size = 1
        p.data.num = id(p)

        return p

    def replace(self, old, new):

        # First step: Replace the actual pieces on the board.
        for r in range(self.height):
            for c in range(self.width):
                if self.layout.grid[r][c] == old:
                    self.layout.place(new, r, c, update=False)
        self.layout.update()

        # Second step: Get rid of it from the group list of its owner.
        owner = new.data.owner
        owner.data.groups.remove(old)

        # Third step: Replace instances of it in the other player's group
        # adjacencies.
        for group in self.next_seat(owner).data.groups:
            if old in group.data.adjacencies:
                group.data.adjacencies.remove(old)
                if new not in group.data.adjacencies:
                    group.data.adjacencies.append(new)

    def remove(self, dead_group):

        # Like above, except removing this time.
        for r in range(self.height):
            for c in range(self.width):
                if self.layout.grid[r][c] == dead_group:
                    self.layout.remove(r, c, update=False)
        self.layout.update()

        owner = dead_group.data.owner
        owner.data.groups.remove(dead_group)

        for group in self.next_seat(owner).data.groups:
            if dead_group in group.data.adjacencies:
                group.data.adjacencies.remove(dead_group)

    def update_board(self, row, col):

        # We just put a fresh piece at this location; it will have to be
        # incorporated into everything else that's on the board.
        this_piece = self.layout.grid[row][col]

        # Look at all of the adjacencies and collapse the same-color groups
        # into one.  Collate the unique enemy groups as well, as we may be
        # capturing them.
        other_adjacencies = []
        potential_capture = False
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc.data.owner == this_piece.data.owner:
                    potential_capture = True
                    if loc != this_piece:

                        # New same-color group to collapse.
                        other_adjacencies.extend([
                            x for x in loc.data.adjacencies
                            if x not in other_adjacencies
                        ])
                        new_size = this_piece.data.size + loc.data.size
                        if this_piece.data.num < loc.data.num:
                            self.replace(loc, this_piece)
                        else:
                            self.replace(this_piece, loc)
                            this_piece = loc

                        # Whichever "won," set the new size.
                        this_piece.data.size = new_size
                elif loc:

                    # A group of the other player.  Add to other adjacencies if
                    # it's not already there.
                    if loc not in other_adjacencies:
                        other_adjacencies.append(loc)

        # After having collapsed all of the same-colored groups, we look to see
        # if this is a potential capture.  If not, we can't affect the opponent's
        # groups, other than to become adjacent to them.  If so, all groups in the
        # list of other adjacencies must be removed from the board.
        if potential_capture:
            for group in other_adjacencies:
                self.remove(group)

            # By definition, a capturing group has no enemy adjacencies.
            this_piece.data.adjacencies = []

            # Return the number of groups we captured.
            return len(other_adjacencies)

        else:

            # Set the adjacency list.
            this_piece.data.adjacencies = other_adjacencies

            # Add ourselves to those pieces' adjacency lists.
            for group in other_adjacencies:
                if this_piece not in group.data.adjacencies:
                    group.data.adjacencies.append(this_piece)

            # No captures.
            return 0

    def move(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is this move a valid play?
        if not self.is_valid_play(seat, row, col):
            self.tell_pre(player, "That move is not valid.\n")
            return False

        # Valid.  Put a piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        piece = self.get_new_piece(seat)
        seat.data.groups.append(piece)
        self.layout.place(piece, row, col, True)

        # Update the board, making any captures.
        capture_str = ""
        self.move_was_capture = False
        capture_count = self.update_board(row, col)
        if capture_count:
            capture_str = ", ^Ycapturing %s^~" % (get_plural_str(
                capture_count, "group"))
            self.move_was_capture = True
        self.bc_pre("%s places a piece at ^C%s^~%s.\n" %
                    (self.get_sp_str(seat), move_str, capture_str))

        seat.data.made_move = True
        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.black.player
                and self.white.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" %
                        (self.black.data.seat_str, self.black.player_name,
                         self.white.data.seat_str, self.white.player_name))
            self.state.set("playing")
            self.turn = self.black
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    self.set_size(player, command_bits[1:])
                    handled = True

                elif primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):

                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in (
                        "move",
                        "play",
                        "mv",
                        "pl",
                ):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.move(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("resign", ):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  If the move was not a capturing move, see if the
                        # next player has a move; if so, change turns.  If not,
                        # print a message and stay here.
                        other = self.next_seat(self.turn)
                        if not self.move_was_capture:
                            if not self.has_move(other):
                                self.bc_pre(
                                    "%s has no valid move; ^Rskipping their turn^~.\n"
                                    % self.get_sp_str(other))
                            else:
                                self.turn = other

                        elif not self.has_move(self.turn):
                            self.bc_pre("%s has no further valid moves.\n" %
                                        self.get_sp_str(self.turn))
                            self.turn = other

                        else:
                            self.bc_pre("%s continues their turn.\n" %
                                        self.get_sp_str(self.turn))

                        # No matter what, send the board again.
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def is_valid_play(self, seat, row, col):

        # Obviously we can't place a piece if there's already one here or we're
        # out of bounds.
        if not self.layout.is_valid(row, col) or self.layout.grid[row][col]:
            return False

        # Okay; can we place a piece here?  Check the adjacent spaces; if there
        # are any pieces owned by this seat, the sum total of their sizes must
        # be equal to the largest enemy group adjacent either to them or this
        # new piece.  If there are no pieces owned by this seat, it's valid.
        same_list = []
        same_total = 0
        largest_other = 0

        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc.data.owner == seat:
                    if loc not in same_list:
                        same_list.append(loc)
                        same_total += loc.data.size
                        for other in loc.data.adjacencies:
                            if other.data.size > largest_other:
                                largest_other = other.data.size
                elif loc:
                    if loc.data.size > largest_other:
                        largest_other = loc.data.size

        # If we didn't find an adjacent same-colored piece, it is immediately
        # valid.
        if not same_total:
            return True

        # If we found same-colored pieces but no other groups, this is not a
        # valid play.
        if not largest_other:
            return False

        # Otherwise, check the sum from the same_list and see if that equals
        # or is greater than the largest other group.
        if same_total >= largest_other:
            return True

        # Not a valid play.
        return False

    def has_move(self, seat):

        for r in range(self.height):
            for c in range(self.width):
                if self.is_valid_play(seat, r, c):
                    return True

        # We checked every location and found no legitimate location.  No move.
        return False

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.white:
            return self.black
        elif self.resigner == self.black:
            return self.white

        # If one player has no pieces left, the other player won.
        if not len(self.white.data.groups) and self.white.data.made_move:
            return self.black
        elif not len(self.black.data.groups) and self.black.data.made_move:
            return self.white

        # No winner.
        return None

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(SquareOust, self).show_help(player)
        player.tell_cc("\nSQUARE OUST SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "             ^!size^. <size>,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nSQUARE OUST PLAY:\n\n")
        player.tell_cc(
            "      ^!move^. <ln>, ^!play^., ^!mv^., ^!pl^.     Make move <ln> (letter number).\n"
        )
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 6
0
class Tanbo(SeatedGame):
    """A Tanbo game table implementation.  Invented in 1993 by Mark Steere.
    This only implements the 2p version, although it does have the 9x9, 13x13,
    and 19x19 sizes.  There's also a 21x21 size that came from discussion with
    Mark, and 5x5 and 7x7 sizes that came naturally from the piece layouts.
    """
    def __init__(self, server, table_name):

        super(Tanbo, self).__init__(server, table_name)

        self.game_display_name = "Tanbo"
        self.game_name = "tanbo"
        self.seats = [
            Seat("Black"),
            Seat("White"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RTanbo^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Tanbo-specific stuff.
        self.size = 19
        self.turn = None
        self.black = self.seats[0]
        self.black.data.seat_str = "^KBlack^~"
        self.black.data.root_list = []
        self.white = self.seats[1]
        self.white.data.seat_str = "^WWhite^~"
        self.white.data.root_list = []
        self.resigner = None
        self.layout = None

        # Initialize the starting layout.
        self.init_layout()

    def get_root_piece(self, seat, num):
        if seat == self.black:
            p = Piece("^K", "x", "X")
        else:
            p = Piece("^W", "o", "O")

        p.data.owner = seat
        p.data.num = num
        return p

    def init_layout(self):

        # Create the layout and fill it with pieces.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.size)

        black_count = 0
        white_count = 0
        self.black.data.root_list = []
        self.white.data.root_list = []

        # There are three different layouts depending on the size.  All of them
        # alternate between black and white pieces; the two larger ones have 16
        # roots, whereas the smallest size has 4.  (There's a 5x5 grid for
        # testing as well.)
        if self.size == 5:
            jump_delta = 4
            extent = 2
            offset = 0
        elif self.size == 7:
            jump_delta = 6
            extent = 2
            offset = 0
        elif self.size == 9:
            jump_delta = 6
            extent = 2
            offset = 1
        elif self.size == 13:
            jump_delta = 4
            extent = 4
            offset = 0
        elif self.size == 19:
            jump_delta = 6
            extent = 4
            offset = 0
        else:  # size == 21
            jump_delta = 4
            extent = 6
            offset = 0
        for i in range(extent):
            for j in range(extent):
                if (i + j) % 2:
                    p = self.get_root_piece(self.black, black_count)
                    self.black.data.root_list.append(p)
                    black_count += 1
                else:
                    p = self.get_root_piece(self.white, white_count)
                    self.white.data.root_list.append(p)
                    white_count += 1
                row = offset + i * jump_delta
                col = offset + j * jump_delta
                p.data.start = (row, col)
                self.layout.place(p, row, col, update=False)
        self.layout.update()

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name,
                                                self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_str):

        if not size_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        size = int(size_str)

        if size not in (
                5,
                7,
                9,
                13,
                19,
                21,
        ):
            self.tell_pre(player, "Size must be 5, 7, 9, 13, 19, or 21.\n")
            return False

        # Valid!
        self.size = size
        self.bc_pre("^R%s^~ has set the board size to ^C%d^~.\n" %
                    (player, size))
        self.init_layout()

    def can_place_at(self, seat, row, col):

        # You can place a piece in Tanbo iff it is adjacent to exactly one of
        # your own pieces.  If a location is valid, we'll return the piece that
        # is adjacent; otherwise we return nothing.

        if self.layout.grid[row][col]:

            # Occupied; clearly can't place here.
            return None

        adj_count = 0
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc.data.owner == seat:
                    piece = loc
                    adj_count += 1

        if adj_count == 1:
            return piece
        else:
            return None

    def recurse_is_bound(self, piece, row, col, prev_row, prev_col):

        # Thanks to the way Tanbo placement works, we don't have to worry about
        # loops, so we can get away with not using an adjacenty map and instead
        # just track the direction we came from.
        #
        # Bail if this isn't a valid location.
        if not self.layout.is_valid(row, col):
            return True

        loc = self.layout.grid[row][col]

        # If there's no piece here, see if we can place one.
        if not loc:
            if self.can_place_at(piece.data.owner, row, col):

                # Yup.  This root isn't bound.
                return False

            else:

                # No, this location is binding.
                return True

        elif loc != piece:

            # Some other root.  Definitely binding.
            return True

        else:

            # Okay, it's another part of this root.  Recurse, but don't double
            # back.
            for r_delta, c_delta in CONNECTION_DELTAS:
                new_r = row + r_delta
                new_c = col + c_delta
                if new_r != prev_row or new_c != prev_col:
                    if not self.recurse_is_bound(piece, new_r, new_c, row,
                                                 col):

                        # A recursive call found a liberty.  Awesome!
                        return False

            # All of the recursive calls returned that they were bound.  This
            # (sub)root is bound.
            return True

    def root_is_bound(self, piece):

        # We'll just start recursing at the root's starting location and find
        # whether it's bound or not.
        row, col = piece.data.start
        return self.recurse_is_bound(piece, row, col, None, None)

    def kill_root(self, piece):

        # We could do this recursively, but we're lazy.
        for r in range(self.size):
            for c in range(self.size):
                loc = self.layout.grid[r][c]
                if loc == piece:
                    self.layout.remove(r, c, update=False)
        self.layout.update()

        # Remove this root from the owner's root list.
        piece.data.owner.data.root_list.remove(piece)

    def update_roots(self, row, col):

        # If the piece at row, col is part of a bounded root, that root is killed.
        piece = self.layout.grid[row][col]
        if self.root_is_bound(piece):
            self.kill_root(piece)

            # -1 indicates a suicide.
            return -1

        # Not a suicide; loop through all roots, finding bound ones.
        bound_root_list = []
        all_roots = self.black.data.root_list[:]
        all_roots.extend(self.white.data.root_list)
        for root in all_roots:
            if self.root_is_bound(root):
                bound_root_list.append(root)

        bound_count = 0
        for bound_root in bound_root_list:
            self.kill_root(bound_root)
            bound_count += 1

        # Return the number of roots we killed.
        return bound_count

    def move(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is it a valid Tanbo play?
        piece = self.can_place_at(seat, row, col)
        if not piece:
            self.tell_pre(
                player,
                "That location is not adjacent to exactly one of your pieces.\n"
            )
            return False

        # Valid.  Put the piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        self.layout.place(piece, row, col, True)

        # Update the root statuses.
        root_kill_str = ""
        root_kill = self.update_roots(row, col)
        if root_kill < 0:
            root_kill_str = ", ^ysuiciding the root^~"
        elif root_kill > 0:
            root_kill_str = ", ^Ykilling %s^~" % (get_plural_str(
                root_kill, "root"))
        self.bc_pre("%s grows a root to ^C%s^~%s.\n" %
                    (self.get_sp_str(seat), move_str, root_kill_str))

        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.black.player
                and self.white.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" %
                        (self.black.data.seat_str, self.black.player_name,
                         self.white.data.seat_str, self.white.player_name))
            self.state.set("playing")
            self.turn = self.black
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    if len(command_bits) == 2:
                        self.set_size(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid size command.\n")
                    handled = True

                elif primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):

                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in (
                        "move",
                        "play",
                        "mv",
                        "pl",
                ):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.move(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("resign", ):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  Change turns and send the board to listeners.
                        self.turn = self.next_seat(self.turn)
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.white:
            return self.black
        elif self.resigner == self.black:
            return self.white

        # If one player has no pieces left, the other player won.
        if not len(self.white.data.root_list):
            return self.black
        elif not len(self.black.data.root_list):
            return self.white

        # No winner.
        return None

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Tanbo, self).show_help(player)
        player.tell_cc("\nTANBO SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "     ^!size^. 5|7|9|13|19|21,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nTANBO PLAY:\n\n")
        player.tell_cc(
            "      ^!move^. <ln>, ^!play^., ^!mv^., ^!pl^.     Make move <ln> (letter number).\n"
        )
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 7
0
class Tanbo(SeatedGame):
    """A Tanbo game table implementation.  Invented in 1993 by Mark Steere.
    This only implements the 2p version, although it does have the 9x9, 13x13,
    and 19x19 sizes.  There's also a 21x21 size that came from discussion with
    Mark, and 5x5 and 7x7 sizes that came naturally from the piece layouts.
    """

    def __init__(self, server, table_name):

        super(Tanbo, self).__init__(server, table_name)

        self.game_display_name = "Tanbo"
        self.game_name = "tanbo"
        self.seats = [
            Seat("Black"),
            Seat("White"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RTanbo^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Tanbo-specific stuff.
        self.size = 19
        self.turn = None
        self.black = self.seats[0]
        self.black.data.seat_str = "^KBlack^~"
        self.black.data.root_list = []
        self.white = self.seats[1]
        self.white.data.seat_str = "^WWhite^~"
        self.white.data.root_list = []
        self.resigner = None
        self.layout = None

        # Initialize the starting layout.
        self.init_layout()

    def get_root_piece(self, seat, num):
        if seat == self.black:
            p = Piece("^K", "x", "X")
        else:
            p = Piece("^W", "o", "O")

        p.data.owner = seat
        p.data.num = num
        return p

    def init_layout(self):

        # Create the layout and fill it with pieces.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.size)

        black_count = 0
        white_count = 0
        self.black.data.root_list = []
        self.white.data.root_list = []

        # There are three different layouts depending on the size.  All of them
        # alternate between black and white pieces; the two larger ones have 16
        # roots, whereas the smallest size has 4.  (There's a 5x5 grid for
        # testing as well.)
        if self.size == 5:
            jump_delta = 4
            extent = 2
            offset = 0
        elif self.size == 7:
            jump_delta = 6
            extent = 2
            offset = 0
        elif self.size == 9:
            jump_delta = 6
            extent = 2
            offset = 1
        elif self.size == 13:
            jump_delta = 4
            extent = 4
            offset = 0
        elif self.size == 19:
            jump_delta = 6
            extent = 4
            offset = 0
        else: # size == 21
            jump_delta = 4
            extent = 6
            offset = 0
        for i in range(extent):
            for j in range(extent):
                if (i + j) % 2:
                    p = self.get_root_piece(self.black, black_count)
                    self.black.data.root_list.append(p)
                    black_count += 1
                else:
                    p = self.get_root_piece(self.white, white_count)
                    self.white.data.root_list.append(p)
                    white_count += 1
                row = offset + i * jump_delta
                col = offset + j * jump_delta
                p.data.start = (row, col)
                self.layout.place(p, row, col, update=False)
        self.layout.update()

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name, self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_str):

        if not size_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        size = int(size_str)

        if size not in (5, 7, 9, 13, 19, 21,):
            self.tell_pre(player, "Size must be 5, 7, 9, 13, 19, or 21.\n")
            return False

        # Valid!
        self.size = size
        self.bc_pre("^R%s^~ has set the board size to ^C%d^~.\n" % (player, size))
        self.init_layout()

    def can_place_at(self, seat, row, col):

        # You can place a piece in Tanbo iff it is adjacent to exactly one of
        # your own pieces.  If a location is valid, we'll return the piece that
        # is adjacent; otherwise we return nothing.

        if self.layout.grid[row][col]:

            # Occupied; clearly can't place here.
            return None

        adj_count = 0
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc.data.owner == seat:
                    piece = loc
                    adj_count += 1

        if adj_count == 1:
            return piece
        else:
            return None

    def recurse_is_bound(self, piece, row, col, prev_row, prev_col):

        # Thanks to the way Tanbo placement works, we don't have to worry about
        # loops, so we can get away with not using an adjacenty map and instead
        # just track the direction we came from.
        #
        # Bail if this isn't a valid location.
        if not self.layout.is_valid(row, col):
            return True

        loc = self.layout.grid[row][col]

        # If there's no piece here, see if we can place one.
        if not loc:
            if self.can_place_at(piece.data.owner, row, col):

                # Yup.  This root isn't bound.
                return False

            else:

                # No, this location is binding.
                return True

        elif loc != piece:

            # Some other root.  Definitely binding.
            return True

        else:

            # Okay, it's another part of this root.  Recurse, but don't double
            # back.
            for r_delta, c_delta in CONNECTION_DELTAS:
                new_r = row + r_delta
                new_c = col + c_delta
                if new_r != prev_row or new_c != prev_col:
                    if not self.recurse_is_bound(piece, new_r, new_c, row, col):

                        # A recursive call found a liberty.  Awesome!
                        return False

            # All of the recursive calls returned that they were bound.  This
            # (sub)root is bound.
            return True

    def root_is_bound(self, piece):

        # We'll just start recursing at the root's starting location and find
        # whether it's bound or not.
        row, col = piece.data.start
        return self.recurse_is_bound(piece, row, col, None, None)

    def kill_root(self, piece):

        # We could do this recursively, but we're lazy.
        for r in range(self.size):
            for c in range(self.size):
                loc = self.layout.grid[r][c]
                if loc == piece:
                    self.layout.remove(r, c, update=False)
        self.layout.update()

        # Remove this root from the owner's root list.
        piece.data.owner.data.root_list.remove(piece)

    def update_roots(self, row, col):

        # If the piece at row, col is part of a bounded root, that root is killed.
        piece = self.layout.grid[row][col]
        if self.root_is_bound(piece):
            self.kill_root(piece)

            # -1 indicates a suicide.
            return -1

        # Not a suicide; loop through all roots, finding bound ones.
        bound_root_list = []
        all_roots = self.black.data.root_list[:]
        all_roots.extend(self.white.data.root_list)
        for root in all_roots:
            if self.root_is_bound(root):
                bound_root_list.append(root)

        bound_count = 0
        for bound_root in bound_root_list:
            self.kill_root(bound_root)
            bound_count += 1

        # Return the number of roots we killed.
        return bound_count

    def move(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is it a valid Tanbo play?
        piece = self.can_place_at(seat, row, col)
        if not piece:
            self.tell_pre(player, "That location is not adjacent to exactly one of your pieces.\n")
            return False

        # Valid.  Put the piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        self.layout.place(piece, row, col, True)

        # Update the root statuses.
        root_kill_str = ""
        root_kill = self.update_roots(row, col)
        if root_kill < 0:
            root_kill_str = ", ^ysuiciding the root^~"
        elif root_kill > 0:
            root_kill_str = ", ^Ykilling %s^~" % (get_plural_str(root_kill, "root"))
        self.bc_pre("%s grows a root to ^C%s^~%s.\n" % (self.get_sp_str(seat), move_str, root_kill_str))

        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.black.player and
           self.white.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" % (self.black.data.seat_str, self.black.player_name, self.white.data.seat_str, self.white.player_name))
            self.state.set("playing")
            self.turn = self.black
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    if len(command_bits) == 2:
                        self.set_size(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid size command.\n")
                    handled = True

                elif primary in ("done", "ready", "d", "r",):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):

                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("move", "play", "mv", "pl",):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.move(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("resign",):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  Change turns and send the board to listeners.
                        self.turn = self.next_seat(self.turn)
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.white:
            return self.black
        elif self.resigner == self.black:
            return self.white

        # If one player has no pieces left, the other player won.
        if not len(self.white.data.root_list):
            return self.black
        elif not len(self.black.data.root_list):
            return self.white

        # No winner.
        return None

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Tanbo, self).show_help(player)
        player.tell_cc("\nTANBO SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("     ^!size^. 5|7|9|13|19|21,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nTANBO PLAY:\n\n")
        player.tell_cc("      ^!move^. <ln>, ^!play^., ^!mv^., ^!pl^.     Make move <ln> (letter number).\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 8
0
class Breakthrough(SeatedGame):
    """A Breakthrough game table implementation.  Invented in 2000 by Dan Troyka.
    """
    def __init__(self, server, table_name):

        super(Breakthrough, self).__init__(server, table_name)

        self.game_display_name = "Breakthrough"
        self.game_name = "breakthrough"
        self.seats = [Seat("Black"), Seat("White")]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RBreakthrough^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Breakthrough-specific stuff.
        self.width = 8
        self.height = 8
        self.turn = None
        self.black = self.seats[0]
        self.white = self.seats[1]
        self.resigner = None
        self.layout = None

        # We cheat and create a black and white piece here.  Breakthrough
        # doesn't differentiate between pieces, so this allows us to do
        # comparisons later.
        self.bp = Piece("^K", "b", "B")
        self.bp.data.owner = self.black
        self.wp = Piece("^W", "w", "W")
        self.wp.data.owner = self.white

        self.init_board()

    def init_board(self):

        # Create the layout and the pieces it uses as well.  Note that we
        # can be ultra-lazy with the pieces, as Breakthrough doesn't distinguish
        # between them, so there's no reason to create a bunch of identical
        # bits.
        self.layout = SquareGridLayout()
        self.layout.resize(self.width, self.height)
        last_row = self.height - 1
        next_last_row = last_row - 1

        for i in range(self.width):
            self.layout.place(self.bp, 0, i, update=False)
            self.layout.place(self.bp, 1, i, update=False)
            self.layout.place(self.wp, next_last_row, i, update=False)
            self.layout.place(self.wp, last_row, i, update=False)

        # Set the piece counts.
        self.white.data.piece_count = self.width * 2
        self.black.data.piece_count = self.width * 2
        self.layout.update()

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str() + "\n")

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def get_turn_str(self):

        if not self.turn:
            return ("The game has not yet started.\n")

        if self.turn == self.black:
            player = self.seats[0].player
            color_msg = "^KBlack^~"
        else:
            player = self.seats[1].player
            color_msg = "^WWhite^~"

        return ("It is ^Y%s^~'s turn (%s)." % (player, color_msg))

    def move(self, player, src, dst):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if self.turn != seat:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        src_c, src_r = src
        dst_c, dst_r = dst
        src_str = "%s%s" % (COLS[src_c], src_r + 1)
        dst_str = "%s%s" % (COLS[dst_c], dst_r + 1)

        # Make sure they're all in range.
        if (not self.layout.is_valid(src_r, src_c)
                or not self.layout.is_valid(dst_r, dst_c)):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Does the player even have a piece there?
        src_loc = self.layout.grid[src_r][src_c]
        if not src_loc or src_loc.data.owner != self.turn:
            self.tell_pre(player,
                          "You don't have a piece at ^C%s^~.\n" % src_str)
            return False

        # Is the destination within range?
        if self.turn == self.black:
            row_delta = 1
        else:
            row_delta = -1

        if src_r + row_delta != dst_r:
            self.tell_pre(
                player, "You can't move from ^C%s^~ to row ^R%d^~.\n" %
                (src_str, dst_r + 1))
            return False
        if abs(src_c - dst_c) > 1:
            self.tell_pre(
                player, "You can't move from ^C%s^~ to column ^R%s^~.\n" %
                (src_str, COLS[dst_c]))
            return False

        # Okay, this is actually (gasp) a potentially legitimate move.  If
        # it's a move forward, it only works if the forward space is empty.
        if src_c == dst_c and self.layout.grid[dst_r][dst_c]:
            self.tell_pre(
                player,
                "A straight-forward move can only be into an empty space.\n")
            return False

        # Otherwise, it must not have one of the player's own pieces in it.
        dst_loc = self.layout.grid[dst_r][dst_c]
        if src_loc == dst_loc:
            self.tell_pre(
                player,
                "A diagonal-forward move cannot be onto your own piece.\n")
            return False

        additional_str = ""
        opponent = self.seats[0]
        if seat == self.seats[0]:
            opponent = self.seats[1]
        if dst_loc:
            # It's a capture.
            additional_str = ", capturing one of ^R%s^~'s pieces" % (
                opponent.player)
            opponent.data.piece_count -= 1
        self.bc_pre("^Y%s^~ moves a piece from ^C%s^~ to ^G%s^~%s.\n" %
                    (seat.player, src_str, dst_str, additional_str))

        # Make the move on the layout.
        self.layout.move(src_r, src_c, dst_r, dst_c, True)

        return ((src_r, src_c), (dst_r, dst_c))

    def tick(self):

        # If both seats are full and the game is active, autostart.
        if (self.state.get() == "need_players" and self.seats[0].player
                and self.seats[1].player and self.active):
            self.state.set("playing")
            self.bc_pre("^KBlack^~: ^R%s^~; ^WWhite^~: ^Y%s^~\n" %
                        (self.seats[0].player, self.seats[1].player))
            self.turn = self.black
            self.send_board()

    def set_size(self, player, size_bits):

        # Is there an 'x' in the middle of a single argument?
        if len(size_bits) == 1:
            size_bits[0] = size_bits[0].lower()
            if "x" in size_bits[0]:
                size_bits = size_bits[0].split("x")

        width = size_bits[0]
        # If there's a single element, height == width.
        if len(size_bits) == 1:
            height = width
        elif len(size_bits) == 2:
            width = size_bits[0]
            height = size_bits[1]
        else:
            self.tell_pre(player, "Invalid size command.\n")
            return

        if not width.isdigit() or not height.isdigit():
            self.tell_pre(player, "Invalid size command.\n")
            return

        w = int(width)
        h = int(height)

        if w < MIN_WIDTH or w > MAX_WIDTH:
            self.tell_pre(
                player, "Width must be between %d and %d inclusive.\n" %
                (MIN_WIDTH, MAX_WIDTH))
            return

        if h < MIN_HEIGHT or h > MAX_HEIGHT:
            self.tell_pre(
                player, "Height must be between %d and %d inclusive.\n" %
                (MIN_HEIGHT, MAX_HEIGHT))
            return

        # Valid!
        self.width = w
        self.height = h
        self.bc_pre("^R%s^~ has set the board size to ^C%d^Gx^C%d^~.\n" %
                    (player, w, h))
        self.init_board()

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if self.turn != seat:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("^R%s^~ is resigning from the game.\n" % player)
        return True

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.split()
            primary = command_bits[0]

            if state == "setup":

                if primary in (
                        "size",
                        "sz",
                ):

                    self.set_size(player, command_bits[1:])
                    handled = True

                if primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):

                    self.state.set("setup")
                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in (
                        "move",
                        "play",
                        "mv",
                        "pl",
                ):

                    invalid = False
                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 2:
                        made_move = self.move(player, move_bits[0],
                                              move_bits[1])
                    else:
                        invalid = True

                    if invalid:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("resign", ):

                    if self.resign(player):
                        made_move = True

                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()
                    if winner:
                        self.resolve(winner)
                        self.finish()
                    else:

                        # Nope.  Switch turns...
                        self.turn = self.next_seat(self.turn)

                        # ...show everyone the board, and keep on.
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # If someone resigned, this is the easiest thing ever.  Same if
        # they lost all their pieces.
        if self.resigner == self.white or self.seats[1].data.piece_count == 0:
            return self.seats[0].player_name
        elif self.resigner == self.black or self.seats[1].data.piece_count == 0:
            return self.seats[1].player_name

        # Aw, we have to do work.  If black has a piece on the last row,
        # they win; if white has a piece on the first row, they win.
        if self.bp in self.layout.grid[-1]:
            return self.seats[0].player_name
        if self.wp in self.layout.grid[0]:
            return self.seats[1].player_name

        # ...that wasn't really much work, but there's no winner yet.
        return None

    def resolve(self, winner):
        self.send_board()
        self.bc_pre("^C%s^~ wins!\n" % winner)

    def show_help(self, player):

        super(Breakthrough, self).show_help(player)
        player.tell_cc("\nBREAKTHROUGH SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "    ^!size^. <size> | <w> <h>, ^!sz^.     Set board to <size>x<size>/<w>x<h>.\n"
        )
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nBREAKTHROUGH PLAY:\n\n")
        player.tell_cc(
            "          ^!move^. <ln> <ln2>, ^!mv^.     Move from <ln> to <ln2> (letter number).\n"
        )
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 9
0
class SquareOust(SeatedGame):
    """A Square Oust game table implementation.  Invented in 2007 by Mark Steere.
    """

    def __init__(self, server, table_name):

        super(SquareOust, self).__init__(server, table_name)

        self.game_display_name = "Square Oust"
        self.game_name = "square_oust"
        self.seats = [
            Seat("Black"),
            Seat("White"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RSquare Oust^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name, self.game_display_name)

        # Square Oust-specific stuff.
        self.height = 11
        self.width = 11
        self.turn = None
        self.black = self.seats[0]
        self.black.data.seat_str = "^KBlack^~"
        self.black.data.groups = []
        self.black.data.made_move = False
        self.white = self.seats[1]
        self.white.data.seat_str = "^WWhite^~"
        self.white.data.groups = []
        self.white.data.made_move = False
        self.resigner = None
        self.layout = None
        self.move_was_capture = False

        # Initialize the starting layout.
        self.init_layout()

    def init_layout(self):

        # Create the layout.  Empty, so easy.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.width, self.height)

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name, self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_bits):

        # Is there an 'x' in the middle of a single argument?
        if len(size_bits) == 1:
            size_bits[0] = size_bits[0].lower()
            if "x" in size_bits[0]:
                size_bits = size_bits[0].split("x")

        width = size_bits[0]
        # If there's a single element, height == width.
        if len(size_bits) == 1:
            height = width
        elif len(size_bits) == 2:
            width = size_bits[0]
            height = size_bits[1]
        else:
            self.tell_pre(player, "Invalid size command.\n")
            return

        if not width.isdigit() or not height.isdigit():
            self.tell_pre(player, "You didn't even send numbers!\n")
            return

        w = int(width)
        h = int(height)

        if w < MIN_SIZE or w > MAX_SIZE or h < MIN_SIZE or h > MAX_SIZE:
            self.tell_pre(player, "Width and height must be between %d and %d inclusive.\n" % (MIN_SIZE, MAX_SIZE))
            return

        # Valid!
        self.width = w
        self.height = h
        self.bc_pre("^R%s^~ has set the board size to ^C%d^Gx^C%d^~.\n" % (player, w, h))
        self.init_layout()

    def get_new_piece(self, seat):

        if seat == self.black:
            p = Piece("^K", "x", "X")
        else:
            p = Piece("^W", "o", "O")
        p.data.owner = seat
        p.data.adjacencies = []
        p.data.size = 1
        p.data.num = id(p)

        return p

    def replace(self, old, new):

        # First step: Replace the actual pieces on the board.
        for r in range(self.height):
            for c in range(self.width):
                if self.layout.grid[r][c] == old:
                    self.layout.place(new, r, c, update=False)
        self.layout.update()

        # Second step: Get rid of it from the group list of its owner.
        owner = new.data.owner
        owner.data.groups.remove(old)

        # Third step: Replace instances of it in the other player's group
        # adjacencies.
        for group in self.next_seat(owner).data.groups:
            if old in group.data.adjacencies:
                group.data.adjacencies.remove(old)
                if new not in group.data.adjacencies:
                    group.data.adjacencies.append(new)

    def remove(self, dead_group):

        # Like above, except removing this time.
        for r in range(self.height):
            for c in range(self.width):
                if self.layout.grid[r][c] == dead_group:
                    self.layout.remove(r, c, update=False)
        self.layout.update()

        owner = dead_group.data.owner
        owner.data.groups.remove(dead_group)

        for group in self.next_seat(owner).data.groups:
            if dead_group in group.data.adjacencies:
                group.data.adjacencies.remove(dead_group)

    def update_board(self, row, col):

        # We just put a fresh piece at this location; it will have to be
        # incorporated into everything else that's on the board.
        this_piece = self.layout.grid[row][col]

        # Look at all of the adjacencies and collapse the same-color groups
        # into one.  Collate the unique enemy groups as well, as we may be
        # capturing them.
        other_adjacencies = []
        potential_capture = False
        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc.data.owner == this_piece.data.owner:
                    potential_capture = True
                    if loc != this_piece:

                        # New same-color group to collapse.
                        other_adjacencies.extend([x for x in loc.data.adjacencies
                           if x not in other_adjacencies])
                        new_size = this_piece.data.size + loc.data.size
                        if this_piece.data.num < loc.data.num:
                            self.replace(loc, this_piece)
                        else:
                            self.replace(this_piece, loc)
                            this_piece = loc

                        # Whichever "won," set the new size.
                        this_piece.data.size = new_size
                elif loc:

                    # A group of the other player.  Add to other adjacencies if
                    # it's not already there.
                    if loc not in other_adjacencies:
                        other_adjacencies.append(loc)

        # After having collapsed all of the same-colored groups, we look to see
        # if this is a potential capture.  If not, we can't affect the opponent's
        # groups, other than to become adjacent to them.  If so, all groups in the
        # list of other adjacencies must be removed from the board.
        if potential_capture:
            for group in other_adjacencies:
                self.remove(group)

            # By definition, a capturing group has no enemy adjacencies.
            this_piece.data.adjacencies = []

            # Return the number of groups we captured.
            return len(other_adjacencies)

        else:

            # Set the adjacency list.
            this_piece.data.adjacencies = other_adjacencies

            # Add ourselves to those pieces' adjacency lists.
            for group in other_adjacencies:
                if this_piece not in group.data.adjacencies:
                    group.data.adjacencies.append(this_piece)

            # No captures.
            return 0

    def move(self, player, move_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        col, row = move_bits

        # Is the move on the board?
        if not self.layout.is_valid(row, col):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is this move a valid play?
        if not self.is_valid_play(seat, row, col):
            self.tell_pre(player, "That move is not valid.\n")
            return False

        # Valid.  Put a piece there.
        move_str = "%s%s" % (COLS[col], row + 1)
        piece = self.get_new_piece(seat)
        seat.data.groups.append(piece)
        self.layout.place(piece, row, col, True)

        # Update the board, making any captures.
        capture_str = ""
        self.move_was_capture = False
        capture_count = self.update_board(row, col)
        if capture_count:
            capture_str = ", ^Ycapturing %s^~" % (get_plural_str(capture_count, "group"))
            self.move_was_capture = True
        self.bc_pre("%s places a piece at ^C%s^~%s.\n" % (self.get_sp_str(seat), move_str, capture_str))

        seat.data.made_move = True
        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.black.player and
           self.white.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" % (self.black.data.seat_str, self.black.player_name, self.white.data.seat_str, self.white.player_name))
            self.state.set("playing")
            self.turn = self.black
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    self.set_size(player, command_bits[1:])
                    handled = True

                elif primary in ("done", "ready", "d", "r",):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in ("config", "setup", "conf",):

                    self.bc_pre("^R%s^~ has switched the game to setup mode.\n" % player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in ("move", "play", "mv", "pl",):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.move(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in ("resign",):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  If the move was not a capturing move, see if the
                        # next player has a move; if so, change turns.  If not,
                        # print a message and stay here.
                        other = self.next_seat(self.turn)
                        if not self.move_was_capture:
                            if not self.has_move(other):
                                self.bc_pre("%s has no valid move; ^Rskipping their turn^~.\n" % self.get_sp_str(other))
                            else:
                                self.turn = other

                        elif not self.has_move(self.turn):
                            self.bc_pre("%s has no further valid moves.\n" % self.get_sp_str(self.turn))
                            self.turn = other

                        else:
                            self.bc_pre("%s continues their turn.\n" % self.get_sp_str(self.turn))

                        # No matter what, send the board again.
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def is_valid_play(self, seat, row, col):

        # Obviously we can't place a piece if there's already one here or we're
        # out of bounds.
        if not self.layout.is_valid(row, col) or self.layout.grid[row][col]:
            return False

        # Okay; can we place a piece here?  Check the adjacent spaces; if there
        # are any pieces owned by this seat, the sum total of their sizes must
        # be equal to the largest enemy group adjacent either to them or this
        # new piece.  If there are no pieces owned by this seat, it's valid.
        same_list = []
        same_total = 0
        largest_other = 0

        for r_delta, c_delta in CONNECTION_DELTAS:
            new_r = row + r_delta
            new_c = col + c_delta
            if self.layout.is_valid(new_r, new_c):
                loc = self.layout.grid[new_r][new_c]
                if loc and loc.data.owner == seat:
                    if loc not in same_list:
                        same_list.append(loc)
                        same_total += loc.data.size
                        for other in loc.data.adjacencies:
                            if other.data.size > largest_other:
                                largest_other = other.data.size
                elif loc:
                    if loc.data.size > largest_other:
                        largest_other = loc.data.size

        # If we didn't find an adjacent same-colored piece, it is immediately
        # valid.
        if not same_total:
            return True

        # If we found same-colored pieces but no other groups, this is not a
        # valid play.
        if not largest_other:
            return False

        # Otherwise, check the sum from the same_list and see if that equals
        # or is greater than the largest other group.
        if same_total >= largest_other:
            return True

        # Not a valid play.
        return False

    def has_move(self, seat):

        for r in range(self.height):
            for c in range(self.width):
                if self.is_valid_play(seat, r, c):
                    return True

        # We checked every location and found no legitimate location.  No move.
        return False

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.white:
            return self.black
        elif self.resigner == self.black:
            return self.white

        # If one player has no pieces left, the other player won.
        if not len(self.white.data.groups) and self.white.data.made_move:
            return self.black
        elif not len(self.black.data.groups) and self.black.data.made_move:
            return self.white

        # No winner.
        return None

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(SquareOust, self).show_help(player)
        player.tell_cc("\nSQUARE OUST SETUP PHASE:\n\n")
        player.tell_cc("          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n")
        player.tell_cc("             ^!size^. <size>,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc("            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n")
        player.tell_cc("\nSQUARE OUST PLAY:\n\n")
        player.tell_cc("      ^!move^. <ln>, ^!play^., ^!mv^., ^!pl^.     Make move <ln> (letter number).\n")
        player.tell_cc("                       ^!resign^.     Resign.\n")
Ejemplo n.º 10
0
class Talpa(SeatedGame):
    """A Talpa game table implementation.  Invented in 2010 by Arty Sandler.
    """
    def __init__(self, server, table_name):

        super(Talpa, self).__init__(server, table_name)

        self.game_display_name = "Talpa"
        self.game_name = "talpa"
        self.seats = [
            Seat("Red"),
            Seat("Blue"),
        ]
        self.min_players = 2
        self.max_players = 2
        self.state = State("need_players")
        self.prefix = "(^RTalpa^~): "
        self.log_prefix = "%s/%s: " % (self.table_display_name,
                                       self.game_display_name)

        # Talpa-specific stuff.
        self.size = 8
        self.turn = None
        self.red = self.seats[0]
        self.red.data.seat_str = "^RRed/Vertical^~"
        self.blue = self.seats[1]
        self.blue.data.seat_str = "^BBlue/Horizontal^~"
        self.resigner = None
        self.layout = None

        # Like in most connection games, there is no difference between pieces
        # of a given color, so we save time and create our singleton pieces
        # here.
        self.rp = Piece("^R", "x", "X")
        self.rp.data.owner = self.red
        self.bp = Piece("^B", "o", "O")
        self.bp.data.owner = self.blue

        # Initialize the starting layout.
        self.init_layout()

    def init_layout(self):

        # Create the layout and fill it with pieces.
        self.layout = SquareGridLayout(highlight_color="^I")
        self.layout.resize(self.size)

        for i in range(self.size):
            for j in range(self.size):
                if (i + j) % 2:
                    self.layout.place(self.rp, i, j, update=False)
                else:
                    self.layout.place(self.bp, i, j, update=False)

        self.layout.update()

    def get_sp_str(self, seat):

        return "^C%s^~ (%s)" % (seat.player_name, seat.data.seat_str)

    def get_turn_str(self):

        if not self.turn:
            return "The game has not yet started.\n"

        return "It is ^C%s^~'s turn (%s).\n" % (self.turn.player_name,
                                                self.turn.data.seat_str)

    def show(self, player):

        player.tell_cc(self.layout)
        player.tell_cc(self.get_turn_str())

    def send_board(self):

        for player in self.channel.listeners:
            self.show(player)

    def set_size(self, player, size_str):

        if not size_str.isdigit():
            self.tell_pre(player, "You didn't even send a number!\n")
            return False

        size = int(size_str)

        if size < MIN_SIZE or size > MAX_SIZE:
            self.tell_pre(
                player, "Size must be between %d and %d inclusive.\n" %
                (MIN_SIZE, MAX_SIZE))
            return False

        # Size must be even.
        if size % 2:
            self.tell_pre(player, "Size must be even.\n")
            return False

        # Valid!
        self.size = size
        self.bc_pre("^R%s^~ has set the board size to ^C%d^~.\n" %
                    (player, size))
        self.init_layout()

    def move(self, player, src_bits, dst_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't move; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to move.\n")
            return False

        src_c, src_r = src_bits
        dst_c, dst_r = dst_bits

        # Are they valid?
        if (not self.layout.is_valid(src_r, src_c)
                or not self.layout.is_valid(dst_r, dst_c)):
            self.tell_pre(player, "Your move is out of bounds.\n")
            return False

        # Is it an orthogonal move?
        c_delta = abs(src_c - dst_c)
        r_delta = abs(src_r - dst_r)
        if c_delta > 1 or r_delta > 1 or (c_delta and r_delta):
            self.tell_pre(
                player, "You can only make a single-space orthogonal move.\n")
            return False

        # Is there a piece for this player in the source location?
        src_loc = self.layout.grid[src_r][src_c]
        if not src_loc or src_loc.data.owner != seat:
            self.tell_pre(player,
                          "You must have a piece in the source location.\n")
            return False

        # Is there a piece for the other player in the destination location?
        dst_loc = self.layout.grid[dst_r][dst_c]
        if not dst_loc or dst_loc.data.owner == seat:
            self.tell_pre(
                player,
                "Your opponent must have a piece in the destination location.\n"
            )
            return False

        # Phew.  Success.  Make the capture.
        src_str = "%s%s" % (COLS[src_c], src_r + 1)
        dst_str = "%s%s" % (COLS[dst_c], dst_r + 1)
        self.bc_pre("%s moves a piece from ^C%s^~ to ^G%s^~.\n" %
                    (self.get_sp_str(seat), src_str, dst_str))
        self.layout.move(src_r, src_c, dst_r, dst_c, True)

        return True

    def has_capture(self, seat):

        # We loop through the board, checking each piece to see if it's for
        # this player.  If so, we check its four adjacencies to see if one
        # of them is for the other player.  If so, there's still a valid
        # capture this player can make.
        for r in range(self.size):
            for c in range(self.size):
                loc = self.layout.grid[r][c]
                if loc and loc.data.owner == seat:
                    for r_delta, c_delta in CONNECTION_DELTAS:
                        new_r = r + r_delta
                        new_c = c + c_delta
                        if self.layout.is_valid(new_r, new_c):
                            dst = self.layout.grid[new_r][new_c]
                            if dst and dst.data.owner != seat:
                                return True

        # We never found a valid capture.
        return False

    def remove(self, player, remove_bits):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't remove; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to remove.\n")
            return False

        c, r = remove_bits

        # Is it within bounds?
        if not self.layout.is_valid(r, c):
            self.tell_pre(player, "Your remove is out of bounds.\n")
            return False

        # Does this player have a piece there?
        loc = self.layout.grid[r][c]
        if not loc or loc.data.owner != seat:
            self.tell_pre(player, "You must have a piece there to remove.\n")
            return False

        # Do they have a valid capture instead?
        if self.has_capture(seat):
            self.tell_pre(player, "You have a capture left.\n")
            return False

        # All right, remove the piece.
        loc_str = "%s%s" % (COLS[c], r + 1)
        self.bc_pre("%s removes a piece from ^R%s^~.\n" %
                    (self.get_sp_str(seat), loc_str))
        self.layout.remove(r, c, True)

        return True

    def resign(self, player):

        seat = self.get_seat_of_player(player)
        if not seat:
            self.tell_pre(player, "You can't resign; you're not playing!\n")
            return False

        if seat != self.turn:
            self.tell_pre(player, "You must wait for your turn to resign.\n")
            return False

        self.resigner = seat
        self.bc_pre("%s is resigning from the game.\n" % self.get_sp_str(seat))
        return True

    def tick(self):

        # If both seats are occupied and the game is active, start.
        if (self.state.get() == "need_players" and self.red.player
                and self.blue.player and self.active):
            self.bc_pre("%s: ^C%s^~; %s: ^C%s^~\n" %
                        (self.red.data.seat_str, self.red.player_name,
                         self.blue.data.seat_str, self.blue.player_name))
            self.state.set("playing")
            self.turn = self.red
            self.send_board()

    def handle(self, player, command_str):

        # Handle common commands.
        handled = self.handle_common_commands(player, command_str)

        if not handled:

            state = self.state.get()
            command_bits = command_str.lower().split()
            primary = command_bits[0]

            if state == "setup":

                if primary in ("size", "sz"):

                    if len(command_bits) == 2:
                        self.set_size(player, command_bits[1])
                    else:
                        self.tell_pre(player, "Invalid size command.\n")
                    handled = True

                elif primary in (
                        "done",
                        "ready",
                        "d",
                        "r",
                ):

                    self.bc_pre("The game is now looking for players.\n")
                    self.state.set("need_players")
                    handled = True

            elif state == "need_players":

                if primary in (
                        "config",
                        "setup",
                        "conf",
                ):

                    self.bc_pre(
                        "^R%s^~ has switched the game to setup mode.\n" %
                        player)
                    self.state.set("setup")
                    handled = True

            elif state == "playing":

                made_move = False

                if primary in (
                        "move",
                        "play",
                        "mv",
                        "pl",
                ):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 2:
                        made_move = self.move(player, move_bits[0],
                                              move_bits[1])
                    else:
                        self.tell_pre(player, "Invalid move command.\n")
                    handled = True

                elif primary in (
                        "remove",
                        "re",
                ):

                    move_bits = demangle_move(command_bits[1:])
                    if move_bits and len(move_bits) == 1:
                        made_move = self.remove(player, move_bits[0])
                    else:
                        self.tell_pre(player, "Invalid remove command.\n")
                    handled = True

                elif primary in ("resign", ):

                    made_move = self.resign(player)
                    handled = True

                if made_move:

                    # Did someone win?
                    winner = self.find_winner()

                    if winner:

                        # Yup!
                        self.resolve(winner)
                        self.finish()
                    else:

                        # No.  Change turns and send the board to listeners.
                        self.turn = self.next_seat(self.turn)
                        self.send_board()

        if not handled:
            self.tell_pre(player, "Invalid command.\n")

    def find_winner(self):

        # Did someone resign?
        if self.resigner == self.red:
            return self.blue
        elif self.resigner == self.blue:
            return self.red

        # Like most connection games, we will do a recursive check.  Unlike
        # most connection games, we're looking for a lack of pieces, not
        # their existence.  In addition, if both players won at the same
        # time, the mover loses.  We need two distinct adjacency maps since
        # we're looking at blank spaces, not pieces of a given color, and
        # those blank spaces can be used by either side.
        self.blue.data.adjacency_map = []
        self.red.data.adjacency_map = []
        for i in range(self.size):
            self.blue.data.adjacency_map.append([None] * self.size)
            self.red.data.adjacency_map.append([None] * self.size)

        self.red.data.won = False
        self.blue.data.won = False

        for i in range(self.size):
            if not self.red.data.won and not self.layout.grid[0][i]:
                self.recurse(self.red, 0, i)
            if not self.blue.data.won and not self.layout.grid[i][0]:
                self.recurse(self.blue, i, 0)

        # Handle the double-win state (mover loses) first.
        if self.red.data.won and self.blue.data.won:
            if self.turn == self.red:
                return self.blue
            else:
                return self.red

        # Now, normal winning states.
        elif self.red.data.won:
            return self.red
        elif self.blue.data.won:
            return self.blue

        # No winner.
        return None

    def recurse(self, seat, row, col):

        # Bail if this seat's already won.
        if seat.data.won:
            return

        # Bail if we're off the board.
        if not self.layout.is_valid(row, col):
            return

        # Bail if we've been here.
        if seat.data.adjacency_map[row][col]:
            return

        # Bail if there's a piece here.
        if self.layout.grid[row][col]:
            return

        # All right.  Empty and we haven't been here.  Mark.
        seat.data.adjacency_map[row][col] = True

        # Did we hit the winning side for this player?
        if seat == self.blue and col == self.size - 1:
            seat.data.won = True
            return
        elif seat == self.red and row == self.size - 1:
            seat.data.won = True
            return

        # Not a win yet.  Recurse over adjacencies.
        for r_delta, c_delta in CONNECTION_DELTAS:
            self.recurse(seat, row + r_delta, col + c_delta)

    def resolve(self, winner):

        self.send_board()
        self.bc_pre("%s wins!\n" % self.get_sp_str(winner))

    def show_help(self, player):

        super(Talpa, self).show_help(player)
        player.tell_cc("\nTALPA SETUP PHASE:\n\n")
        player.tell_cc(
            "          ^!setup^., ^!config^., ^!conf^.     Enter setup phase.\n"
        )
        player.tell_cc(
            "             ^!size^. <size>,  ^!sz^.     Set board to <size>.\n")
        player.tell_cc(
            "            ^!ready^., ^!done^., ^!r^., ^!d^.     End setup phase.\n"
        )
        player.tell_cc("\nTALPA PLAY:\n\n")
        player.tell_cc(
            "          ^!move^. <ln> <ln2>, ^!mv^.     Move from <ln> to <ln2> (letter number).\n"
        )
        player.tell_cc(
            "               ^!remove^. <ln> ^!re^.     Remove piece at <ln> (letter number).\n"
        )
        player.tell_cc("                       ^!resign^.     Resign.\n")