예제 #1
0
파일: talpa.py 프로젝트: sunfall/giles
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")
예제 #2
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")
예제 #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")
예제 #4
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")