clavier ouvert

Blog d'enseignement d'Adrien Foucart.
Toutes les opinions présentées ici n'engagent que moi. Blog garanti sans pub, sans traqueurs, et 100% rédigé par un humain.

2026.05.22
Spirales et chevaliers | 5. finitions et visualisations

Nous sommes arrivés, fonctionnellement, au bout de l’exercice en produisant cette figure-ci lors de l’épisode précédent:

500 premières couches, quatre joueurs, cavalier pour tout le monde

Restent quelques finitions, à commencer par le fait de pouvoir utiliser un fichier JSON pour décrire la figure attendue. Ensuite, on pourra essayer de générer des images sympa!

Configuration

On cherche à décrire la configuration dans un fichier avec cette structure:

{
    "n_layers": 20,
    "players": [
        {"color": "r", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "k", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]}
    ]
}

Créons une dataclass pour la configuration:

# spiral.py
@dataclass
class Config:
    n_layers: int
    players: list[Player]

    @classmethod
    def from_json(cls, path: pathlib.Path):
        with path.open("r", encoding="utf-8") as fp:
            data = json.load(fp)
        if not ("n_layers" in data and "players" in data):
            raise ValueError("Config: missing n_layers or players")        
        players = [Player.from_dict(p) for p in data['players']]
        return cls(int(data['n_layers']), players)

En modifiant Player pour pouvoir aussi être créé à partir d’un dictionnaire:

# player.py

@dataclass
class Player:
    # ...
    @classmethod
    def from_dict(cls, data: dict):
        if not ("color" in data and "offsets" in data):
            raise ValueError("Player: missing color or offsets")
        
        return cls(str(data["color"]), [(int(offset[0]), int(offset[1])) for offset in data['offsets']])

Le main, enfin, va lire le fichier JSON passé en paramètre de l’appel au programme, ou fourni par l’utilisateur via un input:

# spiral.py
if __name__ == "__main__":
    if len(sys.argv) >= 2:
        path = pathlib.Path(sys.argv[1])
    else:
        path = pathlib.Path(input("Path to config file: "))

    config = Config.from_json(path)
    
    board = Board(config.n_layers, config.players)
    show_board(board, save_as=pathlib.Path(str(path).replace('.json', '.png')))

Si on lance python spiral.py two_knights.json (avec bien sûr la configuration ci-dessus dans two_knights.json!), on obtient bien notre figure:

20 couches, deux cavaliers

Parfait. Voyons ce qu’on peut faire de plus.

Visualisations

Prenons notre configuration à quatre cavaliers, et remplaçons deux des cavaliers par des “super-cavaliers”, capable d’aller deux fois plus loin. Donc, au lieu de (1, 2), ils ont un offset de (2, 4), etc:

500 couches, deux cavaliers, deux super-cavaliers

Un cavalier, un super-cavalier, et un super-super-cavalier, avec un offset (3, 6):

500 couches, un cavalier, un super-cavalier, un super-super-cavalier

Un cavalier, un super-cavalier, et un “vizir” (offsets en “croix” (1, 0), (0, 1), (-1, 0), (0, -1)):

500 couches, un cavalier, un super-cavalier, un vizir

Jusqu’à présent, nous n’avons fait que des pièces “symétriques”, mais rien ne nous empêche de définir des pattern plus arbitraires pour les déplacements des pièces. Prenons la configuration suivante:

{
    "n_layers": 500,
    "players": [
        {"color": "r", "offsets": [[-1, -1], [-1, 1], [1, 2], [2, 1], [1, -2], [2, -1]]},
        {"color": "k", "offsets": [[1, -1], [1, 1], [-1, 2], [-2, 1], [-1, -2], [-2, -1]]},
        {"color": "y", "offsets": [[1, 0], [-1, 0], [0, 2], [0, -3]]}
    ]
}

On a une pièce qui va plus loin à droite que à gauche, une autre qui va plus loin à gauche que à droite, et une dernière qui avance plus loin en vertical qu’en horizontal. Le résultat:

500 couches, trois pièces bizarres

Je pense qu’on peut dire que l’expérience est réussie !

Aller, un dernier pour la route, faisons se batailler les sept cavaliers de l’arc-en-ciel sur une spirale à 1000 couches:

{
    "n_layers": 1000,
    "players": [
        {"color": "red", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "orange", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "yellow", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "green", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "cyan", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "blue", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]},
        {"color": "violet", "offsets": [[1, 2], [-1, 2], [2, 1], [-2, 1], [1, -2], [-1, -2], [2, -1], [-2, -1]]}
    ]
}
1000 couches, sept cavaliers

Bon, ça met quelques minutes à tourner et je ne pense pas qu’on me demandera de designer le drapeau de la prochaine Pride… mais ça marche.

Commentaires, remarques, erreurs qu'il faut absolument me faire remarquer? Contactez-moi sur Mastodon ou par mail (adrien@adfoucart.be)