Merge branch 'roboc'

This commit is contained in:
Fred Z 2018-02-21 23:17:29 +01:00
commit c594fa75b7
14 changed files with 697 additions and 0 deletions

11
roboc/cartes/facile.txt Normal file
View File

@ -0,0 +1,11 @@
OOOOOOOOOO
O O O O
O . OO O
O O O XO
O OOOO O.O
O O O U
O OOOOOO.O
O O O
O O OOOOOO
O . O O
OOOOOOOOOO

11
roboc/cartes/ma_carte.txt Normal file
View File

@ -0,0 +1,11 @@
OOOOOOOOOOOOOOOOO
OXO O O O
O . O OOO OO O O
O O O . O O O O
O O O O O O OO.O
O O OOOO O O U
O . . O OOO OO.O
O O O O OO OO O
O O OO OO O OO O
O O O O
OOOOOOOOOOOOOOOOO

3
roboc/cartes/mini.txt Normal file
View File

@ -0,0 +1,3 @@
OOO
OXU
OOO

View File

20
roboc/cartes/prison.txt Normal file
View File

@ -0,0 +1,20 @@
OOOOOOOOOOOOOOOOOOOO
OXO . O O . O
O.O O O O O O
O O O O O O O
O.O O . O O O
O O O OOOOOO O O
O O O O O
O O OOOOOOOOOO O
O.O . O O
O O O O O
O O O O O
O O.O O O
O.O . O O
O O O O O
O O O O O
O O O O O
O O O O O
O.O O O O
O O O U
OOOOOOOOOOOOOOOOOOOO

View File

@ -0,0 +1,26 @@
OOOOOOOOOOOOOOOOOOOO
OXO . O O . O
O.O O O O O O
O O O O O O O
O.O O . O O O
O O O OOOOOO O O
O O O O O
O O OOOOOOOOOO O
O.O . O O
O O O O O
O O O O O
O O.O O O
O.O . O O
O O O O O
O O O O O
O O O O O
O O O O O
O O O O O
O O O O O
O O O O O
O O O O O
O O O O O
O O O O O
O.O O O O
O O O U
OOOOOOOOOOOOOOOOOOOO

View File

@ -0,0 +1,11 @@
OOOOOOOOOO
O O O O
O . OO O
O O O O
O OOOO O.O
O O O U
O OOOOOO.O
O O O
O O OOOOOO
O . O O
OOOOOOOOOO

View File

@ -0,0 +1,2 @@
12
34

0
roboc/cartes/vide.txt Normal file
View File

161
roboc/configuration.py Normal file
View File

@ -0,0 +1,161 @@
"""
Author: freezed <freezed@users.noreply.github.com> 2018-02-11
Version: 0.1
Licence: `GNU GPL v3` GNU GPL v3: http://www.gnu.org/licenses/
Ce fichier fait partie du projet `roboc`
"""
# CONFIGURATION
# fichier de sauvegarde
BACKUP_FILE = '.backup'
# Commandes de refus/acceptation
MSG_NO_YES = ['non', 'oui']
# Commandes
# Le code utilise l'index des listes `COMMANDS` & `COMMANDS_LABEL`
# pour faire le lien entre les 2.
# Ici vous pouvez ajouter de nouvelles commandes de jeu, elle seront
# ajoutees a l'aide automatiquement. Mais il faudra ajouter le code
# leur correspondant dans la condition de traitement du mouvement.
COMMANDS = ['Q', 'H']
# Libelle des commandes d'interuption, conserver l'ordre
COMMANDS_LABEL = ['Sauvegarder & quitter', 'Aide']
# Commandes clavier de deplacement
DIRECTIONS = ['N', 'S', 'E', 'O']
# Étiquette des commandes clavier des de deplacements pour l'affichage
# de l'aide. Garder la correspondance
DIRECTIONS_LABEL = ['nord',
'sud',
'est',
'ouest']
# Pepertoire des fichiers carte
MAP_DIRECTORY = 'cartes/'
# Extention des fichiers carte
MAP_EXTENTION = '.txt'
# Elements dispo dans le labyrinthe
MAZE_ELEMENTS = {'wall': 'O',
'door': '.',
'exit': 'U',
'robo': 'X',
'void': ' '}
# Issue possible d'un mouvement, garder le OK toujours en fin de liste
MOVE_STATUS = ['bad', 'wall', 'exit', 'door', 'ok']
MOVE_STATUS_MSG = {
'bad': "Le déplacement «{}» n'est pas autorisé.",
'wall': "Le déplacement est stoppé par un mur.",
'exit': "Vous êtes sortit du labyrinte",
'door': "Vous passez une porte",
'ok': "Jusqu'ici, tout va bien…"
}
# Messages d'erreurs
ERR_ = "#!@?# Oups… "
ERR_MAP_FILE = ERR_ + "carte «{}» inaccessible!"
ERR_MAP_SIZE = ERR_ + "carte «{}», dimensions incorrecte: «{} x {}»"
ERR_MAP_ROBO = ERR_ + "robo est introuvable sur la carte «{}»!"
ERR_PLAGE = ERR_ + "saisir un nombre dans la plage indiquée! "
ERR_SAISIE = ERR_ + "saisir un nombre! "
ERR_UNKNOW = ERR_ + "personne n'est censé arriver ici…"
MIN_MAP_SIDE = 3
MSG_DISCLAMER = "Bienvenue dans Roboc."
MSG_AVAIBLE_BACKUP = "Sauvegarde dispo, voulez-vous la charger? ({}/{}) "
MSG_AVAIBLE_MAP = "Cartes disponible: "
MSG_BACKUP_DONE = "La partie a été sauvegardée."
MSG_BACKUP_GAME = "Partie sauvegardé chargée"
MSG_CHOOSE_MAP = "Choississez un numéro de carte: "
MSG_CHOOSE_MOVE = "Votre deplacement ({}:{}): "
MSG_START_GAME = "Votre partie commence"
MSG_END_GAME = "Fin du jeu."
# Recapitulatif des commandes
MSG_HELP = "Voici les commandes disponibles:\n"
MSG_SELECTED_MAP = "Vous avez fait le choix #{}, la carte «{}»."
# VARIABLES
maps_name_list = list() # liste des maps proposees a l'utilisateur
user_select_backup = str() # choix utilisateur: la sauvegarde
# FONCTIONS
def cls():
""" Efface l'historique de la console """
import os
os.system('clear')
return
def choose_maps_menu():
"""
Affiche le menu de selection des cartes
Recupere les cartes dans un repertoire, demande a l'utilisateur,
de choisir et effectue quelques tests sur la carte jouee avant de
creer
"""
from map import Map
# VARIABLES
user_select_map_id = -1 # choix utilisateur: une carte
print(MSG_AVAIBLE_MAP)
i = 0
for maps_name in maps_name_list:
print("\t[{}] - {}".format(i, maps_name))
i += 1
# Choix de la carte par l'utilisateur
while user_select_map_id > len(maps_name_list) or user_select_map_id < 0:
user_select_map_id = input(MSG_CHOOSE_MAP)
try:
user_select_map_id = int(user_select_map_id)
# ? if user_select_map_id is int(): ?
except ValueError:
user_select_map_id = -1
continue
if user_select_map_id > len(maps_name_list) or \
user_select_map_id < 0:
print(ERR_PLAGE)
cls() # vide l'ecran de la console
print(MSG_SELECTED_MAP.format(
user_select_map_id,
maps_name_list[user_select_map_id]
))
# Fichier carte a recuperer
map_file = MAP_DIRECTORY + \
maps_name_list[user_select_map_id] + \
MAP_EXTENTION
# instenciation de la carte choisie
return Map(map_file)
def get_msg_list(command, label):
"""
Formate une chaine pour afficher les commandes et leurs descriptifs
:type key: lst()
:param key: liste de commande
:type label: lst()
:param label: Texte descriptif des commande associee
:rtype: str()
:return: Chaine formatee assemblant les elements en parametres
"""
# Modele de mise en forme de la liste de chaque element
TEMPLATE = "\t- «{}»: {}\n"
# Variable de setour
result = str()
for key, value in enumerate(command):
result += TEMPLATE.format(value, label[key])
return result

243
roboc/map.py Normal file
View File

@ -0,0 +1,243 @@
"""
Author: freezed <freezed@users.noreply.github.com> 2018-02-06
Version: 0.1
Licence: `GNU GPL v3` GNU GPL v3: http://www.gnu.org/licenses/
Ce fichier fait partie du projet `roboc`
"""
import os
from configuration import DIRECTIONS, ERR_MAP_FILE, ERR_MAP_ROBO, \
MIN_MAP_SIDE, ERR_MAP_SIZE, ERR_UNKNOW, MAZE_ELEMENTS, MSG_START_GAME
class Map:
"""
Fourni les moyens necessaire a l'utilisation d'un fichier carte.
Controle de coherance sur la carte choise, deplace le robo en
fonction des commandes du joueur jusqu'en fin de partie.
Cette docstring contient des
[DocTests](http://sametmax.com/les-docstrings/), ça ne fait pas
partie du cours, mais c'est un outils facile de test/debug que
j'utilise et qui reste transparent.
Doctests
========
:Example:
>>> EasyMap = Map("cartes/facile.txt")
>>> MiniMap = Map("cartes/mini.txt")
>>> PrisonMap = Map("cartes/prison.txt")
>>> EmptyMap = Map("cartes/vide.txt")
>>> TooSmallMap = Map("cartes/trop_petite.txt")
>>> NoRoboMap = Map("cartes/sans_robo.txt")
>>> print(EmptyMap.status_message)
#!@?# Oups… carte «cartes/vide.txt», dimensions incorrecte: «0 x 0»
>>> print(TooSmallMap.status_message)
#!@?# Oups… carte «cartes/trop_petite.txt», dimensions incorrecte: «3 x 2»
>>> print(NoRoboMap.status_message)
#!@?# Oups… robo est introuvable sur la carte «cartes/sans_robo.txt»!
>>> print("_column_nb: {}".format(EasyMap._column_nb))
_column_nb: 11
>>> print("_line_nb: {}".format(EasyMap._line_nb))
_line_nb: 11
>>> MiniMap.move_to("O")
1
>>> MiniMap.move_to("Z1")
0
>>> MiniMap.move_to("4")
0
>>> MiniMap.move_to("E1")
2
>>> MiniMap.map_print()
OOO
O X
OOO
"""
def __init__(self, map_file):
"""
Initialisation de la carte utilise
Instancie un objet Map avec les attributs suivant:
:var int status: Etat de l'objet apres le deplacement
:var str status_message: Message relatif au deplacement
:var int _column_nb: Nbre de colonne du labyrinte (1ere ligne)
:var str _data_text: Contenu du labyrinte
:var str _element_under_robo: Element sous le robot
:var int _line_nb: Nbre de ligne du labyrinte
:var int _robo_position: position du robo dans _data_text
:param map_file: fichier «carte» avec chemin relatif
:rtype map: str()
:return: None
"""
# Chargement du fichier carte choisi
if os.path.isfile(map_file) is True:
with open(map_file, "r") as map_data:
# contenu de la carte en texte
self._data_text = map_data.read()
# contenu de la carte ligne a ligne
map_data_list = self._data_text.splitlines()
# nbre de colonne de la 1ere ligne de la carte
try:
self._column_nb = len(map_data_list[0]) + 1
except IndexError:
self._column_nb = 0
# nbre de ligne de la carte
try:
self._line_nb = len(map_data_list)
except IndexError:
self._line_nb = 0
# position du robot
self._robo_position = self._data_text.find(
MAZE_ELEMENTS['robo']
)
# element «sous» le robo, au depart
self._element_under_robo = MAZE_ELEMENTS['void']
# Quelques controle sur la carte chargee:
# dimensions assez grande pour deplacer un robo?
if self._column_nb < MIN_MAP_SIDE or \
self._line_nb < MIN_MAP_SIDE:
self.status = False
self.status_message = ERR_MAP_SIZE.format(
map_file, self._column_nb, self._line_nb
)
# y a t il un robo?
elif self._robo_position == -1:
self.status = False
self.status_message = ERR_MAP_ROBO.format(map_file)
# on peu ajouter plein d'autres controles ici
# carte consideree utilisable
else:
self.status = True
self.status_message = MSG_START_GAME
# Erreur de chargement du fichier
else:
self.status = False
self.status_message = ERR_MAP_FILE.format(map_file)
def map_print(self):
""" Affiche la carte avec la position de jeu courante """
print(self._data_text)
def move_to(self, move):
"""
Deplace le robo sur la carte
:param str move: mouvement souhaite
:return int: une cle de la constante MOVE_STATUS
"""
# decompose le mouvement
try: # on recupere le 1er caractere (la direction)
direction = move[0]
except IndexError:
return 0
except TypeError:
return 0
if len(move[1:]) > 0: # on recupere les caractere suivants (dist)
try:
goal = int(move[1:])
except ValueError as except_detail:
return 0
else: # si pas de chiffre, on avance d'une seule unite
goal = 1
steps = 0
# direction non conforme
if direction not in DIRECTIONS:
move_status = 0
# supprime le robo de son emplacement actuel et on replace
# l'elements «dessous»
else:
self._data_text = self._data_text.replace(
MAZE_ELEMENTS['robo'],
self._element_under_robo
)
# pour chaque pas on recupere la position suivante
while steps < goal:
steps += 1
if direction == DIRECTIONS[0]: # nord
next_position = self._robo_position - self._column_nb
elif direction == DIRECTIONS[1]: # sud
next_position = self._robo_position + self._column_nb
elif direction == DIRECTIONS[2]: # est
next_position = self._robo_position + 1
elif direction == DIRECTIONS[3]: # ouest
next_position = self._robo_position - 1
else: # juste au cas ou
raise NotImplementedError(ERR_UNKNOW)
self._element_under_robo = MAZE_ELEMENTS['void']
# Traitement en fonction de la case du prochain pas
next_char = self._data_text[next_position]
if next_char == MAZE_ELEMENTS['wall']:
move_status = 1
steps = goal
elif next_char == MAZE_ELEMENTS['exit']:
self._robo_position = next_position
move_status = 2
steps = goal
elif next_char == MAZE_ELEMENTS['door']:
self._robo_position = next_position
self._element_under_robo = MAZE_ELEMENTS['door']
move_status = 3
steps = goal
else:
self._robo_position = next_position
move_status = 4
# place le robo sur sa nouvelle position
self.place_element(MAZE_ELEMENTS['robo'])
return move_status
def place_element(self, element):
"""
Place l'element sur la carte.
La position est celle de l'attribut self._robo_position au
moment de l'appel.
Utilise pour place le robot et remettre les portes.
:param str element: element a placer sur la carte
"""
pos = self._robo_position
txt = self._data_text
self._data_text = txt[:pos] + element + txt[pos + 1:]
if __name__ == "__main__":
""" Starting doctests """
import doctest
doctest.testmod()

68
roboc/readme.md Normal file
View File

@ -0,0 +1,68 @@
# Roboc
## Consignes
> Votre mission est de concevoir et développer un petit jeu permettant de
contrôler un robot dans un labyrinthe. Ce jeu devra être développé en
console pour des raisons d'accessibilité. Je l'ai appelé... Roboc.
> Le jeu est un labyrinthe formé d'obstacles: des murs qui sont tout
simplement là pour vous ralentir, des portes qui peuvent être traversées
et au moins un point par lequel on peut quitter le labyrinthe. Si le
robot arrive sur ce point, la partie est considérée comme gagnée.
Pour les consignes détaillées de l'exercice rendez-vous dans le cours
_[Apprenez à programmer en Python](https://openclassrooms.com/courses/apprenez-a-programmer-en-python/exercises/180)_
d'**Open Classrooms**.
## Utilisation
Je n'ai pas utilisé les éléments de base fourni dans l'exercice. Le
script contient 4 fichiers et 1 répertoire:
- `roboc.py`: fichier à lancer
- `configuration.py`: contient les constantes/variables de
configuration du script, ainsi que les fonctions.
- `map.py`: fourni les moyens necessaire a l'utilisation d'un fichier
carte. Controle de coherance sur la carte choise, deplace le robo en
fonction des commandes du joueur jusqu'en fin de partie.
- `readme.md`, que vous êtes en train de lire!
- `cartes`: répertoire contenant les cartes de jeu (fichier `.txt`)
## Contrôle du robot
Le robot est contrôlable grâce à des commandes entrées au clavier. Il
doit exister les commandes suivantes :
- Q: sauvegarder et quitter la partie en cours
- N: déplacer vers le nord (haut de l'écran)
- E: déplacer vers l'est (droite de l'écran)
- S: déplacer vers le sud (bas de l'écran)
- O: déplacer vers l'ouest (gauche de l'écran)
- Chacune des directions ci-dessus suivies d'un nombre permet d'avancer
de plusieurs cases (ex. E3: 3 cases vers l'est)
## Remarques
Dans les fonctionnalites attendue, la consigne sur la sauvegarde à été
décrites dans 2 consignes qui m'ont semblée floue:
> Enregistrer automatiquement chaque partie à chaque coup pour permettre
de les continuer plus tard
> Q qui doit permettre de sauvegarder et quitter la partie en cours
J'ai choisi d'implementer la sauvegarde par tour, qui sera de fait,
réalisée quand le joueur quitte. Les 2 fonctionnalitées sont ainsi
respectées.
## Bonus
Quelques fonctionnalitées en bonus:
- contrôle/vérification des cartes, avec quelques cartes «de test», qui
ne sont pas jouable
- affichage du recap des commandes (aide), permettant d'ajouter
d'autres commades utilisateur plus tard
- la docstring du fichier `map.py` contient des [DocTests](http://sametmax.com/les-docstrings/),
ça ne fait pas partie du cours, mais c'est un outils facile de
test/debug que j'utilise et qui reste transparent.

122
roboc/roboc.py Normal file
View File

@ -0,0 +1,122 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Author: freezed <freezed@users.noreply.github.com> 2018-02-06
Version: 0.1
Licence: `GNU GPL v3` GNU GPL v3: http://www.gnu.org/licenses/
roboc
=====
Jeu permettant de controler un robot dans un labyrinthe.
Voir readme.md
"""
import os
import pickle
from configuration import BACKUP_FILE, choose_maps_menu, cls, COMMANDS, \
COMMANDS_LABEL, DIRECTIONS, DIRECTIONS_LABEL, get_msg_list, \
MAP_DIRECTORY, MAP_EXTENTION, maps_name_list, MOVE_STATUS, \
MOVE_STATUS_MSG, MSG_AVAIBLE_BACKUP, MSG_BACKUP_DONE, MSG_BACKUP_GAME, \
MSG_CHOOSE_MOVE, MSG_DISCLAMER, MSG_END_GAME, MSG_HELP, MSG_NO_YES, \
MSG_START_GAME, user_select_backup
# DEBUT DU JEU
# Recuperation de la liste des cartes
try:
maps_avaiable = os.listdir(MAP_DIRECTORY)
except FileNotFoundError as except_detail:
print("FileNotFoundError: «{}»".format(except_detail))
else:
for map_file in maps_avaiable:
filename_len = len(map_file) - len(MAP_EXTENTION)
# garde les fichiers avec la bonne extention
if map_file[filename_len:] == MAP_EXTENTION:
maps_name_list.append(map_file[: filename_len])
# Affichage du debut de partie
cls() # vide l'ecran de la console
print(MSG_DISCLAMER)
# Verif si un fichier de sauvegarde est dispo
if os.path.isfile(BACKUP_FILE) is True:
with open(BACKUP_FILE, 'rb') as backup_file:
backup_map = pickle.Unpickler(backup_file).load()
# On demande si l'utilisateur veut charger la sauvegarde
while user_select_backup not in MSG_NO_YES:
user_select_backup = input(
MSG_AVAIBLE_BACKUP.format(*MSG_NO_YES)
).lower()
# utilisateur veut la sauvegarde
if user_select_backup == MSG_NO_YES[1]:
current_map = backup_map
else:
current_map = choose_maps_menu()
else:
current_map = choose_maps_menu()
# DEBUT DE BOUCLE DE TOUR DE JEU
# Affichage de la carte tant que status == True
while current_map.status:
# Affiche la carte et le message
current_map.map_print()
print(current_map.status_message)
# Demande a l'utilisateur son choix du deplacement
user_move = input(MSG_CHOOSE_MOVE.format(
COMMANDS[1], COMMANDS_LABEL[1])
).upper()
cls() # vide l'ecran de la console
# Traitement de la commande utilisateur
if user_move == COMMANDS[0]: # quitter
current_map.status = False
# Le premier tour n'a pas ete joue, pas de sauvegarde faite
if current_map.status_message != MSG_START_GAME:
current_map.status_message = MSG_BACKUP_DONE
else:
current_map.status_message = ""
elif user_move == COMMANDS[1]: # Affiche l'aide
current_map.status_message = MSG_HELP
# liste les directions
current_map.status_message += get_msg_list(
DIRECTIONS, DIRECTIONS_LABEL
)
# liste les commandes
current_map.status_message += get_msg_list(COMMANDS, COMMANDS_LABEL)
else: # Traitement du deplacement
status = current_map.move_to(user_move)
message = MOVE_STATUS_MSG[MOVE_STATUS[status]].format(user_move)
# La sortie est atteinte, fin de la boucle
if MOVE_STATUS[status] == 'exit':
current_map.status = False
current_map.status_message = message
else: # sinon on sauvegarde la partie avant de refaire un tour
current_map.status_message = MSG_BACKUP_GAME
with open(BACKUP_FILE, 'wb') as backup_file:
pickle.Pickler(backup_file).dump(current_map)
current_map.status_message = message
# fin de la boucle de tour
if current_map.status is False:
print(current_map.status_message)
# Fin de partie
print(MSG_END_GAME)
current_map.map_print()

19
roboc/todo.md Normal file
View File

@ -0,0 +1,19 @@
# TODO list
Piste d'amelioration du code après avoir vu le corrigé-type et fait la
correction des autres élèves.
- Quand la partie est gagnée, on supprime la sauvegarde
- simplification de gestion de fichier avec endswith(), os.path.join()
- creer un objet pour traiter la rencontre d'un element, avec des
attributs et methodes qui permettront d'ajouter des obstacles simplement
dans la conf et sur la carte, plutot que de rajouter une condition dans
le script traitant les mouvements et mettre a jour les attributs de laç
carte _à la main_
- definir dans sa classe les caracteristique d'une carte valide plutôt
que dans le script
- permettre a un element de ralentir/accelerer la progression du robot
- proposer la suppression d'une carte invalide
- limiter un mouvement a la dimension max de la carte sur l'axe concerné
- Utiliser des cartes aux lignes de longueur variable
-