Advertisement
Guest User

Othello

a guest
Oct 11th, 2024
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.54 KB | None | 0 0
  1. import random
  2.  
  3. class Board:
  4.     """
  5.    Othello board with pieces in the self.pieces dict.
  6.    self.pieces format is such that {(x, y): c}, where c is 0 for empty,
  7.    1 for white and 2 for black. x and y range from 0-7.
  8.    """
  9.     def __init__(self):
  10.         self.pieces = {}
  11.         self.setup_board()
  12.  
  13.     def setup_board(self):
  14.         """Sets up starting board."""
  15.         for x in range(8):
  16.             for y in range(8):
  17.                 self.pieces[(x, y)] = 0
  18.         self.pieces[(3, 4)] = self.pieces[(4, 3)] = 1
  19.         self.pieces[(3, 3)] = self.pieces[(4, 4)] = 2
  20.  
  21.     def draw(self, paint=False, list_of_points=None, marker="x"):
  22.         """Draws board. If paint is enabled, a list of (x,y) points will shown with the
  23.        marker x for debugging purposes."""
  24.         # ┐ └ ─ ┘ ┌ │ ■ □
  25.         print("  x 0 1 2 3 4 5 6 7  ")
  26.         print("y ┌─────────────────┐")
  27.         for y in range(0, 8):
  28.             print(y, "│ ", end="")
  29.             for x in range(0, 8):
  30.                 if self.pieces[(x,y)] == 0:
  31.                     c = " "
  32.                 elif self.pieces[(x,y)] == 1:
  33.                     c = "■"
  34.                 else:
  35.                     c = "□"
  36.                 if paint:
  37.                     if (x,y) in list_of_points:
  38.                         c = marker
  39.                 print(c + " ", end="")
  40.             print("│")
  41.         print("  └─────────────────┘")
  42.  
  43.     def place(self, x, y, player):
  44.         assert 0 <= x <= 7 and 0 <= y <= 7
  45.         self.pieces[(x, y)] = player
  46.  
  47.     def switch(self, x, y):
  48.         """Switches marker from black to white and vice versa."""
  49.         if self.pieces[(x, y)] == 1:
  50.             self.pieces[(x, y)] = 2
  51.         elif self.pieces[(x, y)] == 2:
  52.             self.pieces[(x, y)] = 1
  53.         else:
  54.             raise ValueError("Tried to switch an invalid piece")
  55.  
  56.     def board_is_full(self):
  57.         return len([x for x in self.pieces.values() if x != 0]) == 64
  58.  
  59.     def get_score(self):
  60.         white_score = len([c for c in self.pieces.values() if c == 1])
  61.         black_score = len([c for c in self.pieces.values() if c == 2])
  62.         return white_score, black_score
  63.  
  64.     def position_is_occupied(self, x, y):
  65.         return self.pieces[(x,y)] != 0
  66.  
  67.     def get_populated_neighbours(self, x, y):
  68.         """Returns list of (x,y) where a marker is placed."""
  69.         neighbours = [(x - 1, y - 1),
  70.                       (x, y - 1),
  71.                       (x + 1, y - 1),
  72.                       (x - 1, y),
  73.                       (x + 1, y),
  74.                       (x - 1, y + 1),
  75.                       (x, y + 1),
  76.                       (x + 1, y + 1),
  77.                       ]
  78.         populated_neighbours = []
  79.         for n in neighbours:
  80.             if n in self.pieces:
  81.                 if self.pieces[n] != 0:
  82.                     populated_neighbours.append(n)
  83.         return populated_neighbours
  84.  
  85.     def get_pieces_taken(self, x, y, player):
  86.         """
  87.        Returns all pieces that are taken if this players piece is placed here. For optimization, this
  88.        method tries to return None as early as possible if no pieces can be taken here.
  89.  
  90.        :param x: X position of placed piece.
  91.        :param y: Y position of placed piece.
  92.        :param player: 1 if placed piece is white, 2 if black.
  93.        :return: A dict where the keys are adjacent pieces that will be taken if a piece is placed here,
  94.        and the values are lists of all pieces that will be taken in that direction (including the piece
  95.        in the key). Returns None if no pieces can be taken.
  96.        """
  97.  
  98.         # check self
  99.         if self.position_is_occupied(x, y):
  100.             return None
  101.  
  102.         # check if neighbours are empty
  103.         populated_neighbours = self.get_populated_neighbours(x, y)
  104.         if not populated_neighbours:
  105.             return None
  106.  
  107.         # check if populated neighbours are of same type
  108.         populated_neighbours_of_other_type = [n for n in populated_neighbours if self.pieces[n] == opponent_to(player)]
  109.         if not populated_neighbours_of_other_type:
  110.             return None
  111.  
  112.         pieces_taken = {n: [n] for n in populated_neighbours_of_other_type}
  113.         #Looks in all 8 directions to see if the line can be taken.
  114.         for key, pieces_list in pieces_taken.items():
  115.             a, b = key
  116.             dx = a - x
  117.             dy = b - y
  118.             next_piece = (a + dx, b + dy)
  119.             while True:
  120.                 # PIECE OUTSIDE BOARD: None are taken.
  121.                 if next_piece not in self.pieces:
  122.                     pieces_list.clear()
  123.                     break
  124.                 # PIECE IS SELF: This line ends.
  125.                 if self.pieces[next_piece] == player:
  126.                     break
  127.                 # PIECE IS EMPTY: None are taken.
  128.                 if self.pieces[next_piece] == 0:
  129.                     pieces_list.clear()
  130.                     break
  131.                 # PIECE IS OPPONENT: This piece is taken, and next is checked.
  132.                 if self.pieces[next_piece] == opponent_to(player):
  133.                     pieces_list.append(next_piece)
  134.                 next_piece = (next_piece[0] + dx, next_piece[1] + dy)
  135.  
  136.         if any(pieces_taken.values()):
  137.             return pieces_taken
  138.         return None
  139.  
  140.     def read_board(self, board_str):
  141.         """Reads a board as a multi line string, the same format as it is drawn in self.draw(). For debugging."""
  142.         for line in board_str.splitlines():
  143.             if not line:
  144.                 continue
  145.             if line[0] in ("01234567"):
  146.                 y = int(line[0])
  147.                 for i in range(4, 19, 2):
  148.                     c = line[i]
  149.                     x = (i-4)//2
  150.                     if c == " ":
  151.                         a = 0
  152.                     if c == "■":
  153.                         a = 1
  154.                     if c == "□":
  155.                         a = 2
  156.                     self.pieces[(x, y)] = a
  157.  
  158. class AI:
  159.     def __init__(self, player, board):
  160.         self.player = player
  161.         self.board = board
  162.         self.color = "white" if self.player == 1 else "black"
  163.  
  164.     def __str__(self):
  165.         return "AI"
  166.  
  167.     def produce_move(self):
  168.         """
  169.        Main AI move generator. Currently it chooses randomly from all moves that are valid,
  170.        but this method can be made more advanced.
  171.        :return: (x, y) for a move, or "forfeit" if no moves are valid.
  172.        """
  173.         possible_placements = self.get_all_possible_placements()
  174.         if not possible_placements:
  175.             return "forfeit"
  176.         move = random.choice(possible_placements)
  177.         print("AI moves to:", move)
  178.         return move
  179.  
  180.     def get_all_possible_placements(self):
  181.         placements = []
  182.         for y in range(8):
  183.             for x in range(8):
  184.                 placement = self.board.get_pieces_taken(x, y, self.player)
  185.                 if placement is not None:
  186.                     placements.append((x, y))
  187.         return placements
  188.  
  189. class Human:
  190.     def __init__(self, player, board):
  191.         self.player = player
  192.         self.board = board
  193.         self.color = "white" if self.player == 1 else "black"
  194.  
  195.     def __str__(self):
  196.         return "Human"
  197.  
  198.     def produce_move(self):
  199.         print("Human move. Input 'exit' to quit, 'forfeit' to forfeit this move.")
  200.         player_str = "White" if self.player == 1 else "Black"
  201.         while True:
  202.             i = input(f"{player_str}: xy - ")
  203.             if i in ("exit", "forfeit"):
  204.                 return i
  205.             try:
  206.                 i = [c for c in i if c in "0123456789"]
  207.                 x, y = int(i[0]), int(i[1])
  208.                 if not (0 <= x <= 7 and 0 <= y <= 7):
  209.                     print("Outside board.")
  210.                     continue
  211.             except:
  212.                 print("Invalid input.")
  213.                 continue
  214.             if self.board.get_pieces_taken(x, y, self.player) is None:
  215.                 print("This move takes no pieces.")
  216.             else:
  217.                 return x, y
  218.  
  219.  
  220. class Player:
  221.     """
  222.    Player class. playertype can be of class Human() or AI().
  223.    """
  224.     def __init__(self, playertype):
  225.         self.playertype = playertype
  226.         self.no_moves_available = False
  227.  
  228.     def __str__(self):
  229.         return self.playertype.__str__()
  230.  
  231.     def produce_move(self):
  232.         return self.playertype.produce_move()
  233.  
  234.     def get_player(self):
  235.         return self.playertype.player
  236.  
  237.     def get_color(self):
  238.         return self.playertype.color
  239.  
  240. def opponent_to(player):
  241.     """Returns the opponent to the player."""
  242.     if player == 1:
  243.         return 2
  244.     elif player == 2:
  245.         return 1
  246.     else:
  247.         raise ValueError("Not a valid player")
  248.  
  249.  
  250. def main():
  251.     b = Board()
  252.     p1 = Player(Human(2, b)) # Black
  253.     p2 = Player(AI(1, b)) # White
  254.     players = (p1, p2)
  255.     print("STARTING GAME")
  256.     running = True
  257.     while running:
  258.         print()
  259.         print(f"{p1} is {p1.get_color()}, {p2} is {p2.get_color()}.")
  260.         for p in players:
  261.             # Check if game is won.
  262.             if all((p1.no_moves_available, p2.no_moves_available)) or b.board_is_full():
  263.                 print()
  264.                 white_score, black_score = b.get_score()
  265.                 if white_score == black_score:
  266.                     print(f"Game is a tie. White has {white_score} pieces, black has {black_score} pieces.")
  267.                 else:
  268.                     winner = "white" if white_score > black_score else "black"
  269.                     print(f"Game won by {winner}. White has {white_score} pieces, black has {black_score} pieces.")
  270.                 print("Final board:")
  271.                 b.draw()
  272.                 running = False
  273.             if not running:
  274.                 break
  275.             b.draw()
  276.             move = p.produce_move()
  277.             # Check if player exited.
  278.             if move == "exit":
  279.                 running = False
  280.                 break
  281.             if move == "forfeit":
  282.                 print("Move forfeited.")
  283.                 p.no_moves_available = True # If both player forfeited, game ends.
  284.                 continue
  285.             p.no_moves_available = False
  286.             x, y = move
  287.             pieces_taken = b.get_pieces_taken(x, y, p.get_player()) # Check what pieces are taken.
  288.             b.place(x, y, p.get_player()) # Place the one piece.
  289.             for direction in pieces_taken.values(): # Switch the pieces that are taken.
  290.                 for x, y in direction:
  291.                     b.switch(x, y)
  292.  
  293.  
  294. main()
  295.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement