le jeu de Sowing
Le cadre
Le jeu de Sowing a été inventé par le mathématicien anglais John Horton Conway, qui l'a décrit en 1994 lors d'un atelier international sur la théorie des jeux combinatoires organisé par le Mathematical Sciences Research Institute (MSRI) à Berkeley, en Californie. Il s'agit d'un jeu de mancala 1 à un tour et à un rang.
Les règles
Un plateau est constitué d'une rangée de pots dans lesquels il y a initialement des graines.
Un plateau de 1x24 avec trois graines dans chaque pot peut être une bonne taille si le jeu est joué par des humains.
- Deux joueurs se font face,
- à chaque tour, un joueur :
- choisit un pot non vide,
- prend tout ce qu'il y a dans le pot,
- sème ces graines une à une :
- vers sa droite, dans les cases suivantes,
- sans sortir du jeu,
- et sans finir sur une case vide.
- Le dernier joueur qui est en mesure de faire un mouvement légal gagne.
Exemples de partie
On modélise le plateau par une liste Python contenant le nombre de graines dans chaque pot.
On désigne par :
- le joueur Sud : il est en bas de la liste, il joue vers notre droite.
- le joueur Nord : il est en face, en haut de la liste, il joue vers notre gauche, (mais vers sa droite).
On choisit ici d'étudier toutes les parties où
[2, 2, 2, 2]est la condition initiale,- Sud commence à jouer.
[0, 3, 3, 2]: le joueur Sud a joué à l'indice 0[0, 4, 4, 0]: le joueur Nord ne pouvait jouer qu'à l'indice 3- le joueur Sud a perdu, il ne peut plus jouer !
Sud n'aurait pas dû commencer à jouer en 0 !
[2, 0, 3, 3]: le joueur Sud a joué à l'indice 1[3, 1, 4, 0]: le joueur Nord ne pouvait jouer qu'à l'indice 3[3, 0, 5, 0]: le joueur Sud ne pouvait jouer qu'à l'indice 1- le joueur Nord a perdu, il ne peut plus jouer !
Si le joueur Sud commence en 1, il gagne !
Bilan avec un départ en [2, 2, 2, 2]
Il existe une stratégie gagnante pour le joueur qui commence dans la configuration [2, 2, 2, 2].
Exercice
Coder une fonction un_tour
- qui prend en paramètres :
joueur: ou bien"Sud", ou bien"Nord", ou bien un intrus (à vérifier), le nom du joueur qui joue.plateau: une liste d'entiers naturels qui indique le nombre de graines par pôt.j: un entier, l'indice du coup à jouer, il devrait être positif et valide (à vérifier).
- et qui, si le coup est valide, renvoie un nouveau plateau à l'issue de ce coup.
- ou sinon qui provoque une erreur, la première dans l'ordre, parmi :
raise KeyError("Joueur inconnu !"); seuls"Nord"et"Sud"peuvent jouer.raise IndexError("Début hors des limites du plateau !"); indices positifs obligatoires.raise ValueError("Début sur case vide !"); interdit, voir règles.raise IndexError("Sorti du plateau !"); interdit, voir règles.raise ValueError("Fin sur une case vide !"); interdit, voir règles.
On garantit que
tableau ne contient que des entiers naturels. (Inutile de le vérifier donc.)
Ne pas avoir peur des tests, il n'est pas nécessaire de comprendre la gestion des erreurs. C'est pourtant, ici, une très bonne occasion de découvrir une manière d'attraper une erreur.
Gestion des erreurs (facultatif)
Il n'est pas ici nécessaire de comprendre (même vaguement) la gestion des erreurs proposées, ni même de les créer. Voici, toutefois, des clés simples pour comprendre les tests.
try:
instruction_s()
except TrucError:
action_si_erreur()
else:
action_si_aucune_erreur()
try: On essaie d'exécuter une ou des instruction(s).except TrucError: le cas où une erreur de type Truc est provoquée, on peut alors faire une action dédiée. En effet, le programme ne s'arrête pas ; l'erreur a été attrapée.else: sinon, on peut réagir autrement.
Dans les tests ce cet exercice, on attend que les essais (try) conduisent aux erreurs spécifiques indiquées, et donc aux messages print("OK..."). Entrer dans la clause else n'est pas le comportement attendu.
Dans cet exercice, les différents types d'erreurs utilisés sont :
KeyError: une erreur liée à une clé (key).IndexError: une erreur liée à un indice (index).ValueError: une erreur liée à une valeur (value).
Il en existe bien d'autres !
Ainsi, quand votre fonction sera prête, vous aurez la sortie
OK. 'Autre' joueur n'a pas été autorisé à jouer.
OK. 'Sud' n'a pas été autorisé à jouer sur une case vide.
OK. 'Sud' n'a pas été autorisé à jouer hors du plateau.
OK. 'Sud' n'a pas été autorisé à jouer en finissant sur une case vide.
OK. 'Sud' n'a pas été autorisé à jouer en sortant du plateau.
Aide
- On pourra utiliser le dictionnaire
{"Nord": -1, "Sud": +1}pour connaitre la directiondjà prendre sur l'axe. On pourra aussi s'en servir initialement pour tester si un autre joueur tente illégalement de jouer ! - On pourra utiliser
suite = tableau.copy()pour avoir une copie indépendante à modifier. - On pourra vérifier que le coup est dans les limites du plateau.
- Puis qu'on commence sur uen case non vide.
- On tentera ensuite de verser les graines dans la bonne direction.
- si on sort du plateau, il faut le signifier.
- si on finit sur une case vide également
- On renvoie le plateau modifié !
Guide
def un_tour(joueur, tableau, j):
direction = {"Nord": -1, "Sud": +1}
if ...:
raise KeyError("Joueur inconnu !")
longueur = len(tableau)
if not(...):
raise IndexError("Début hors des limites du plateau !")
suite = tableau.copy()
nb_graines = ...
if ...:
raise ValueError("Début sur case vide !")
suite[j] = ...
i = j
di = direction[...]
while nb_graines > 0:
i += di
if ...:
raise IndexError("Sorti du plateau !")
suite[i] = ...
nb_graines = ...
if ...:
raise ValueError("Fin sur une case vide !")
return ...
# Tests(insensible à la casse)(Ctrl+I)
(Alt+: ; Ctrl pour inverser les colonnes)
(Esc)