battleship game
This commit is contained in:
SpudGunMan
2025-11-03 22:07:38 -08:00
parent 1122d6007e
commit 09b684fad8
7 changed files with 701 additions and 7 deletions
+1
View File
@@ -448,6 +448,7 @@ hangman = True
hamtest = True
tictactoe = True
wordOfTheDay = True
battleShip = True
# enable or disable the quiz game module questions are in data/quiz.json
quiz = False
+115 -1
View File
@@ -16,7 +16,7 @@ import modules.settings as my_settings
from modules.system import *
# list of commands to remove from the default list for DM only
restrictedCommands = ["blackjack", "videopoker", "dopewars", "lemonstand", "golfsim", "mastermind", "hangman", "hamtest", "tictactoe", "tic-tac-toe", "quiz", "q:", "survey", "s:"]
restrictedCommands = ["blackjack", "videopoker", "dopewars", "lemonstand", "golfsim", "mastermind", "hangman", "hamtest", "tictactoe", "tic-tac-toe", "quiz", "q:", "survey", "s:", "battleship"]
restrictedResponse = "🤖only available in a Direct Message📵" # "" for none
def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_number, deviceID, isDM):
@@ -31,6 +31,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
"ask:": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel),
"askai": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel),
"bannode": lambda: handle_bbsban(message, message_from_id, isDM),
"battleship": lambda: handleBattleship(message, message_from_id, deviceID),
"bbsack": lambda: bbs_sync_posts(message, message_from_id, deviceID),
"bbsdelete": lambda: handle_bbsdelete(message, message_from_id),
"bbshelp": bbs_help,
@@ -1094,6 +1095,118 @@ def handleTicTacToe(message, nodeID, deviceID):
msg = tictactoe.play(nodeID, message)
return msg
def handleBattleship(message, nodeID, deviceID):
global battleshipTracker
from modules.games import battleship
# Helper to get short_name from tracker
def get_short_name(nid):
entry = next((e for e in battleshipTracker if e['nodeID'] == nid), None)
return entry['short_name'] if entry and 'short_name' in entry else get_name_from_number(nid, 'short', deviceID)
msg_lower = message.lower().strip()
tracker_entry = next((entry for entry in battleshipTracker if entry['nodeID'] == nodeID), None)
# End/exit command
if msg_lower.startswith('end') or msg_lower.startswith('exit'):
if tracker_entry:
if 'session_id' in tracker_entry:
battleship.Battleship.end_game(tracker_entry['session_id'])
battleshipTracker.remove(tracker_entry)
return "Thanks for playing Battleship! 🚢"
# Create new P2P game with short code
if msg_lower.startswith("battleship new"):
short_name = get_name_from_number(nodeID, 'short', deviceID)
msg, code = battleship.Battleship.new_game(nodeID, vs_ai=False)
battleshipTracker.append({
"nodeID": nodeID,
"short_name": short_name,
"last_played": time.time(),
"session_id": battleship.Battleship.short_codes.get(code, code)
})
return f"{msg}"
# Show open P2P games waiting for a player
if msg_lower.startswith("battleship lobby"):
open_codes = []
for code, session_id in battleship.Battleship.short_codes.items():
session = battleship.Battleship.sessions.get(session_id)
if session and session.player2_id is None:
open_codes.append(code)
if not open_codes:
return "No open Battleship games waiting for players."
return "Open Battleship games (join with 'battleship join <code>'):\n" + ", ".join(open_codes)
# Join existing P2P game using short code
if msg_lower.startswith("battleship join"):
try:
code = msg_lower.split("join", 1)[1].strip()
except IndexError:
return "Usage: battleship join <code>"
session = battleship.Battleship.get_session(code)
if not session:
return "Session not found."
if session.player2_id is not None:
return "Session already has two players."
session.player2_id = nodeID
session.next_turn = nodeID # Make joining player go first!
short_name = get_name_from_number(nodeID, 'short', deviceID)
battleshipTracker.append({
"nodeID": nodeID,
"short_name": short_name,
"last_played": time.time(),
"session_id": session.session_id
})
p1_short_name = get_short_name(session.player1_id)
send_message(
f"{p1_short_name}, your opponent {short_name} has joined the game! It's their turn first.",
0, # channel 0 for DM
session.player1_id, # recipient nodeID
deviceID
)
time.sleep(splitDelay) # slight delay to avoid message overlap
return "You joined the game! It's your turn. Enter your move (e.g., 'B4')."
# If not found, create new tracker entry and new game vs AI (default)
if not tracker_entry:
short_name = get_name_from_number(nodeID, 'short', deviceID)
msg, session_id = battleship.Battleship.new_game(nodeID)
battleshipTracker.append({
"nodeID": nodeID,
"short_name": short_name,
"last_played": time.time(),
"session_id": session_id
})
return msg
# Update last played
tracker_entry["last_played"] = time.time()
session_id = tracker_entry.get("session_id")
# Play the game and check if we need to alert the next player
response = battleship.playBattleship(message, nodeID, deviceID, session_id=session_id)
# --- Notify the next player when it's their turn in P2P ---
session = battleship.Battleship.get_session(session_id)
if session and not session.vs_ai and session.player1_id and session.player2_id:
# Only notify if the game is not over (optional: add a game-over check)
if getattr(session, "last_move", None):
next_player_id = session.next_turn
# Only notify if it's not the player who just moved
if next_player_id != nodeID:
next_player_short_name = get_short_name(next_player_id)
send_message(
f"{next_player_short_name}, it's your turn in Battleship! Enter your move (e.g., 'B4').",
0, # channel 0 for DM
next_player_id,
deviceID
)
time.sleep(splitDelay) # slight delay to avoid message overlap
return response
def quizHandler(message, nodeID, deviceID):
global quizGamePlayer
user_name = get_name_from_number(nodeID)
@@ -2128,6 +2241,7 @@ gameTrackers = [
(hamtestTracker, "HamTest", handleHamtest),
(tictactoeTracker, "TicTacToe", handleTicTacToe),
(surveyTracker, "Survey", surveyHandler),
(battleshipTracker, "Battleship", handleBattleship),
# quiz does not use a tracker (quizGamePlayer) always active
]
+74 -5
View File
@@ -8,12 +8,13 @@
- [Lemonade Stand](#lemonade-stand-game-module)
- [Tic-Tac-Toe (2D/3D)](#tic-tac-toe-game-module)
- [MasterMind](#mastermind-game-module)
- [Battleship](#battleship-game-module)
- [Video Poker](#video-poker-game-module)
- [Hangman](#hangman-game-module)
- [Quiz](#quiz-game-module)
- [Survey](#survey--module-game)
- [Word of the Day Game](#word-of-the-day-game--rules--features)
- [Game Server Configuration (`game.ini`)](#game-server-configuration-gameini)
- [Game Server](#game-server-configuration-gameini)
- [PyGame Help](#pygame-help)
---
@@ -520,6 +521,77 @@ Place your Bet, or (L)eave Table.
- Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
# Battleship Game Module
A classic Battleship game for the Meshtastic mesh-bot. Play solo against the AI or challenge another user in peer-to-peer (P2P) mode!
## How to Play
- **Start a New Game (vs AI):**
Send `battleship` via DM to the bot to start a new game against the AI.
- **Start a New P2P Game:**
Send `battleship new` to create a game and receive a join code.
Share the code with another user.
- **Join a P2P Game:**
Send `battleship join <code>` (replace `<code>` with the provided number) to join a waiting game.
- **View Open Games:**
Send `battleship lobby` to see a list of open P2P games waiting for players.
- **Gameplay:**
- Enter your move using coordinates:
- Format: `B4` or `B,4` (row letter, column number)
- Example: `C7`
- The bot will show your radar, ship status, and results after each move.
- In P2P, you and your opponent take turns. The bot will notify you when its your turn.
- **End Game:**
Send `end` or `exit` to leave your current game.
## Rules & Features
- 10x10 grid, classic ship sizes (Carrier, Battleship, Cruiser, Submarine, Destroyer).
- Ships are placed randomly.
- In P2P, the joining player goes first.
- Radar view shows a 4x4 grid centered on your last move.
- Game tracks whose turn it is and notifies the next player in P2P mode.
- Game ends when all ships of one player are sunk.
## Example Session
```
New 🚢Battleship🤖 game started!
Enter your move using coordinates: row-letter, column-number.
Example: B5 or C,7
Type 'exit' or 'end' to quit the game.
> B4
Your move: 💥Hit!
AI ships: 5/5 afloat
Radar:
🗺️3 4 5 6
B ~ ~ * ~
C ~ ~ ~ ~
D ~ ~ ~ ~
E ~ ~ ~ ~
AI move: D7 (missed)
Your ships: 5/5 afloat
```
## Notes
- Only one Battleship session per player at a time.
- Play via DM for best experience.
- In P2P, share the join code with your opponent.
- Coordinates are not case-sensitive.
## Credits
- Written for Meshtastic mesh-bot by K7MHI Kelly Keeton 2025
# Word of the Day Game — Rules & Features
- **Word of the Day:**
@@ -736,8 +808,6 @@ This module implements a survey system for the Meshtastic mesh-bot.
**Written for Meshtastic mesh-bot by K7MHI Kelly Keeton 2025**
___
# Game Server Configuration (`game.ini`)
@@ -786,5 +856,4 @@ mUDP library provides UDP-based broadcasting of Meshtastic-compatible packets. M
**Installation**
```sh
pip install mudp
```
---
```
+407
View File
@@ -0,0 +1,407 @@
#!/usr/bin/env python3
import random
import copy
import uuid
OCEAN = "~"
FIRE = "x"
HIT = "*"
SIZE = 10
SHIPS = [5, 4, 3, 3, 2]
SHIP_NAMES = ["✈️Carrier", "Battleship", "Cruiser", "Submarine", "Destroyer"]
class Session:
def __init__(self, player1_id, player2_id=None, vs_ai=True):
self.session_id = str(uuid.uuid4())
self.vs_ai = vs_ai
self.player1_id = player1_id
self.player2_id = player2_id
self.game = Battleship(vs_ai=vs_ai)
self.next_turn = player1_id
self.last_move = None
class Battleship:
sessions = {}
short_codes = {}
@classmethod
def _generate_short_code(cls):
while True:
code = str(random.randint(1000, 9999))
if code not in cls.short_codes:
return code
@classmethod
def new_game(cls, player_id, vs_ai=True, p2p_id=None):
session = Session(player1_id=player_id, player2_id=p2p_id, vs_ai=vs_ai)
cls.sessions[session.session_id] = session
if not vs_ai:
code = cls._generate_short_code()
cls.short_codes[code] = session.session_id
msg = (
"New 🚢Battleship🚢 game started!\n"
"Joining player goes first, waiting for them to join...\n"
f"Share\n'battleship join {code}'"
)
return msg, code
else:
msg = (
"New 🚢Battleship🤖 game started!\n"
"Enter your move using coordinates: row-letter, column-number.\n"
"Example: B5 or C,7\n"
"Type 'exit' or 'end' to quit the game."
)
return msg, session.session_id
@classmethod
def end_game(cls, session_id):
if session_id in cls.sessions:
del cls.sessions[session_id]
return "Thanks for playing 🚢Battleship🚢"
@classmethod
def get_session(cls, code_or_session_id):
session_id = cls.short_codes.get(code_or_session_id, code_or_session_id)
return cls.sessions.get(session_id)
def __init__(self, vs_ai=True):
if vs_ai:
self.player_board = self._blank_board()
self.ai_board = self._blank_board()
self.player_radar = self._blank_board()
self.ai_radar = self._blank_board()
self.number_board = self._blank_board()
self.player_alive = sum(SHIPS)
self.ai_alive = sum(SHIPS)
self._place_ships(self.player_board, self.number_board)
self._place_ships(self.ai_board)
self.ai_targets = []
self.ai_last_hit = None
self.ai_orientation = None
else:
# P2P: Each player has their own board and radar
self.player1_board = self._blank_board()
self.player2_board = self._blank_board()
self.player1_radar = self._blank_board()
self.player2_radar = self._blank_board()
self.player1_alive = sum(SHIPS)
self.player2_alive = sum(SHIPS)
self._place_ships(self.player1_board)
self._place_ships(self.player2_board)
def _blank_board(self):
return [[OCEAN for _ in range(SIZE)] for _ in range(SIZE)]
def _place_ships(self, board, number_board=None):
for idx, ship_len in enumerate(SHIPS):
placed = False
while not placed:
vertical = random.choice([True, False])
if vertical:
row = random.randint(0, SIZE - ship_len)
col = random.randint(0, SIZE - 1)
if all(board[row + i][col] == OCEAN for i in range(ship_len)):
for i in range(ship_len):
board[row + i][col] = str(idx)
if number_board is not None:
number_board[row + i][col] = idx
placed = True
else:
row = random.randint(0, SIZE - 1)
col = random.randint(0, SIZE - ship_len)
if all(board[row][col + i] == OCEAN for i in range(ship_len)):
for i in range(ship_len):
board[row][col + i] = str(idx)
if number_board is not None:
number_board[row][col + i] = idx
placed = True
def player_move(self, row, col):
"""Player fires at AI's board. Returns 'hit', 'miss', or 'sunk:<ship_idx>'."""
if self.player_radar[row][col] != OCEAN:
return "repeat"
if self.ai_board[row][col] not in (OCEAN, FIRE, HIT):
self.player_radar[row][col] = HIT
ship_idx = int(self.ai_board[row][col])
self.ai_board[row][col] = HIT
if self._is_ship_sunk(self.ai_board, ship_idx):
self.ai_alive -= SHIPS[ship_idx]
return f"sunk:{ship_idx}"
return "hit"
else:
self.player_radar[row][col] = FIRE
self.ai_board[row][col] = FIRE
return "miss"
def ai_move(self):
"""AI fires at player's board. Returns (row, col, result or 'sunk:<ship_idx>')."""
while True:
row = random.randint(0, SIZE - 1)
col = random.randint(0, SIZE - 1)
if self.ai_radar[row][col] == OCEAN:
break
if self.player_board[row][col] not in (OCEAN, FIRE, HIT):
self.ai_radar[row][col] = HIT
ship_idx = int(self.player_board[row][col])
self.player_board[row][col] = HIT
if self._is_ship_sunk(self.player_board, ship_idx):
self.player_alive -= SHIPS[ship_idx]
return row, col, f"sunk:{ship_idx}"
return row, col, "hit"
else:
self.ai_radar[row][col] = FIRE
self.player_board[row][col] = FIRE
return row, col, "miss"
def p2p_player_move(self, row, col, attacker, defender, radar, defender_alive_attr):
"""P2P: attacker fires at defender's board, updates radar and defender's board."""
if radar[row][col] != OCEAN:
return "repeat"
if defender[row][col] not in (OCEAN, FIRE, HIT):
radar[row][col] = HIT
ship_idx = int(defender[row][col])
defender[row][col] = HIT
if self._is_ship_sunk(defender, ship_idx):
setattr(self, defender_alive_attr, getattr(self, defender_alive_attr) - SHIPS[ship_idx])
return f"sunk:{ship_idx}"
return "hit"
else:
radar[row][col] = FIRE
defender[row][col] = FIRE
return "miss"
def _is_ship_sunk(self, board, ship_idx):
for row in board:
for cell in row:
if cell == str(ship_idx):
return False
return True
def is_game_over(self, vs_ai=True):
if vs_ai:
return self.player_alive == 0 or self.ai_alive == 0
else:
return self.player1_alive == 0 or self.player2_alive == 0
def get_player_board(self):
return copy.deepcopy(self.player_board)
def get_player_radar(self):
return copy.deepcopy(self.player_radar)
def get_ai_board(self):
return copy.deepcopy(self.ai_board)
def get_ai_radar(self):
return copy.deepcopy(self.ai_radar)
def get_ship_status(self, board):
status = {}
for idx in range(len(SHIPS)):
afloat = any(str(idx) in row for row in board)
status[idx] = "Afloat" if afloat else "Sunk"
return status
def display_draw_board(self, board, label="Board"):
print(f"{label}")
print(" " + " ".join(str(i+1).rjust(2) for i in range(SIZE)))
for idx, row in enumerate(board):
print(chr(ord('A') + idx) + " " + " ".join(cell.rjust(2) for cell in row))
def get_short_name(node_id):
from mesh_bot import battleshipTracker
entry = next((e for e in battleshipTracker if e['nodeID'] == node_id), None)
return entry['short_name'] if entry and 'short_name' in entry else str(node_id)
def playBattleship(message, nodeID, deviceID, session_id=None):
if not session_id or session_id not in Battleship.sessions:
return Battleship.new_game(nodeID, vs_ai=True)
session = Battleship.get_session(session_id)
game = session.game
if not session.vs_ai and session.player2_id is None:
code = next((k for k, v in Battleship.short_codes.items() if v == session.session_id), None)
return (
f"Waiting for another player to join.\n"
f"Share this code: {code}\n"
"Type 'end' to cancel this P2P game."
)
if nodeID != session.next_turn:
return "It's not your turn!"
msg = message.strip().lower()
if msg.startswith("battleship"):
msg = msg[len("battleship"):].strip()
if msg.startswith("b:"):
msg = msg[2:].strip()
msg = msg.replace(" ", "")
x = y = None
if "," in msg:
parts = msg.split(",")
if len(parts) == 2 and len(parts[0]) == 1 and parts[0].isalpha() and parts[1].isdigit():
y = ord(parts[0]) - ord('a')
x = int(parts[1]) - 1
else:
return "Invalid coordinates. Use format A2 or A,2 (row letter, column number)."
elif len(msg) >= 2 and msg[0].isalpha() and msg[1:].isdigit():
y = ord(msg[0]) - ord('a')
x = int(msg[1:]) - 1
else:
return "Invalid command. Use format A2 or A,2 (row letter, column number)."
if x is None or y is None or not (0 <= x < SIZE and 0 <= y < SIZE):
return "Coordinates out of range."
ai_row = ai_col = ai_result = None
over = False
if session.vs_ai:
result = game.player_move(y, x)
ai_row, ai_col, ai_result = game.ai_move()
over = game.is_game_over(vs_ai=True)
else:
# P2P: determine which player is moving and fire at the other player's board
if nodeID == session.player1_id:
attacker = "player1"
defender = "player2"
result = game.p2p_player_move(
y, x,
game.player1_board, game.player2_board,
game.player1_radar, "player2_alive"
)
else:
attacker = "player2"
defender = "player1"
result = game.p2p_player_move(
y, x,
game.player2_board, game.player1_board,
game.player2_radar, "player1_alive"
)
over = game.is_game_over(vs_ai=False)
coord_str = f"{chr(y+65)}{x+1}"
session.last_move = (nodeID, coord_str, result)
# --- DEBUG DISPLAY ---
DEBUG = False
if DEBUG:
if session.vs_ai:
game.display_draw_board(game.player_board, label=f"Player Board ({session.player1_id})")
game.display_draw_board(game.player_radar, label="Player Radar")
game.display_draw_board(game.ai_board, label="AI Board")
game.display_draw_board(game.ai_radar, label="AI Radar")
else:
p1_id = session.player1_id
p2_id = session.player2_id if session.player2_id else "Waiting"
game.display_draw_board(game.player1_board, label=f"Player 1 Board ({p1_id})")
game.display_draw_board(game.player1_radar, label="Player 1 Radar")
game.display_draw_board(game.player2_board, label=f"Player 2 Board ({p2_id})")
game.display_draw_board(game.player2_radar, label="Player 2 Radar")
# Format radar as a 4x4 grid centered on the player's move
if session.vs_ai:
radar = game.get_player_radar()
else:
radar = game.player1_radar if nodeID == session.player1_id else game.player2_radar
window_size = 4
half_window = window_size // 2
min_row = max(0, min(y - half_window, SIZE - window_size))
max_row = min(SIZE, min_row + window_size)
min_col = max(0, min(x - half_window, SIZE - window_size))
max_col = min(SIZE, min_col + window_size)
radar_str = "🗺️" + " ".join(str(i+1) for i in range(min_col, max_col)) + "\n"
for idx in range(min_row, max_row):
radar_str += chr(ord('A') + idx) + " " + " ".join(radar[idx][j] for j in range(min_col, max_col)) + "\n"
def format_ship_status(status_dict):
afloat = 0
for idx, state in status_dict.items():
if state == "Afloat":
afloat += 1
return f"{afloat}/{len(SHIPS)} afloat"
if session.vs_ai:
ai_status_str = format_ship_status(game.get_ship_status(game.ai_board))
player_status_str = format_ship_status(game.get_ship_status(game.player_board))
else:
ai_status_str = format_ship_status(game.get_ship_status(game.player2_board))
player_status_str = format_ship_status(game.get_ship_status(game.player1_board))
def move_result_text(res, is_player=True):
if res.startswith("sunk:"):
idx = int(res.split(":")[1])
name = SHIP_NAMES[idx]
return f"Sunk🎯 {name}!"
elif res == "hit":
return "💥Hit!"
elif res == "miss":
return "missed"
elif res == "repeat":
return "📋already targeted"
else:
return res
# After a valid move, switch turns
if session.vs_ai:
session.next_turn = nodeID
else:
session.next_turn = session.player2_id if nodeID == session.player1_id else session.player1_id
# Output message
if session.vs_ai:
msg_out = (
f"Your move: {move_result_text(result)}\n"
f"AI ships: {ai_status_str}\n"
f"Radar:\n{radar_str}"
f"AI move: {chr(ai_row+65)}{ai_col+1} ({move_result_text(ai_result, False)})\n"
f"Your ships: {player_status_str}"
)
else:
my_name = get_short_name(nodeID)
opponent_id = session.player2_id if nodeID == session.player1_id else session.player1_id
opponent_short_name = get_short_name(opponent_id) if opponent_id else "Waiting"
opponent_label = f"{opponent_short_name}:"
my_move_result_str = f"Your move: {move_result_text(result)}\n"
last_move_str = ""
if session.last_move and session.last_move[0] != nodeID:
last_player_short_name = get_short_name(session.last_move[0])
last_coord = session.last_move[1]
last_result = move_result_text(session.last_move[2])
last_move_str = f"Last move by {last_player_short_name}: {last_coord} ({last_result})\n"
if session.next_turn == nodeID:
turn_prompt = f"Your turn, {my_name}! Enter your move:"
else:
turn_prompt = f"Waiting for {opponent_short_name}..."
msg_out = (
f"{my_move_result_str}"
f"{last_move_str}"
f"{opponent_label} {ai_status_str}\n"
f"Radar:\n{radar_str}"
f"Your ships: {player_status_str}\n"
f"{turn_prompt}"
)
if over:
if session.vs_ai:
if game.player_alive == 0:
winner = "AI 🤖"
msg_out += f"\nGame over! {winner} wins! Better luck next time.\n"
else:
winner = get_short_name(nodeID)
msg_out += f"\nGame over! {winner} wins! You sank all the AI's ships! 🎉\n"
else:
# P2P: Announce winner by short name
if game.player1_alive == 0:
winner = get_short_name(session.player2_id)
elif game.player2_alive == 0:
winner = get_short_name(session.player1_id)
else:
winner = "Nobody"
msg_out += f"\nGame over! {winner} wins! 🚢🏆\n"
msg_out += "Type 'battleship' to start a new game."
return msg_out
+94
View File
@@ -0,0 +1,94 @@
import pygame
import sys
import time
from modules.games.battleship import Battleship, SHIP_NAMES, SIZE, OCEAN, FIRE, HIT
CELL_SIZE = 40
BOARD_MARGIN = 50
STATUS_WIDTH = 320
def draw_board(screen, board, top_left, cell_size, show_ships=False):
font = pygame.font.Font(None, 28)
x0, y0 = top_left
for y in range(SIZE):
for x in range(SIZE):
rect = pygame.Rect(x0 + x*cell_size, y0 + y*cell_size, cell_size, cell_size)
pygame.draw.rect(screen, (100, 100, 200), rect, 1)
val = board[y][x]
# Show ships if requested, otherwise hide ship numbers
if not show_ships and val.isdigit():
val = OCEAN
color = (200, 200, 255) if val == OCEAN else (255, 0, 0) if val == FIRE else (0, 255, 0) if val == HIT else (255,255,255)
if val != OCEAN:
pygame.draw.rect(screen, color, rect)
text = font.render(val, True, (0,0,0))
screen.blit(text, rect.move(10, 5))
# Draw row/col labels
for i in range(SIZE):
# Col numbers
num_surface = font.render(str(i+1), True, (255, 255, 0))
screen.blit(num_surface, (x0 + i*cell_size + cell_size//2 - 8, y0 - 24))
# Row letters
letter_surface = font.render(chr(ord('A') + i), True, (255, 255, 0))
screen.blit(letter_surface, (x0 - 28, y0 + i*cell_size + cell_size//2 - 10))
def draw_status_panel(screen, game, top_left, width, height, is_player=True):
font = pygame.font.Font(None, 32)
x0, y0 = top_left
pygame.draw.rect(screen, (30, 30, 60), (x0, y0, width, height), border_radius=10)
# Title
title = font.render("Game Status", True, (255, 255, 0))
screen.blit(title, (x0 + 10, y0 + 10))
# Ships status
ships_title = font.render("Ships Remaining:", True, (200, 200, 255))
screen.blit(ships_title, (x0 + 10, y0 + 60))
# Get ship status
if is_player:
status_dict = game.get_ship_status(game.player_board)
else:
status_dict = game.get_ship_status(game.ai_board)
for i, ship in enumerate(SHIP_NAMES):
status = status_dict.get(i, "Afloat")
name_color = (200, 200, 255)
if status.lower() == "sunk":
status_color = (255, 0, 0)
status_text = "Sunk"
else:
status_color = (0, 255, 0)
status_text = "Afloat"
ship_name_surface = font.render(f"{ship}:", True, name_color)
screen.blit(ship_name_surface, (x0 + 20, y0 + 100 + i * 35))
status_surface = font.render(f"{status_text}", True, status_color)
screen.blit(status_surface, (x0 + 180, y0 + 100 + i * 35))
def battleship_visual_main(game):
pygame.init()
screen = pygame.display.set_mode((2*SIZE*CELL_SIZE + STATUS_WIDTH + 3*BOARD_MARGIN, SIZE*CELL_SIZE + 2*BOARD_MARGIN))
pygame.display.set_caption("Battleship Visualizer")
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
running = False
screen.fill((20, 20, 30))
# Draw radar (left)
draw_board(screen, game.get_player_radar(), (BOARD_MARGIN, BOARD_MARGIN+30), CELL_SIZE, show_ships=False)
radar_label = pygame.font.Font(None, 36).render("Your Radar", True, (0,255,255))
screen.blit(radar_label, (BOARD_MARGIN, BOARD_MARGIN))
# Draw player board (right)
draw_board(screen, game.get_player_board(), (SIZE*CELL_SIZE + 2*BOARD_MARGIN, BOARD_MARGIN+30), CELL_SIZE, show_ships=True)
board_label = pygame.font.Font(None, 36).render("Your Board", True, (0,255,255))
screen.blit(board_label, (SIZE*CELL_SIZE + 2*BOARD_MARGIN, BOARD_MARGIN))
# Draw status panel (far right)
draw_status_panel(screen, game, (2*SIZE*CELL_SIZE + 2*BOARD_MARGIN, BOARD_MARGIN), STATUS_WIDTH, SIZE*CELL_SIZE)
pygame.display.flip()
clock.tick(30)
pygame.quit()
sys.exit()
# if __name__ == "__main__":
# # Example: create a new game and show the boards
# game = Battleship(vs_ai=True)
# battleship_visual_main(game)
+2
View File
@@ -50,6 +50,7 @@ lemonadeTracker = [] # Lemonade Stand game tracker
dwPlayerTracker = [] # DopeWars player tracker
jackTracker = [] # Jack game tracker
mindTracker = [] # Mastermind (mmind) game tracker
battleshipTracker = [] # Battleship game tracker
# Memory Management Constants
MAX_MSG_HISTORY = 250
@@ -484,6 +485,7 @@ try:
surveyRecordID = config['games'].getboolean('surveyRecordID', True)
surveyRecordLocation = config['games'].getboolean('surveyRecordLocation', True)
wordOfTheDay = config['games'].getboolean('wordOfTheDay', True)
battleship_enabled = config['games'].getboolean('battleShip', True)
# messaging settings
responseDelay = config['messagingSettings'].getfloat('responseDelay', 0.7) # default 0.7
+8 -1
View File
@@ -235,6 +235,11 @@ if wordOfTheDay:
theWordOfTheDay = WordOfTheDayGame()
# this runs in background and wont enable other games
if battleship_enabled:
from modules.games.battleship import playBattleship # from the spudgunman/meshing-around repo
trap_list = trap_list + ("battleship",)
games_enabled = True
# Games Configuration
if games_enabled is True:
help_message = help_message + ", games"
@@ -262,6 +267,8 @@ if games_enabled is True:
gamesCmdList += "hamTest, "
if tictactoe_enabled:
gamesCmdList += "ticTacToe, "
if battleship_enabled:
gamesCmdList += "battleship, "
gamesCmdList = gamesCmdList[:-2] # remove the last comma
else:
gamesCmdList = ""
@@ -483,7 +490,7 @@ def cleanup_game_trackers(current_time):
tracker_names = [
'dwPlayerTracker', 'lemonadeTracker', 'jackTracker',
'vpTracker', 'mindTracker', 'golfTracker',
'hangmanTracker', 'hamtestTracker', 'tictactoeTracker', 'surveyTracker'
'hangmanTracker', 'hamtestTracker', 'tictactoeTracker', 'surveyTracker', 'battleshipTracker'
]
for tracker_name in tracker_names: