import streamlit as st import random import json import os import boto3 from pymongo import MongoClient from bson.objectid import ObjectId # Constants BOARD_SIZE = 10 EMPTY = ':ocean:' HIT = ':boom:' MISS = ':heavy_multiplication_x:' SHIPS = [ {"name": "Carrier", "size": 5, "symbol": ":ship:"}, {"name": "Battleship", "size": 4, "symbol": ":motor_boat:"}, {"name": "Cruiser", "size": 3, "symbol": ":boat:"}, {"name": "Submarine", "size": 3, "symbol": ":ferry:"}, {"name": "Destroyer", "size": 2, "symbol": ":speedboat:"} ] # MongoDB Atlas Connection client = MongoClient(os.environ.get('MONGODB_ATLAS_URI')) db = client['battleship'] games = db['games'] # AWS Bedrock Client Setup bedrock_runtime = boto3.client('bedrock-runtime', aws_access_key_id=os.environ.get('AWS_ACCESS_KEY'), aws_secret_access_key=os.environ.get('AWS_SECRET_KEY'), region_name="us-west-2") def get_bedrock_claude_move(board, openent_moves=None): """ Get the next move from Claude AI using AWS Bedrock. """ print("inside get_bedrock_claude_move") claude_body = json.dumps({ "anthropic_version": "bedrock-2023-05-31", "max_tokens": 5000, "temperature": 0, "system": f"Please provide the next move as an opponent in the battleship game , the board is {BOARD_SIZE} X {BOARD_SIZE} . Be smart and stratigic. Respond only in JSON format strictly: 'row' : ... , 'col' : ... , 'entertainment_comment' and nothing more . ", "messages": [{ "role": "user", "content": [ {"type": "text", "text": f"The current board: {board} and your moves were: {openent_moves if openent_moves else 'None'}"} ] }] }) model_id = "anthropic.claude-3-5-sonnet-20241022-v2:0" if st.session_state.difficulty == "Expert" else "anthropic.claude-3-5-sonnet-20240620-v1:0" response = bedrock_runtime.invoke_model( body=claude_body, modelId=model_id, accept="application/json", contentType="application/json", ) response_body = json.loads(response.get("body").read()) json_response = json.loads(response_body["content"][0]['text']) return json_response['row'], json_response['col'], json_response['entertainment_comment'] # Game Setup Functions def create_empty_board(): """Create an empty game board.""" return [[EMPTY for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)] def is_valid_position(row, col): """Check if a position is within the board boundaries.""" return 0 <= row < BOARD_SIZE and 0 <= col < BOARD_SIZE def check_adjacent_cells(board, row, col): """Check if any adjacent cells (including diagonals) contain a ship.""" for i in range(-1, 2): for j in range(-1, 2): if i == 0 and j == 0: continue new_row, new_col = row + i, col + j if is_valid_position(new_row, new_col) and board[new_row][new_col] != EMPTY: return False return True def can_place_ship(board, row, col, size, direction): """Check if a ship can be placed at the given position.""" if direction == 'horizontal': if col + size > BOARD_SIZE: return False # Check the ship's cells and their adjacent cells for i in range(-1, size + 1): for j in range(-1, 2): check_row = row + j check_col = col + i if is_valid_position(check_row, check_col): if board[check_row][check_col] != EMPTY: return False else: # vertical if row + size > BOARD_SIZE: return False # Check the ship's cells and their adjacent cells for i in range(-1, 2): for j in range(-1, size + 1): check_row = row + j check_col = col + i if is_valid_position(check_row, check_col): if board[check_row][check_col] != EMPTY: return False return True def place_ships_randomly(board): """Place ships randomly on the board.""" for ship in SHIPS: while True: row = random.randint(0, BOARD_SIZE - 1) col = random.randint(0, BOARD_SIZE - 1) direction = random.choice(['horizontal', 'vertical']) if can_place_ship(board, row, col, ship['size'], direction): place_ship(board, row, col, ship['size'], direction, ship['symbol']) break return board def place_ship(board, row, col, size, direction, symbol): """Place a ship on the board.""" if direction == 'horizontal': for i in range(size): board[row][col+i] = symbol else: for i in range(size): board[row+i][col] = symbol def initialize_game(): """Initialize the game state.""" if 'difficulty' not in st.session_state: st.session_state.difficulty = "Beginner" if 'game_state' not in st.session_state: st.session_state.game_state = { 'player_board': place_ships_randomly(create_empty_board()), 'opponent_board': place_ships_randomly(create_empty_board()), 'player_attacks': create_empty_board(), 'player_hits_left': 17, 'opponent_attacks': create_empty_board(), 'openent_moves': [], 'opponent_hits_left': 17, 'current_player': 'player', 'game_state': 'not_started', 'game_over': False, 'message': '' } if 'game_id' not in st.session_state.game_state: st.session_state.game_state['game_id'] = ObjectId() gameId = st.session_state.game_state['game_id'] # Initialize game data in MongoDB games.update_one({'game_id': gameId, 'type': 'player'}, {"$set": {'game_id': gameId, 'board': st.session_state.game_state['player_board'], 'attacking_board': st.session_state.game_state['player_attacks']}}, upsert=True) games.update_one({'game_id': gameId, 'type': 'opponent'}, {"$set": {'game_id': gameId, 'board': st.session_state.game_state['opponent_board'], 'oponent_moves': st.session_state.game_state['openent_moves'], 'attacking_board': st.session_state.game_state['opponent_attacks']}}, upsert=True) def check_game_over(): """Check if the game is over.""" game_state = st.session_state.game_state if game_state['player_hits_left'] == 0: game_state['message'] = "You won!" game_state['game_over'] = True return True elif game_state['opponent_hits_left'] == 0: game_state['message'] = "You lost!" game_state['game_over'] = True return True def render_board(board, is_opponent=False): """Render the game board using Streamlit components.""" for i, row in enumerate(board): cols = st.columns(BOARD_SIZE) for j, cell in enumerate(row): with cols[j]: if is_opponent and cell == EMPTY: if st.button('🌊', key=f'opp_{i}_{j}', on_click=lambda r=i, c=j: attack(r, c)): pass else: # Determine the button color based on the cell content if cell == EMPTY: button_color = 'secondary' elif cell in [HIT, MISS]: button_color = 'primary' # Use default color for hits and misses else: button_color = 'primary' if not is_opponent else 'secondary' # Use Streamlit's button with theme colors st.button(cell, key=f'{"opp" if is_opponent else "player"}_{i}_{j}', type=button_color) def attack(row, col): """Process a player's attack.""" game_state = st.session_state.game_state if game_state['opponent_board'][row][col] != EMPTY: game_state['player_attacks'][row][col] = HIT game_state['message'] = "You hit a ship!" game_state['opponent_hits_left'] -= 1 else: game_state['player_attacks'][row][col] = MISS game_state['message'] = "You missed!" update_database('player') game_state['current_player'] = 'opponent' def update_database(type): """Update the game state in the database.""" game_state = st.session_state.game_state gameId = game_state['game_id'] games.update_one({'game_id': gameId, 'type': type}, {'$set': {'attacking_board': game_state['player_attacks'], 'oponent_moves': game_state['openent_moves']}}) def opponent_turn(): """Process the opponent's turn.""" game_state = st.session_state.game_state with st.spinner("Opponent is making a move..."): opponent_doc = games.find_one({'game_id': ObjectId(game_state['game_id']), 'type': 'opponent'}) print("opponent_doc:", opponent_doc) row, col, comment = get_bedrock_claude_move(opponent_doc['attacking_board'], openent_moves=opponent_doc['oponent_moves']) game_state['current_player'] = 'player' if game_state['player_board'][row][col] != EMPTY and game_state['opponent_attacks'][row][col] != MISS: game_state['openent_moves'].append({'row': row, 'col': col, 'status': 'hit'}) game_state['opponent_attacks'][row][col] = HIT game_state['player_board'][row][col] = HIT game_state['player_hits_left'] -= 1 game_state['message'] = f"Opponent hit your ship! Cell row {row} col {col} Opponent: {comment}" else: game_state['openent_moves'].append({'row': row, 'col': col, 'status': 'miss'}) game_state['opponent_attacks'][row][col] = MISS game_state['player_board'][row][col] = MISS game_state['message'] = f"Opponent missed! Cell row {row} col {col}, Opponent: {comment}" update_database('opponent') game_state['current_player'] = 'player' def main(): """Main function to run the Battleship game.""" st.title('Battleship Game') # Initialize difficulty in session state if not present if 'difficulty' not in st.session_state: st.session_state.difficulty = "Beginner" # Add difficulty selector st.session_state.difficulty = st.selectbox( "Select Difficulty", ["Beginner", "Expert"], index=0 if st.session_state.difficulty == "Beginner" else 1 ) initialize_game() if st.session_state.game_state['game_over']: st.subheader('Game Over! You Lost!' if st.session_state.game_state['player_hits_left'] == 0 else 'You Won!') else: st.subheader('Your Turn') col1, col3, col2 = st.columns(3) with col1: st.subheader('Your Board') render_board(st.session_state.game_state['player_board']) with col3: st.subheader("Opponent Ships") for ship in SHIPS: my_ships, status = st.columns(2) with my_ships: st.write(f" {ship['symbol']} {ship['name']}, size: {ship['size']}") with status: st.checkbox("Sunk", key=f"{ship['name']}_sunk") st.markdown("#### Opponent history") container = st.container(height=250) for move in st.session_state.game_state['openent_moves']: container.write(f"Row: {move['row']} Col: {move['col']} Status: {move['status']}") with col2: st.subheader("Opponent's Board") render_board(st.session_state.game_state['player_attacks'], is_opponent=True) st.write(st.session_state.game_state['message']) if st.button('Reset Game'): st.session_state.clear() st.rerun() if st.session_state.game_state['current_player'] == 'opponent': opponent_turn() if check_game_over(): st.session_state.game_state['current_player'] = 'game_over' else: st.rerun() st.header('Debug') expander = st.expander("Game State") for row in st.session_state.game_state['opponent_board']: expander.markdown(row) if st.button("Chat"): pop_chat() if __name__ == '__main__': main()