From a28dfac7a8b48c14d7e071a6bb96f08bf8cafd7c Mon Sep 17 00:00:00 2001 From: Tim McCarthy Date: Mon, 25 Apr 2022 18:22:08 -0700 Subject: [PATCH] Side-by-side board display --- battleship/battleship.py | 22 ++++++++++++++++++---- battleship/human.py | 10 ++-------- battleship/player.py | 1 + battleship/ui.py | 36 +++++++++++++++++++++++++++++++++--- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/battleship/battleship.py b/battleship/battleship.py index c746760..7e4a5e2 100644 --- a/battleship/battleship.py +++ b/battleship/battleship.py @@ -4,15 +4,30 @@ from typing import List from .player import Player from .ship import ShipType -from .ui import clear_screen, format_board, opponent_cell_fn +from .ui import clear_screen, opponent_cell_fn, format_boards + + +def show_boards(players: List[Player], active: Player): + players = list(sorted(players, key=lambda p: p.name)) + active_idx = players.index(active) + + cell_fns = [ + opponent_cell_fn(players[0], players[1], players[0].last_guess), + opponent_cell_fn(players[1], players[0], players[1].last_guess) + ] + boards = [players[1].board, players[0].board] + titles = [player.name for player in players] + titles[active_idx] = f"[ {titles[active_idx]} ]" + print(format_boards(cell_fns, boards, titles)) def play_turn(player: Player, opponent: Player): guess = player.guess(opponent) + player.last_guess = guess player.guesses.add(guess) clear_screen() - print(format_board(opponent_cell_fn(player, opponent, guess), opponent.board)) + show_boards([player, opponent], player) print() ship = opponent.ship_at(guess) @@ -23,8 +38,7 @@ def play_turn(player: Player, opponent: Player): else: print(f"{player.name}: Hit!") - print("\n") - input("Press ENTER to continue.") + input("\nPress ENTER to continue.") def play_battleship(player_1: Player, player_2: Player, ship_types: List[ShipType]): diff --git a/battleship/human.py b/battleship/human.py index 5a791ac..b3ec85c 100644 --- a/battleship/human.py +++ b/battleship/human.py @@ -5,8 +5,7 @@ from typing import List from .bs_types import Coordinate from .player import Player, PlacementError from .ship import Ship, ShipType -from .ui import read_coordinate, format_board, player_cell_fn, read_orientation, opponent_cell_fn, \ - clear_screen +from .ui import read_coordinate, format_board, player_cell_fn, read_orientation, clear_screen class HumanPlayer(Player): @@ -17,7 +16,7 @@ class HumanPlayer(Player): clear_screen() def place_ship(self, ship_type: ShipType) -> Ship: - current_board = format_board(player_cell_fn(self), self.board) + current_board = format_board(player_cell_fn(self), self.board, self.name) while True: clear_screen() @@ -40,12 +39,7 @@ class HumanPlayer(Player): input() def guess(self, opponent: Player) -> Coordinate: - current_board = format_board(opponent_cell_fn(self, opponent), opponent.board) while True: - clear_screen() - print(current_board) - print() - coord = read_coordinate(f"{self.name}, where would you like to aim your cannons? ", opponent.board) if coord not in self.guesses: diff --git a/battleship/player.py b/battleship/player.py index 4217507..721757f 100644 --- a/battleship/player.py +++ b/battleship/player.py @@ -20,6 +20,7 @@ class Player: self.board = board self.guesses: Set[Coordinate] = set() self.ships: List[Ship] = [] + self.last_guess: Optional[Coordinate] = None def place_ships(self, ship_types: List[ShipType]): for ship_type in ship_types: diff --git a/battleship/ui.py b/battleship/ui.py index 70bdaa4..b1f255d 100644 --- a/battleship/ui.py +++ b/battleship/ui.py @@ -1,9 +1,10 @@ from __future__ import annotations +import itertools import os import re from string import ascii_uppercase -from typing import Optional +from typing import Optional, List from .board import Board from .ship import Ship, ShipOrientation, ShipType @@ -95,12 +96,20 @@ def opponent_cell_fn(player, opponent, latest: Optional[Coordinate] = None) -> C return _cell -def format_board(cell_fn: CellFn, board: Board, row_separator="-", column_separator="|", intersection="+") -> str: +def format_board( + cell_fn: CellFn, + board: Board, + title: str, + row_separator="-", + column_separator="|", + intersection="+" +) -> str: """ Formats the current board state as a string. :param cell_fn: A rendering function that takes a cell coordinate and returns a string. :param board: The board we're rendering. + :param title: A title to place at the top :param row_separator: A character to render between rows. :param column_separator: A character to render between columns. :param intersection: A character to render at intersections. @@ -109,8 +118,8 @@ def format_board(cell_fn: CellFn, board: Board, row_separator="-", column_separa y_label_width = len(str(board.width+1)) cell_width = 3 - rendered_board = "" divider = " " * y_label_width + column_separator + (row_separator*cell_width + intersection) * board.width + "\n" + rendered_board = title.center(len(divider)) + "\n" # Render the header with tickmarks for the X axis # We use letters to represent the X axis so that each header will always be a single cell @@ -137,6 +146,27 @@ def format_board(cell_fn: CellFn, board: Board, row_separator="-", column_separa return rendered_board +def format_boards( + cell_fns: List[CellFn], + boards: List[Board], + titles: List[str], + row_separator="-", + column_separator="|", + intersection="+", + padding=4, +) -> str: + rendered_boards = [format_board(fn, board, title, row_separator, column_separator, intersection).splitlines(keepends=False) + for fn, board, title in zip(cell_fns, boards, titles)] + max_row_length = max(len(row) for row in itertools.chain(*rendered_boards)) + spaces = " " * padding + + merged_rows = [spaces.join(row.ljust(max_row_length) for row in rows) + for rows in zip(*rendered_boards)] + render = "\n".join(merged_rows) + + return render + + def print_test_board(): from .player import Player board = Board(7, 7)