396 lines
12 KiB
Python
Executable file
396 lines
12 KiB
Python
Executable file
#!/usr/bin/python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
TP AP1
|
|
Licence SESI 1ère année
|
|
Univ. Lille 1
|
|
|
|
bataille_navale.py
|
|
|
|
Module pour le jeu de bataille navale.
|
|
|
|
Pour une description générale du jeu voir
|
|
http://fr.wikipedia.org/wiki/Bataille_navale_%28jeu%29
|
|
|
|
"""
|
|
|
|
__author__ = 'BEAUSSART Jean-loup & PREUD\'HOMME Geoffrey'
|
|
__date_creation__ = 'Mon, 16 Feb 2015 19:30:54 +0100'
|
|
|
|
|
|
###############################################
|
|
# Modules utilisés
|
|
###############################################
|
|
|
|
# Pour la disposition aléatoire des navires
|
|
from random import randint
|
|
|
|
# Pour le fichier des scores
|
|
from datetime import datetime
|
|
|
|
###############################################
|
|
# Constantes nommées utilisées
|
|
###############################################
|
|
|
|
# les deux dispositions possibles pour un navire
|
|
# sur le plateau :
|
|
# - H : horizontale
|
|
# - V : verticale
|
|
DISPOSITIONS = "HV"
|
|
|
|
|
|
# codes réponses d'analyse de tirs
|
|
RATE = 0
|
|
TOUCHE = 1
|
|
COULE = 2
|
|
|
|
# nom du fichier contenant les scores réalisés
|
|
FICHIER_RESULT = 'bataille_navale_scores.txt'
|
|
|
|
###############################################
|
|
# Procédure principale du jeu
|
|
###############################################
|
|
|
|
|
|
def jouer(nom, descr):
|
|
"""
|
|
str, str -> ()
|
|
procédure de jeu complet de bataille navale,
|
|
le nom du joueur est donné par le paramètre nom,
|
|
et le jeu est décrit dans le fichier descr.
|
|
|
|
CU : le fichier jeu doit exister et être conforme
|
|
à un fichier de description.
|
|
"""
|
|
jeu = cree_jeu(descr)
|
|
decrire_le_jeu(jeu)
|
|
nbre_tirs = 0
|
|
while not tous_coules(jeu):
|
|
# afficher_jeu(jeu) # Décommenter pour une nouvelle fonctionalité
|
|
tir = lire_un_tir(nom)
|
|
nbre_tirs += 1
|
|
nav, res = analyse_un_tir(jeu, tir)
|
|
if res == RATE:
|
|
print("raté.")
|
|
elif res == TOUCHE:
|
|
print(nav + " touché.")
|
|
else:
|
|
print(nav + " coulé.")
|
|
sauver_result(nom, descr, nbre_tirs)
|
|
print("Terminé en {0} tirs".format(nbre_tirs))
|
|
|
|
###############################################
|
|
# Opérations sur les fichiers
|
|
###############################################
|
|
|
|
|
|
def lire_donnees(num_descr):
|
|
"""
|
|
str -> tuple
|
|
renvoie un triplet dont les deux premières composantes sont
|
|
et la troisième une liste de couples (nature, taille) où
|
|
nature est une chaîne de caractères décrivant la nature du navire
|
|
et taille un entier désignant le nombre de cases occupées par ce navire.
|
|
Toutes ces données sont lues dans un fichier nommé 'jeu'+num_descr+'.txt'.
|
|
|
|
CU : le fichier 'jeu'+num_descr+'.txt' doit exister et être au bon format,
|
|
ie un fichier texte contenant :
|
|
larg : haut
|
|
nature1 : taille1
|
|
nature2 : taille2
|
|
...
|
|
"""
|
|
# with s'occupe de fermer automatiquement le fichier ouvert, c'est un gestionnaire de contexte.
|
|
# Il ferme le fichier à la fin du bloc, si une exception est levée ou si on rencontre un
|
|
# break/return, autrement dit, quoi qu'il arrive
|
|
with open('jeu%s.txt' % num_descr, 'r') as fichier:
|
|
dimensions = fichier.readline().split(':')
|
|
largeur, hauteur = int(dimensions[0].strip()), int(dimensions[1].strip())
|
|
|
|
navires = list()
|
|
|
|
for ligne in fichier:
|
|
couple = ligne.split(':')
|
|
navires.append((couple[0].strip(), int(couple[1].strip())))
|
|
|
|
return (largeur, hauteur, navires)
|
|
|
|
# Tests
|
|
# print(lire_donnees('1'))
|
|
# print(lire_donnees('2'))
|
|
# print(lire_donnees('3')) # Fichier personnalisé
|
|
|
|
|
|
# Sauvegarde du bilan
|
|
|
|
# >>> datetime.today()
|
|
# datetime.datetime(2015, 2, 16, 19, 6, 17, 207811)
|
|
# On obtient la date et l'heure sous forme d'objet datetime
|
|
|
|
# >>> str(datetime.today())
|
|
# '2015-02-16 19:07:31.624846'
|
|
# On obtient la date et l'heure au format AAAA-MM-JJ HH:MM:SS.UUUUUU
|
|
|
|
|
|
def sauver_result(nom, jeu, nbre):
|
|
"""
|
|
str, str, int -> NoneType
|
|
ajoute une ligne dans le fichier FICHIER_RESULT
|
|
contenant le nom, le numéro du jeu joué et le nombre de tirs effectués
|
|
dans la partie.
|
|
|
|
CU : aucune
|
|
"""
|
|
|
|
with open(FICHIER_RESULT, 'a') as fichierScores:
|
|
fichierScores.write(':'.join([nom, str(jeu), str(nbre), str(datetime.today())]) + '\n')
|
|
|
|
# sauver_result('Pisitrate', 2, 125)
|
|
# sauver_result('Aristide', 52, 12)
|
|
# sauver_result('Caligula', 1112, 12356)
|
|
# sauver_result('Périclès', 12, 1458)
|
|
|
|
###############################################
|
|
# Procédures de construction du jeu
|
|
###############################################<
|
|
|
|
|
|
def cree_jeu(descr):
|
|
"""
|
|
str -> dict
|
|
renvoie un nouveau jeu de bataille navale construit à partir des données
|
|
lues dans le fichier descr.
|
|
|
|
|
|
Le jeu est représenté par un dictionnaire à quatre clés :
|
|
- 'plateau' pour représenter le plateau de jeu (l'espace maritime) avec ses navires
|
|
- 'nb_cases_occupees' dont la valeur associée est le nombre de cases du plateau
|
|
occupées par les navires
|
|
- 'touches' dictionnaire contenant deux champs :
|
|
* l'un nomme 'nb_touches' contenant un entier
|
|
* l'autre nomme 'etats_navires' qui contient un dictionnaire
|
|
donnant pour chaque navire le nombre de tirs
|
|
qu'ils peuvent encore recevoir avant d'être coulés
|
|
- 'coups_joues' ensemble des coups joués depuis le début de la partie.
|
|
|
|
CU : le fichier doit contenir une description correcte du jeu (cf lire_donnees)
|
|
"""
|
|
|
|
donnees = lire_donnees(descr)
|
|
nb_cases_occupees = 0
|
|
etats_navires = dict()
|
|
for n in donnees[2]:
|
|
etats_navires[n[0]] = n[1]
|
|
nb_cases_occupees += n[1]
|
|
return {'plateau': cree_plateau(*donnees), 'nb_cases_occupees' : nb_cases_occupees, \
|
|
'touches': {'nb_touches': 0, 'etats_navires': etats_navires}, 'coups_joues': set()}
|
|
|
|
# Tests
|
|
# print(cree_jeu('1'))
|
|
# print(cree_jeu('2'))
|
|
# print(cree_jeu('3'))
|
|
|
|
def cree_plateau(l, h, l_nav):
|
|
"""
|
|
int, int, list -> dict
|
|
renvoie un plateau de largeur l et de hauteur h occupé par les navires
|
|
de l_nav.
|
|
La disposition est aléatoire.
|
|
|
|
CU : les dimensions doivent permettre le placement de tous les navires
|
|
"""
|
|
esp = {'larg': l, 'haut': h}
|
|
for nav in l_nav:
|
|
placer(esp, nav)
|
|
return esp
|
|
|
|
def cases_du_navire(nav, pos, disp):
|
|
"""
|
|
tuple, tuple, str -> list
|
|
fournit une liste des cases occupées par un navire nav
|
|
aux coordonnées pos dans la disposition disp
|
|
CU : disp = 'H' ou 'V'
|
|
"""
|
|
assert(disp in DISPOSITIONS), 'disp = \'H\' ou \'V\''
|
|
|
|
cases = list()
|
|
if disp == 'H':
|
|
for i in range(pos[0], pos[0]+nav[1]):
|
|
cases.append((i, pos[1]))
|
|
elif disp == 'V':
|
|
for i in range(pos[1], pos[1]+nav[1]):
|
|
cases.append((pos[0], i))
|
|
return cases
|
|
|
|
def est_placable(esp, nav, pos, disp):
|
|
"""
|
|
dict, tuple, tuple, str -> bool
|
|
indique si le navire nav aux coordonnées pos peut être posé dans la
|
|
disposition disp dans l'espace maritime esp
|
|
CU : disp = 'H' ou 'V'
|
|
"""
|
|
|
|
assert(disp in DISPOSITIONS), 'disp = \'H\' ou \'V\''
|
|
|
|
for c in cases_du_navire(nav, pos, disp):
|
|
if c[0] > esp['larg'] or c[0] <= 0 or c[1] <= 0 or c[1] > esp['haut'] or c in esp:
|
|
return False
|
|
return True
|
|
|
|
# Tests
|
|
# espTest = {'larg': 4, 'haut': 3, (1, 2): 'grand', (2, 2): 'grand', (4, 1): 'petit'}
|
|
# print(est_placable(espTest, ('Poutre', 3), (1, 1), 'H')) # True
|
|
# print(est_placable(espTest, ('Buche', 3), (1, 1), 'V')) # False
|
|
# print(est_placable(espTest, ('Règle', 3), (3, 1), 'V')) # True
|
|
# print(est_placable(espTest, ('Antenne', 3), (3, 2), 'V')) # False
|
|
|
|
def placer(esp, nav):
|
|
"""
|
|
dict, tuple -> NoneType
|
|
place le navire nav dans l'espace maritime esp.
|
|
Choix de l'emplacement aléatoire.
|
|
|
|
CU : il doit rester de la place
|
|
"""
|
|
|
|
while True:
|
|
pos = (randint(0, esp['larg']-1), randint(0, esp['haut']-1))
|
|
disp = DISPOSITIONS[randint(0, 1)]
|
|
if est_placable(esp, nav, pos, disp):
|
|
for c in cases_du_navire(nav, pos, disp):
|
|
esp[c] = nav[0]
|
|
return
|
|
|
|
# Tests
|
|
# espTest2 = {'larg': 4, 'haut': 3}
|
|
# placer(espTest2, ('grand', 2))
|
|
# placer(espTest2, ('petit', 1))
|
|
# print(espTest2) # Aléatoire
|
|
|
|
|
|
###############################################
|
|
# Procédures de déroulement du jeu
|
|
###############################################
|
|
|
|
def decrire_le_jeu(jeu):
|
|
"""
|
|
dict -> ()
|
|
imprime une description du jeu.
|
|
|
|
CU : aucune
|
|
"""
|
|
print('Dimensions du plateau de jeu :')
|
|
print('- largeur : %d' % jeu['plateau']['larg'])
|
|
print('- hauteur : %d' % jeu['plateau']['haut'])
|
|
|
|
print('Navires :')
|
|
etats = jeu['touches']['etats_navires']
|
|
for navire in etats:
|
|
print('- %s : %d case%s' %
|
|
(navire, etats[navire], 's' * (etats[navire] >= 2)))
|
|
print(
|
|
'À vous de jouer en répondant à l\'invite ?- par deux nombres séparés par une virgule.')
|
|
|
|
def afficher_jeu(jeu):
|
|
"""
|
|
dict -> ()
|
|
imprime une description visuelle du jeu
|
|
|
|
CU : aucune
|
|
"""
|
|
if jeu['plateau']['larg'] > 30:
|
|
return
|
|
for x in range(1, jeu['plateau']['larg']+1):
|
|
for y in range(1, jeu['plateau']['haut']+1):
|
|
if (x, y) in jeu['coups_joues']:
|
|
if (x, y) in jeu['plateau']:
|
|
print('o', end=' ')
|
|
else:
|
|
print('x', end=' ')
|
|
else:
|
|
print('.', end=' ')
|
|
print()
|
|
|
|
def lire_un_tir(nom):
|
|
"""
|
|
str -> tuple
|
|
renvoie un couple d'entiers lus sur l'entrée standard
|
|
|
|
CU : l'entrée doit être de la forme xxx,yyy avec xxx et yyy
|
|
une représentation décimale de deux nombres entiers
|
|
"""
|
|
while True:
|
|
try:
|
|
entree = input('%s - ' % nom)
|
|
dec = entree.split(',')
|
|
x, y = int(dec[0]), int(dec[1])
|
|
assert x > 0 and y > 0
|
|
return (x, y)
|
|
except KeyboardInterrupt:
|
|
sys.exit(0)
|
|
except IndexError:
|
|
print('Merci de saisir deux composantes séparées par une virgule')
|
|
except ValueError:
|
|
print('Merci de saisir des composantes entières')
|
|
except AssertionError:
|
|
print('Merci de saisir des composantes strictement positives')
|
|
|
|
|
|
def analyse_un_tir(jeu, tir):
|
|
"""
|
|
dict, tuple -> str,int
|
|
renvoie
|
|
- ("",RATE) si tir raté
|
|
- (nav,TOUCHE) si nav touché
|
|
- (nav,COULE) si nav coulé
|
|
et modifie l'état du jeu en conséquence.
|
|
|
|
CU : aucune
|
|
"""
|
|
if tir in jeu['coups_joues']:
|
|
return ('', RATE)
|
|
elif tir in jeu['plateau']:
|
|
jeu['coups_joues'].add(tir)
|
|
nav = jeu['plateau'][tir]
|
|
jeu['touches']['nb_touches'] += 1
|
|
jeu['touches']['etats_navires'][nav] += -1
|
|
if jeu['touches']['etats_navires'][nav] > 0:
|
|
return (nav, TOUCHE)
|
|
else:
|
|
return (nav, COULE)
|
|
else:
|
|
jeu["coups_joues"].add(tir)
|
|
return ('', RATE)
|
|
|
|
# Tests
|
|
# test = {'nb_cases_occupees': 3, 'plateau': {'haut': 3, (2, 3): 'grand', 'larg': 4, (2, 1): \
|
|
# 'petit', (2, 2): 'grand'}, 'coups_joues': set((1, 2)), 'touches': {'etats_navires': {'grand': 2, \
|
|
# 'petit': 1}, 'nb_touches': 0}}
|
|
# print(analyse_un_tir(test, (1, 1)) == ('', RATE))
|
|
# print(analyse_un_tir(test, (1, 2)) == ('', RATE))
|
|
# print(analyse_un_tir(test, (2, 2)) == ('grand', TOUCHE))
|
|
# print(analyse_un_tir(test, (2, 1)) == ('petit', COULE))
|
|
|
|
def tous_coules(jeu):
|
|
"""
|
|
dict -> bool
|
|
renvoie True si tous les navires du plateau de jeu ont été coulés
|
|
et False dans le cas contraire.
|
|
|
|
CU : aucune
|
|
"""
|
|
return jeu['touches']['nb_touches'] >= jeu['nb_cases_occupees']
|
|
|
|
|
|
###############################################
|
|
# Pour une utilisation du module depuis un terminal
|
|
###############################################
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
if len(sys.argv) != 3:
|
|
jouer('Jean Bart', '1')
|
|
else:
|
|
jouer(sys.argv[1], sys.argv[2])
|