Skip to content

Il voyait des spirales partout…

♪ …et pour lui ça voulait dire des trucs : ♫ ♪

Spirales sur essuie-tout

Je suis tombé sur cet essuie-tout par hasard et ça m’a rappelé de suite un logo, voire un thème. 🙂

Écran de connexion à debian9

Et pour quelques elif de plus

Face à une succession de if-elif-elif-elif-… longue comme un générique de film, il peut être tentant de les remplacer par un switch-case comme on peut en voir en C, PHP, Java, etc. C’est simplement impossible en Python car cette instruction n’existe pas.

Plusieurs propositions pour l’inclure dans le langage ont été faites mais elles ont été rejetées car Guido Van Rossum ne les a pas trouvées convaincantes. Son argumentaire détaillé est disponible dans la PEP 3103 publiée en 2006.

L’enchaînement de conditions que l’on souhaite éviter :

if personnage == "Le manchot":
    acteur = "Clint Eastwood"
elif personnage == "Colonel Douglas Mortimer":
    acteur = "Lee Van Cleef"
elif personnage == "El Indio":
    acteur = "Gian Maria Volontè"
#je vous fais grâce de toute la distribution
else:
    acteur = None

La FAQ de la documentation python explique comment se passer du switch (pour python 2, pour python 3). Inutile le lire les deux, c’est la même réponse à chaque fois. Elle recommande l’utilisation d’un dictionnaire. C’est aussi la solution la plus courante que l’on trouve dans du code Python.

L’équivalent du code précédent avec un dictionnaire est :

d = {"Le manchot": "Clint Eastwood", 
     "Colonel Douglas Mortimer": "Lee Van Cleef",
     "El Indio": "Gian Maria Volontè"}

acteur = d.get("El Indio", None)

Comme la structure switch n’est pas disponible dans le langage, plusieurs tentatives ont été faites pour l’ajouter à partir des structures existantes. Comme on peut s’y attendre, le code est plus verbeux et, à mon avis, la solution à base de dictionnaire reste préférable. Parmi les essais disponibles dans pypi.python.org, on peut trouver plusieurs choix d’interface :

Switchcase qui utilise l’instruction break de manière similaire au switch des autres langages : sans l’instruction break, les cas suivants sont automatiquement considérés comme vrai. Fonctionnalité permettant des comportments astucieuses mais qui est aussi un nid à bogues difficile à comprendre. C’est donc probablement une excellente idée de l’avoir ajoutée.

Switch qui a une interface me semblant plus simple à comprendre. La simulation du break est fait par un paramètre facultatif, ce qui s’écarte du switch canonique.

Pyswitch qui utilise les annotations pour définir les différents cas. Je ne suis pas sûr que ce soit le plus lisible mais c’est un choix original.

Une autre solution est de transformer le code en remplaçant les tests par du polymorphisme. Avec l’exemple précédent, on aurait :

class Personnage:
    acteur = None


class LeManchot(Personnage):
    acteur = "Clint Eastwood"


class ColonelDouglasMortimer(Personnage): 
    acteur = "Lee Van Cleef"


class ElIndio(Personnage):
    acteur = "Gian Maria Volontè"

p.acteur # p étant une instance d'une des classes précédente

Il n’est pas nécessaire que p soit une instance héritant de Personnage. Il suffit que l’interface soit identique. Cette transformation n’est pas spécifique à Python, elle est utilisable dans tous les langages objet.

Exemples de structures de données dans les jeux de sociétés

Les mécaniques des jeux de sociétés reposent parfois sur des structures de données classiques en informatique. En voici quelques exemples :

Pile

À l’image d’une pile de livres sur laquelle il est possible d’empiler et de dépiler des éléments, une pile permet d’accéder au dernier élément ajouté. C’est dernier arrivé, premier sorti.
Dans Keltis, un joueur défaussant une carte d’une couleur, la met sur la pile de cette couleur. Les joueurs auront alors, comme action de jeu, la possibilité de la prendre. La carte empilée empêche de prendre celle qu’elle vient de recouvrir.

Pile de cartes à Keltis

Sur la photo précédente, les joueurs ont défaussé successivement plusieurs cartes jaunes, en finissant par le 9, puis le 6 puis le 7.
Lorsqu’un joueur choisit de prendre le 7 jaune, le suivant pourra choisir de prendre le 6. Si ces deux cartes ont été choisies, le 9 devient disponible, etc.

Dans Skull and Roses, un jeu principalement basé sur le bluff et un mécanisme de stop-ou-encore, un joueur va finir par retourner des cartes. Tout d’abord en retournant ses cartes, puis celle des autres joueurs en commençant par les dernières posées. Il peut s’arrêter de dépiler la pile d’un des joueurs pour choisir de dépiler celle d’un autre joueur. L’objectif étant de ne jamais retourner un crâne. Sur la photo suivante, la première carte a été retournée, permettant de voir des roses. Les trois dernières cartes peuvent, si le joueur le décide, être retournées :
Pile de cartes Skull and roses

File

À l’image d’une file d’attente de supermarché où les derniers clients se mettent à la fin de la file, dans une file, c’est le premier arrivé qui est aussi le premier sorti.
Dans Smallworld, les joueurs jouent un peuple puis en choisissent régulièrement un nouveau. Lorsque un joueur choisit un peuple, un nouveau peuple arrive en dernière position. L’intérêt est de permettre aux joueurs d’anticiper les peuples qui vont devenir disponibles. C’est équivalent à une pile, dont la règle du jeu fixe la longueur à 5.

Dans la photo ci-dessous, si un joueur décide de prendre le premier peuple de la file (les zombies des collines), alors le second peuple (les hommes-rats diplomates) deviendront le premier peuple et un nouveau peuple sera ajouté après les mi-portions scouts.
smallword

Contrairement au comportement normal d’une file, il est possible dans Smallworld de choisir un peuple qui n’est pas le premier à condition de payer plus cher. L’intérêt ludique étant d’obliger le joueur à choisir entre économiser ou choisir un peuple qu’il préfère avant qu’un autre joueur le choisisse (mais payer plus).

Arbre

Un arbre est un ensemble de nœuds reliés par des arrêtes dont un seul chemin permet de relier deux nœuds. Bref, c’est un graphe sans cycle.
Dans 7 Wonders, chaque joueur développe une civilisation en posant des cartes représentant des ressources, des bâtiments, etc. Normalement, il faut payer à chaque fois le coût pour poser la carte mais certaines cartes permettent de chaîner les développements de sa civilisation : si une carte permettant un chaînage a déjà été posée par un joueur, celui-ci pourra jouée la carte suivante gratuitement.

Plusieurs arbres de développement distincts sont présents dans le jeu. La photo suivante montre un des arbres possibles du jeu.

Un arbre dans 7 wonders

Tous les arbres disponibles ne sont pas de jolis arbres binaires. Dans l’exemple ci-dessous, trois arbres parmi ceux possibles dans le jeu de base sont présentés (dont celui de la photo) :
Quelques arbres de développement dans 7 wonders
Les couleurs représentent le type de développements (jaune pour le commerce, vert pour la science et rouge pour le militaire). Il existe des cartes avec d’autres couleurs mais ce n’est pas le sujet donc ce n’est pas la peine de s’étendre dessus et cette phrase devrait s’arrêter au plus vite pour qu’on puisse passer à autre chose. Merci.

Hors du jeu de société, les compétitions avec quart de finale, demi et finale sont les arbres binaires dont toutes les branches ont la même hauteur.

Graphe

Un graphe est un ensemble de nœuds reliés par des arêtes.

Les plateaux des Aventuriers du rail sont des graphes : chaque nœud est une ville et les arêtes sont les chemins qui relient les villes entre elles. Le but du jeu est de faire des liaisons entre des villes pour marquer des points. Faire le chemin le plus court entre chaque ville n’a pas d’intérêt. En réutilisant les mêmes routes pour plusieurs objectifs, le joueur optimisera son nombre de coups nécessaires. Chaque joueur aura tendance à faire un arbre couvrant chacune des villes de ses objectifs pour minimiser le coût de pose des chemins.

Détail de la carte États-Unis publiée pour le dixième anniversaire du jeu

Détail de la carte États-Unis publiée pour le dixième anniversaire du jeu

Représentation schématique du détail de la carte États-Unis

Représentation schématique du détail de la carte États-Unis

Il existe probablement plus de variantes de graphes que de cartes des Aventuriers du rails. Pourtant, le nombre de cartes est loin d’être ridicule : États-Unis, Europe, Allemagne, Suisse, Asie etc.

Un graphe est dit pondéré lorsque chaque arête est dotée d’un poids spécifique. La longueur de chaque route et le besoin (ou non) de locomotive peut être apparenté à son poids. Dans une des cartes du jeu représentant la Hollande, les routes ont un autre coût qui est un montant que le premier joueur posant la route doit payer à la banque et que le second joueur la construisant doit payer au premier joueur. D’un point de vue ludique, l’intérêt de donner un avantage au joueur posant rapidement des wagons et d’imposer d’une ressource secondaire à gérer pour les joueurs.

Détails de la carte Pays-Bas dans le jeu Les aventuriers du rail

Pour relier Eindhoven à Maastricht, les joueurs devront payer soit quatre cartes jaunes, soit quatre cartes mauves (ce sont les règles de base) mais aussi payer deux pièces.

Sources et propriétaires des jeux

Les schémas ont été réalisés avec graphviz :

#graphe 7 wonders
#dot -Tpng 7wonders.gv -o 7wonders.png

digraph G {
    "comptoir est" [color=goldenrod1, style=filled];
    "comptoir ouest" [color=goldenrod1, style=filled];
    forum [color=goldenrod1, style=filled];
    port [color=goldenrod1, style=filled];
    officine [color=forestgreen, style=filled];
    écuries [color=red2, style=filled];
    dispensaire [color=forestgreen, style=filled];
    arène [color=goldenrod1, style=filled];
    loge [color=forestgreen, style=filled];
    dispensaire [color=forestgreen, style=filled];
    muraille [color=red2, style=filled];
    fortifications [color=red2, style=filled];

    "comptoir est" -> forum [dir=none];
    "comptoir ouest" -> forum [dir=none];
    forum -> port [dir=none];
    officine -> écuries [dir=none];
    officine -> dispensaire [dir=none];
    dispensaire -> arène [dir=none];
    dispensaire -> loge [dir=none];
    muraille -> fortifications [dir=none];
}
#graphe aventuriers du rail
#dot -Tpng aventuriers_etats-unis.gv -o aventuriers_etats-unis.png

digraph G {
    "sault-ste-marie" -> montreal [dir=none];
    "sault-ste-marie" -> toronto [dir=none];
    montreal -> toronto [dir=none];
    montreal -> "new york" [dir=none];
    montreal -> boston [dir=none];
    boston -> "new york" [dir=none];
    toronto -> pittsburgh [dir=none];
    "new york" -> pittsburgh [dir=none];
}

Merci aux propriétaires ! 🙂

  • l’association Ludoludik pour Keltis et Skull and Roses
  • Nantenai pour Smallworld
  • Stéphanie pour 7 Wonders
  • Pierre pour les Aventuriers du rail

Redimensionner des images avec python-resize-image

python-resize-image est une bibliothèque python disponible sur pypi.python.org. C’est une surcouche à PIL qui simplifie son usage pour des redimensionnements courants d’image. Bien pratique, elle ne remplacera pas PIL pour toutes les autres possibilités comme les empilements d’image par exemple.

La bibliothèque est utilisable avec python2 et python3 mais le fichier wheel n’a été fait que pour la version 2.
Pour l’installer sur python2, pip install python-resize-image suffit.
Pour l’installer sur python3, il faut d’abord cloner le projet, créer l’archive avant de l’installer dans son virtualenv.

Cet article montre un rendu d’exemple pour chaque fonction.

L’image de départ fait 1000 × 413 pixels :

Le fichier d'origine avant modification

Crop : resize_crop()

Dimension d’arrivée : 800 x 700 pixels

L’image d’arrivée possède une zone transparente au-dessus et au-dessous de la photo.

Avec la fonction resize_crop()

Cover : resize_cover()

Dimension d’arrivée : 800 x 700 pixels

Contrairement au crop, l’image d’arrivée est complètement remplie par la photo d’origine

Avec la fonction resize_cover()

Contain : resize_contain()

Dimension d’arrivée : 800 x 700 pixels

Comme le crop, l’image d’arrivée possède une zone transparente au-dessus et au-dessous de la photo.

Avec la fonction resize_contain()

Largeur fixée : resize_width()

Dimension d’arrivée : 400 x 165 pixels

Pas de transparence. L’image d’arrivée est un redimensionnement de l’image de départ, sous la contrainte de la largeur donnée en paramètre de resize_with().

Avec la fonction resize_width()

Hauteur fixée : resize_height()

Dimension d’arrivée : 969 x 400 pixels

Le comportement est équivalent à resize_width(), mais pour la hauteur.

Avec la fonction resize_height()

Vignette : resize_thumbnail()

Dimension d’arrivée : 800 x 330 pixels

La transformation respecte le ratio de départ et redimensionne au mieux pour respecter les dimensions passées en paramètre. Dans l’exemple, les 800 pixels pour la largeur sont bien là mais les 700 pixels demandés pour la hauteur ont été réduits à 330.

Avec la fonction resize_thumbnail()

Sources

J’ai pris la photo au Cap (en Afrique du Sud, pas en France) la semaine précédent la DebConf 16.

L’ensemble des images sont disponibles à http://stephane.yaal.fr/python-resize-image/ au cas où l’envoi sur WordPress ait modifié les données.

Le code source qui a permis de produire les images :

from PIL import Image
from resizeimage import resizeimage

with open('origine_1000.png', 'r') as f:
    TAILLE = [800, 700]
    FIXE = 400
    img = Image.open(f)
    img = resizeimage.resize_crop(img, TAILLE)
    img.save('crop.png', img.format)
    
    img = Image.open(f)
    img = resizeimage.resize_cover(img, TAILLE)
    img.save('cover.png', img.format)
    
    img = Image.open(f)
    img = resizeimage.resize_contain(img, TAILLE)
    img.save('contain.png', img.format)
    
    img = Image.open(f)
    img = resizeimage.resize_width(img, FIXE)
    img.save('width.png', img.format)
    
    img = Image.open(f)
    img = resizeimage.resize_height(img, FIXE)
    img.save('height.png', img.format)
    
    img = Image.open(f)
    img = resizeimage.resize_thumbnail(img, TAILLE)
    img.save('thumbnail.png', img.format)

Des graphiques à la Xkcd

Ou comment faire des graphiques dans le légendaire style de XKCD (une finesse du trait plus tranchante que Michel-Ange, des couleurs plus contrastées que Léonard de Vinci).

graphique à la xkcd

Les développeurs de Matplotlib l’ont fait et intégré à la bibliothèque. Globalement, il suffit d’initialiser le script python avec la fonction xkcd(). Cette fonction initialise des paramètres pour le rendu des graphiques.

with plt.xkcd():
    #le code pour produire le graphique

Installation

Dans Debian, le paquet de base de Matplotlib est python-matplotlib pour python2 et python3-matplotlib pour python3.

Pour que le graphique ait une police similaire à ceux de xkcd, la police Humor Sans doit être installée. Dans Debian, elle se trouve dans le paquet fonts-humor-sans.

Il est possible d’avoir encore une erreur signalant que la police n’est pas trouvée :

/usr/lib/python2.7/dist-packages/matplotlib/font_manager.py:1288: UserWarning: findfont: Font family [u'Humor-Sans'] not found. Falling back to Bitstream Vera Sans

En réalité, elle est bien accessible par matplotlib mais la bibliothèque a construit un cache des polices disponibles lors de la création d’un autre graphique par le passé. Ce cache n’est pas vidé après l’installation de la police. L’erreur survient car matplotlib regarde dans son cache et non les polices actuellement installées sur le système. Il suffit donc de supprimer ce cache (fontList.cache pour python2 ou fontList.py3k.cache pour python3) et d’exécuter à nouveau le script.


stephane@foehn:~$ ls .cache/matplotlib/
fontList.cache fontList.py3k.cache tex.cache
stephane@foehn:~$ rm .cache/matplotlib/fontList.cache #si script lancé avec python2

Et là, ça marche ! 🙂

Évitez tout de même de mettre des accents, la police ne dispose pas de ces caractères et un « ? » est affiché à la place.

Versions des logiciels utilisés, code source

paquet python-matplotlib : 1.5.1-1
paquet fonts-humor-sans : 1.0-1

Le code source qui a permis de produire le magnifique graphique inséré au début de l’article :

from matplotlib import pyplot as plt
import numpy as np

with plt.xkcd():
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    plt.xticks([])
    plt.yticks([])
    ax.set_ylim([-1, 2])

    x = np.linspace(0, 10)
    plt.plot(x, np.sin(x) + 0.3, '--')

    plt.xlabel('abscisses')
    plt.ylabel('ordonnees')
    plt.title("c'est le plus beau graphique du monde !")

    plt.savefig("/tmp/graph_xkcd.png")