Python Battleship
Python Battleship
Codul complet
import pandas as pd
import random
class BattleshipBoard:
column_list = ["a", "b", "c", "d", "e"]
row_list = [1, 2, 3, 4, 5]
def __init__(self):
self._board = pd.DataFrame([[ '*' for _ in range(1, 6)] for _ in range(5) ], columns = self.column_list, index = self.row_list)
self._mask_board = pd.DataFrame([[ '*' for _ in range(1, 6)] for _ in range(5) ], columns = self.column_list, index = self.row_list)
self.generate_ship(1)
self.generate_ship(2)
self.generate_ship(3)
@property
def board(self):
return self._board
@property
def mask_board(self):
return self._mask_board
def generate_ship(self, size):
while True:
column = random.choice(self.column_list)
start_pos = random.randint(1, len(self.column_list))
if(start_pos+size-1) > len(self.column_list):
continue
ship_position = set(self._board.loc[start_pos:start_pos+size-1, column].values.flatten())
if (not ship_position.intersection([1])):
self._board.loc[start_pos:start_pos+size-1, column] = 1
break
def shoot(self, row, column):
if(self._board.loc[int(row), column]) == 1:
print(f"{column}{row} ------> Boom!!\n\n")
self._board.loc[int(row), column] = "X"
self._mask_board.loc[int(row), column] = "X"
else:
print(f"{column}{row} ------> Pe langa!!\n\n")
self._mask_board.loc[int(row), column] = "-"
self._board.loc[int(row), column] = "-"
@property
def get_remaining_ships(self):
return self._board.eq(1).sum().sum()
class Player:
def __init__(self, name):
self._player = name
self._board = BattleshipBoard()
@property
def player(self):
return self._player
@property
def board(self):
return self._board.board
@property
def mask_board(self):
return self._board.mask_board
class BattleshipGame:
bot_shot_history = []
player_shot_history = []
def __init__(self, player1):
self.player1 = player1
self.player2 = Player("Programatic Bot")
def is_game_ongoing(self, player1, player2):
if player1._board.get_remaining_ships == 0:
return False, player1.player
if player2._board.get_remaining_ships == 0:
return False, player2.player
return (True,)
def start_game(self):
i = 1
while True:
print(f"Tabla ta {self.player1.player}\n{self.player1.board}")
self.handle_player_shot()
self.handle_bot_shot()
game_state = self.is_game_ongoing(self.player1, self.player2)
if game_state[0] == False:
winner = self.player2.player if game_state[1] == self.player1.player else self.player1.player
print(f"Jocul s-a terminat... \n A castigat {winner}")
break
i += 1
print(f"Runda {i}")
def handle_player_shot(self):
print(f"Tabla lui {self.player2.player}")
print(self.player2.mask_board)
while True:
index = input("\n\nCoordonatele(ex: a1, e5) -> ")
if f"{index[1]}{index[0]}" not in self.player_shot_history:
self.player_shot_history.append(f"{index[1]}{index[0]}")
break
else:
print(f"Ai folosit deja coordonatele: {index[1]}{index[0]} Incearca altele")
self.player2._board.shoot(index[1], index[0])
def handle_bot_shot(self):
while True:
column = random.choice(self.player1._board.column_list)
row = random.choice(self.player1._board.row_list)
if f"{column}{row}" not in self.bot_shot_history:
self.bot_shot_history.append(f"{column}{row}")
break
print(f"Programatic Bot a tras: {column}{row}")
self.player1._board.shoot(row, column)
player_name = input("Creeaza numele jucatorului tau: ")
player = Player(player_name)
BattleshipGame(player).start_game()
Explicarea codului
Importarea bibliotecilor
import pandas as pd
import random
pandas
- Pandas este o bibliotecă populară în Python, utilizată pentru manipularea și analiza datelor. În contextul jocului nostru, folosim Pandas pentru a crea și manipula tabele 2D denumite DataFrames, care vor reprezenta tabla de joc.
- DataFrame funcționează similar cu o matrice sau tabel, având rânduri și coloane. Este ideal pentru gestionarea coordonatelor de pe tabla de joc.
random
- Biblioteca random este folosită pentru a genera valori aleatorii, esențiale în acest joc, deoarece navele sunt plasate aleatoriu pe tablă și botul trage tot aleatoriu.
Clasa BattleshipBoard
Clasa BattleshipBoard definește tabla de joc și logica de amplasare a navelor. Acesta este punctul central al jocului, întrucât în această clasă se gestionează ambele table de joc (jucător și bot).
class BattleshipBoard:
column_list = ["a", "b", "c", "d", "e"]
row_list = [1, 2, 3, 4, 5]
column_list și row_list
column_list
șirow_list
definesc dimensiunile și coordonatele tablei de joc.column_list
: are 5 coloane etichetate cu litere ('a', 'b', 'c', 'd', 'e').row_list
: are 5 rânduri etichetate cu numere (1, 2, 3, 4, 5).
- Aceste liste sunt utilizate pentru a crea capetele de tabel și pentru a genera coordonatele necesare atât la plasarea navelor, cât și la tragerea loviturilor.
Constructorul clasei
def __init__(self):
self._board = pd.DataFrame([[ '*' for _ in range(1, 6)] for _ in range(5)], columns=self.column_list, index=self.row_list)
self._mask_board = pd.DataFrame([[ '*' for _ in range(1, 6)] for _ in range(5)], columns=self.column_list, index=self.row_list)
self.generate_ship(1)
self.generate_ship(2)
self.generate_ship(3)
self._board
: Inițializează tabla de joc. Aceasta este o DataFrame 5x5, cu*
în toate celulele. Asteriscul (*) reprezintă apa.DataFrame
este creat cu 5 rânduri și 5 coloane. Fiecare element din matrice este marcat cu*
.columns=self.column_list
: etichetează coloanele (a-e).index=self.row_list
: etichetează rândurile (1-5).
self._mask_board
: Reprezintă o a doua tablă de joc, utilizată pentru a ascunde locațiile reale ale navelor. Aceasta este folosită în interacțiunile cu jucătorul pentru a afișa doar loviturile reușite(X)
și ratările(-)
, fără a expune locația navelor.generate_ship()
: Plasează 3 nave pe tabla de joc. Navele au dimensiuni diferite: 1, 2 și 3. Fiecare navă este amplasată pe verticală pe o coloană aleatorie și la o poziție de început aleatorie.
Metoda generate_ship
def generate_ship(self, size):
while True:
column = random.choice(self.column_list)
start_pos = random.randint(1, len(self.column_list))
if (start_pos + size - 1) > len(self.column_list):
continue
ship_position = set(self._board.loc[start_pos:start_pos + size - 1, column].values.flatten())
if (not ship_position.intersection([1])):
self._board.loc[start_pos:start_pos + size - 1, column] = 1
break
Această metodă plasează o navă de dimensiune size pe tabla de joc. Să vedem cum funcționează detaliat:
Alegerea coloanei și poziției de start:
column = random.choice(self.column_list)
: Selectează aleatoriu o coloană din listacolumn_list
.start_pos = random.randint(1, len(self.column\_list))
: Selectează aleatoriu o poziție de start pe coloană. Poziția de start este un număr între 1 și 5 (coordonatele rândurilor).
Verificarea dacă nava se încadrează pe tablă:
if (start_pos + size - 1) > len(self.column_list)
: Dacă nava nu se încadrează pe tablă (de exemplu, dacă o navă de mărime 3 începe la rândul 4, ea ar ieși în afara grilei), se trece la o altă iterație a buclei.
Verificarea suprapunerii cu alte nave
:ship_position = set(self._board.loc[start_pos:start_pos + size - 1, column].values.flatten())
: Aceasta linie ia toate valorile de pe coloana selectată, în intervalul de rânduri unde nava urmează să fie plasată, și le adaugă într-un set. Dacă pe acele poziții există deja o navă (valori1
), plasarea este anulată și bucla continuă până când găsește o poziție liberă.
Plasarea navei:
self._board.loc[start_pos:start_pos + size - 1, column] = 1
: Dacă pozițiile selectate sunt libere, nava este plasată pe tablă.1
reprezintă o parte a navei.
Proprietățile board și mask_board
@property
def board(self):
return self._board
@property
def mask_board(self):
return self._mask_board
Aceste două proprietăți sunt metode de acces pentru a obține tabla completă a jocului (board) și tabla mascată (mask_board), care va fi afișată jucătorului.
Metoda shoot
Aceasta metodă procesează loviturile jucătorului sau ale botului pe tabla de joc.
def shoot(self, row, column):
if(self._board.loc[int(row), column]) == 1:
print(f"{column}{row} ------> Boom!!\n\n")
self._board.loc[int(row), column] = "X"
self._mask_board.loc[int(row), column] = "X"
else:
print(f"{column}{row} ------> Pe langa!!\n\n")
self._mask_board.loc[int(row), column] = "-"
self._board.loc[int(row), column] = "-"
Primirea coordonatelor: Primește două argumente: rândul (
row
) și coloana (column
) unde jucătorul sau botul trage.Determinarea loviturii:
if(self._board.loc[int(row), column]) == 1
: Verifică dacă pe poziția selectată există o parte a navei (1
). Dacă există, lovitura este reușită.
Actualizarea tablei:
- Lovitură reușită: Dacă lovitura este reușită, valoarea de pe
self._board
șiself._mask_board
se schimbă înX
pentru a marca lovitura. - Lovitură ratată: Dacă lovitura este ratată (adică pe poziția selectată era apă), tabla este actualizată cu
-
.
- Lovitură reușită: Dacă lovitura este reușită, valoarea de pe
Proprietatea get_remaining_ships
@property
def get_remaining_ships(self):
return self._board.eq(1).sum().sum()
get_remaining_ships
returnează numărul de părți ale navelor rămase pe tabla jucătorului. Folosește funcțiaeq(1)
pentru a număra toate pozițiile care conțin valoarea1
(navele), iarsum()
însumează valorile pentru a returna numărul total de nave rămase.
Clasa Player
Clasa Player definește un jucător al jocului.
class Player:
def __init__(self, name):
self._player = name
self._board = BattleshipBoard()
name
: Numele jucătorului._board
: O instanță a clasei BattleshipBoard, reprezentând tabla de joc pentru acest jucător.
Clasa BattleshipGame
Clasa BattleshipGame controlează întreaga logică a jocului, inclusiv gestionarea rundelor, tragerea loviturilor și determinarea câștigătorului.
Metoda is_game_ongoing
Această metodă verifică dacă jocul mai este activ.
def is_game_ongoing(self, player1, player2):
if player1._board.get_remaining_ships == 0:
return False, player1.player
if player2._board.get_remaining_ships == 0:
return False, player2.player
return (True,)
- Verifică dacă unul dintre jucători nu mai are nave. Dacă toate navele unui jucător au fost distruse, jocul se termină.
Concluzie
Acest tutorial a demonstrat pas cu pas cum să implementăm un joc de Battleship în Python, folosind Pandas pentru manipularea tabelelor și generarea unei grile de joc 5x5. Am început cu construcția clasei BattleshipBoard, care gestionează plasarea navelor și actualizarea tablei de joc. Am explorat detaliat cum navele sunt plasate aleatoriu pe grilă și cum sunt verificate suprapunerile pentru a asigura un joc corect. De asemenea, am discutat despre utilizarea a două tabele: una care reprezintă pozițiile reale ale navelor și una care maschează aceste poziții pentru a oferi o experiență de joc autentică.
Apoi, am trecut la definirea jucătorilor și a interacțiunii dintre aceștia și tabla de joc. Am explicat cum fiecare jucător, inclusiv botul, poate trage în poziții aleatorii, iar loviturile sunt gestionate prin actualizarea tablei și prin feedback-ul vizual oferit jucătorului (prin simbolurile "X" pentru lovituri reușite și "-" pentru cele ratate). Logica jocului a fost gestionată de clasa BattleshipGame, care coordonează alternarea rundelor și verificarea stării jocului pentru a determina când un jucător a câștigat.
Acest proiect nu doar că ilustrează concepte fundamentale de programare în Python, cum ar fi manipularea datelor în structuri bidimensionale și utilizarea funcțiilor aleatorii, dar pune și accent pe organizarea codului și modularizarea logicii unui joc complex. Extensibilitatea codului permite adăugarea de noi funcționalități, cum ar fi nave cu forme diferite, grile mai mari, sau chiar mai mulți jucători.
Cu această implementare de bază, puteți construi un joc mai complex, îmbunătățindu-vă abilitățile de programare și înțelegerea conceptelor de dezvoltare software. Am reușit să creăm un proiect captivant și interactiv, cu potențial de extindere, care demonstrează aplicabilitatea bibliotecilor Python precum Pandas în soluții mai creative, nu doar în analiza datelor.