import random import gradio as gr import matplotlib.pyplot as plt import numpy as np BOARD_SIZE = 5 NUM_SHIPS = 3 def place_ships(board, num_ships): """ Randomly place 'num_ships' single-cell ships on 'board'. """ placed = 0 while placed < num_ships: r, c = random.randint(0, BOARD_SIZE - 1), random.randint(0, BOARD_SIZE - 1) if board[r][c] == 0: # empty spot board[r][c] = 1 placed += 1 def init_game(): """ Create initial state for a new Battleship game. """ user_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] ai_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)] place_ships(user_board, NUM_SHIPS) place_ships(ai_board, NUM_SHIPS) return { "user_board": user_board, "ai_board": ai_board, "user_ships_remaining": NUM_SHIPS, "ai_ships_remaining": NUM_SHIPS, "game_over": False, "message": "Game started! Enter row and column to fire.", "ai_guesses": set() } def plot_board(board, is_user_board=True): """ Visual representation of the game board using Matplotlib. Different colors are used for ships, hits, and misses. """ fig, ax = plt.subplots(figsize=(5, 5)) colors = { 0: "#D3D3D3", # Empty - Light Gray 1: "#1E90FF" if is_user_board else "#D3D3D3", # Ship - Blue for User, Hidden for AI 2: "#FF0000", # Hit - Red 3: "#FFFFFF" # Miss - White } grid = np.array(board) if not is_user_board: # Hide AI's ships by turning cells with '1' into '0' grid = np.where(grid == 1, 0, grid) colored_grid = np.vectorize(colors.get)(grid) for r in range(BOARD_SIZE): for c in range(BOARD_SIZE): rect = plt.Rectangle((c, BOARD_SIZE - r - 1), 1, 1, facecolor=colored_grid[r][c], edgecolor="black") ax.add_patch(rect) ax.set_xticks(np.arange(BOARD_SIZE) + 0.5, labels=[str(i) for i in range(BOARD_SIZE)]) ax.set_yticks(np.arange(BOARD_SIZE) + 0.5, labels=[str(i) for i in range(BOARD_SIZE)][::-1]) ax.set_xticks(np.arange(BOARD_SIZE + 1), minor=True) ax.set_yticks(np.arange(BOARD_SIZE + 1), minor=True) ax.grid(which="minor", color="black", linestyle='-', linewidth=1) ax.set_xlim(0, BOARD_SIZE) ax.set_ylim(0, BOARD_SIZE) ax.set_frame_on(False) return fig def plot_color_key(): """ Generates a small color-coded key explaining the board symbols. """ fig, ax = plt.subplots(figsize=(5, 1)) ax.set_xlim(0, 4) ax.set_ylim(0, 1) color_labels = [ ("#1E90FF", "Your Ship"), ("#FF0000", "Hit Ship"), ("#FFFFFF", "Miss"), ("#D3D3D3", "Hidden") ] for i, (color, label) in enumerate(color_labels): rect = plt.Rectangle((i, 0), 1, 1, facecolor=color, edgecolor="black") ax.add_patch(rect) ax.text(i + 0.5, 0.5, label, ha='center', va='center', fontsize=10) ax.set_xticks([]) ax.set_yticks([]) ax.set_frame_on(False) return fig def ai_guess(state): """ AI randomly picks an unguessed cell in the user's board and marks hit/miss. """ user_board = state["user_board"] valid_positions = [ (r, c) for r in range(BOARD_SIZE) for c in range(BOARD_SIZE) if (r, c) not in state["ai_guesses"] ] if not valid_positions: return r, c = random.choice(valid_positions) state["ai_guesses"].add((r, c)) if user_board[r][c] == 1: user_board[r][c] = 2 # AI hit state["user_ships_remaining"] -= 1 state["message"] += f"\nAI hit your ship at ({r}, {c})!" else: user_board[r][c] = 3 # AI miss state["message"] += f"\nAI missed at ({r}, {c})." def make_move(state, row, col): """ Handles the user's move, updates the board, and checks win conditions. """ if state["game_over"]: return ( plot_board(state["user_board"]), plot_board(state["ai_board"], is_user_board=False), state["message"], plot_color_key() ) # Validate row/col input if not (0 <= row < BOARD_SIZE and 0 <= col < BOARD_SIZE): state["message"] = "Invalid input. Row/Col must be between 0 and 4." return ( plot_board(state["user_board"]), plot_board(state["ai_board"], is_user_board=False), state["message"], plot_color_key() ) # Check if user already attacked this cell if state["ai_board"][row][col] in (2, 3): state["message"] = f"You already attacked ({row}, {col})!" return ( plot_board(state["user_board"]), plot_board(state["ai_board"], is_user_board=False), state["message"], plot_color_key() ) # If there's a ship at the target if state["ai_board"][row][col] == 1: state["ai_board"][row][col] = 2 # User hit state["ai_ships_remaining"] -= 1 state["message"] = f"Hit! You hit AI's ship at ({row}, {col})." else: state["ai_board"][row][col] = 3 # User miss state["message"] = f"Miss! No ship at ({row}, {col})." # Check if user just won if state["ai_ships_remaining"] == 0: state["message"] += "\nYou sank all the AI's ships. You win!" state["game_over"] = True return ( plot_board(state["user_board"]), plot_board(state["ai_board"], is_user_board=False), state["message"], plot_color_key() ) # Otherwise, let the AI guess ai_guess(state) # Check if AI just won if state["user_ships_remaining"] == 0: state["message"] += "\nAI sank all your ships. You lose!" state["game_over"] = True return ( plot_board(state["user_board"]), plot_board(state["ai_board"], is_user_board=False), state["message"], plot_color_key() ) def reset_game(): """ Reset the game state completely. """ return init_game() def update_display(state): """ Helper function to pull out the boards and message so we can show them immediately or after any reset. """ return ( plot_board(state["user_board"]), plot_board(state["ai_board"], is_user_board=False), state["message"], plot_color_key() ) with gr.Blocks() as demo: gr.Markdown(""" # Battleship Game **Welcome to Battleship!** In your opponent's 5x5 grid, there are three hidden ships (size=1). Select the row (0-4) and column (0-4) you want to fire at, then hit "Fire!" Follow the messages to see who gets hit and eventually wins. """) # Initialize game state state = gr.State(init_game()) # Layout with gr.Row(): with gr.Column(): user_board_display = gr.Plot(label="Your Board") with gr.Column(): ai_board_display = gr.Plot(label="AI Board") message_display = gr.Textbox(label="Game Messages", interactive=False) color_key_display = gr.Plot(label="Color Key") # Will be updated automatically row_in = gr.Number(label="Row (0-4)", value=0, precision=0) col_in = gr.Number(label="Column (0-4)", value=0, precision=0) fire_button = gr.Button("Fire!") reset_button = gr.Button("Reset Game") # Render boards on initial load demo.load(fn=update_display, inputs=state, outputs=[user_board_display, ai_board_display, message_display, color_key_display]) # Fire action fire_button.click( fn=make_move, inputs=[state, row_in, col_in], outputs=[user_board_display, ai_board_display, message_display, color_key_display] ) # Reset action # 1. Reset the state # 2. Then re-render the boards using update_display reset_button.click(fn=reset_game, inputs=[], outputs=state).then( fn=update_display, inputs=state, outputs=[user_board_display, ai_board_display, message_display, color_key_display] ) demo.launch()