from random import randint NUMBER_OF_MARKS_FOR_WIN = 4 """ Schéma tříd a jejich metod/atributů: Field ├─────────────────────────────── ├─ __init__() ├─ is_empty() ├─ __str__() ├─────────────────────────────── ├─> mark └─────────────────────────────── Board ├─────────────────────────────── ├─ __init__(rows, cols) ├─ __str__() ├─ get_column(i) ├─ get_forward_diagonal(i) ├─ get_backward_diagonal(i) ├─ get_all_forward_diagonals(i) ├─ get_all_backward_diagonals(i) ├─ is_valid_choice(col) ├─ add_mark(col, mark) ├─ is_board_full() ├─ is_line_winning(line) ├─ are_rows_winning() ├─ are_cols_winning() ├─ are_diagonals_winning() ├─ is_win() ├─────────────────────────────── ├─> cols ├─> fields ├─> rows └─────────────────────────────── HumanPlayer ├─────────────────────────────── ├─ __init__(name) ├─ strategy(board, mark) ├─────────────────────────────── ├─> name └─────────────────────────────── RandomComputerPlayer ├─────────────────────────────── ├─ __init__(name) ├─ strategy(board, mark) ├─────────────────────────────── ├─> name └─────────────────────────────── Game ├─────────────────────────────── ├─ __init__(rows, cols, output=True) ├─ play(player1, player2) ├─────────────────────────────── ├─> statistics ├─> rows ├─> cols └─────────────────────────────── """ class Field(object): """ Represent one field of the game board. """ def __init__(self): """ Konstruktor -> metoda spuštěná při vytvoření objektu. Zde je možné vytvořit atribury (vlastnosti) třídy. Konstruktor může dostávat parametry jako běžná funkce. self.parametr = "nejaka hodnota" První parametr je samotný objekt a nepředává se při volání funkce (vytváření objektu: my_field = Field() """ self.mark = None def is_empty(self): """ Return True, if the field is empty. """ return self.mark is None def __str__(self): """ Nutné vrátit stringovou reprezentaci objektu. Využívá se při str(my_field) nebo print(my_field). Return the mark or dot for the empty field. """ if self.is_empty(): return "." return self.mark class Board(object): """ Represent a board for the tictactoe game. """ def __init__(self, rows, cols): """ Construct new board from the given rows and cols number. """ self.rows = rows self.cols = cols # Creation of the list of lists. # (list of rows with fields) # # self.fields contain following structure: # [ # [Field(), Field(), ...], # [Field(), Field(), ...], # : # [Field(), Field(), ...] # ] self.fields = [[Field() for _ in range(cols)] for _ in range(rows)] def __str__(self): """ Nicely formated board. """ result = "" for row in self.fields: for field in row: # Concatenate the string representation of each field result += str(field) + " " # end of row, jump to new line result += "\n" return result def get_column(self, i): """ Return one column of the board. """ result = [] for row in self.fields: result.append(row[i]) return result # One-liner: # return [row[i] for row in self.fields] def get_forward_diagonal(self, i): """ Return diagonal starting at i-th column. """ result = [] for j in range(min(self.cols - i, self.rows)): if i + j >= 0: result.append(self.fields[j][i + j]) return result def get_backward_diagonal(self, i): """ Return backward diagonal starting at i-th column. """ result = [] for j in range(0, self.rows): column = i - j if column >= 0 and column < self.cols: result.append(self.fields[j][column]) return result def get_all_forward_diagonals(self): """ Return all diagonals. # Going throw all diagonals # and mark the fields with the number: board = Board(rows=4, cols=7) for index, diagonal in enumerate(board.get_all_diagonals()): for field in diagonal: field.mark = str(index) print(board) """ result = [] for i in range(-1 * self.rows, self.cols): result.append(self.get_forward_diagonal(i)) return result def get_all_backward_diagonals(self): """ Return all backward diagonals. """ result = [] for i in range(self.cols + self.rows - 1): result.append(self.get_backward_diagonal(i)) return result def is_valid_choice(self, col): """ Return True, if the choice is valid. """ return 0 <= col and col < self.cols and self.fields[0][col].is_empty() # or: # return 0 <= col < self.rows and self.fields[0][col].is_empty() def add_mark(self, col, mark): """ Add new mark to the field. """ if not self.is_valid_choice(col): print("Out of the board!") return selected_column = self.get_column(col) for field in reversed(selected_column): if field.is_empty(): field.mark = mark return def is_board_full(self): """ Return True, if the board is full. """ for row in self.fields: if row[0].is_empty(): return False return True def is_line_winning(self, line): """ Return True, if the line contains sequence of four same marks. """ last_mark = None mark_count = 0 for field in line: if field.mark == last_mark: if field.is_empty(): # empty mark cannot win continue mark_count += 1 if mark_count == NUMBER_OF_MARKS_FOR_WIN: return True else: last_mark = field.mark mark_count = 1 # No sequence of four same marks return False def are_rows_winning(self): """ Test the rows for win. """ for row in self.fields: if self.is_line_winning(row): return True return False def are_cols_winning(self): """ Test the columns for win. """ for col_number in range(self.cols): column = self.get_column(col_number) if self.is_line_winning(column): return True return False def are_diagonals_winning(self): """ Test the diagonals for win. """ diagonals = self.get_all_forward_diagonals() \ + self.get_all_backward_diagonals() for diagonal in diagonals: if self.is_line_winning(diagonal): return True return False def is_win(self): """ Yes, if the board has four same marks in line. """ return self.are_rows_winning() \ or self.are_cols_winning() \ or self.are_diagonals_winning() class HumanPlayer(object): def __init__(self, name): self.name = name def strategy(self, board, mark): """ Ask user for the column number. """ choice = int(input("Get me column number:")) while not board.is_valid_choice(choice): choice = int(input("Get me correct column number:")) return choice class RandomComputerPlayer(object): def __init__(self, name): self.name = name def strategy(self, board, mark): """ Return the random valid column. """ choice = randint(0, board.cols) while not board.is_valid_choice(choice): choice = randint(0, board.cols) return choice class Game(object): def __init__(self, rows, cols): self.statistics = {} self.rows = rows self.cols = cols def play(self, player1, player2, output=True): """ Play one game and return the winner. """ # Add new user to the statistics: self.statistics.setdefault(player1.name, 0) self.statistics.setdefault(player2.name, 0) # Create new board board = Board(rows=self.rows, cols=self.cols) if output: print(board) # Tuples, that are switched after each turn last_mark, current_mark = "O", "X" last_player, current_player = player2, player1 while not board.is_board_full(): # Switch the players last_mark, current_mark = current_mark, last_mark last_player, current_player = current_player, last_player # Choice of the player player_choice = current_player.strategy(board, current_mark) board.add_mark(player_choice, current_mark) if output: # Nicely print the board print(board) # Winner? if board.is_win(): # Mark the win to statistic self.statistics[current_player.name] += 1 if output: print("The {} won!".format(current_player.name)) # Return the winner return current_player # In case of draw (remíza) if output: print("Draw!".format(current_player.name)) return None # Example: petr = RandomComputerPlayer(name="Petr") pavel = RandomComputerPlayer(name="Pavel") jakub = RandomComputerPlayer(name="Jakub") match = Game(rows=6, cols=9) match.play(player1=petr, player2=pavel) match.play(player1=petr, player2=jakub) match.play(player1=jakub, player2=petr) print(match.statistics)