Conversion hexadécimale, typage de fonctions en Python

Manipulation de coordonnées hexadécimales en Python, avec découpage en fonctions et typage pour un code clair et fiable.

→ Challenge Correction: Bug-out Shelter #6 – Misleading Transmission

On poursuit les corrigés de l’édition 2025 de la Battle Dev Thales. Dans ce challenge, il était question de cryptage de données de coordonnées. Il n’y avait pas de précision particulière quant au système de cryptage mis en oeuvre. Mais en observant les valeurs, on remarquait la présence, en plus des chiffres, des caractères « a » à « f ». Ce qui pouvait laisser penser qu’on était face à un cryptage hexadecimal. C’est donc sur cette base qu’il fallait décrypter, manipuler puis réencoder les coordonnées des escouades.

La difficulté principale du challenge résidait donc plutôt dans la manipulation de ces coordonnées par rapport aux points clés de la carte. Il fallait bien comprendre la mécanique à mettre en œuvre, quitte à faire différents essais jusqu’à trouver la bonne solution.

Résolution du challenge

On va volontairement résoudre ce challenge en découpant le code en plusieurs fonctions :

  • Décryptage des coordonnées
  • Identification de la zone la plus proche
  • Repousser l’escouade
  • Recrypter les données

Puis le programme principal qui orchestre tout ça et crée la réponse attendue.

On s’attardera également sur le typage en Python, en créant un type sur mesure, et en typant correctement toutes les déclarations des fonctions.

Type sur mesure

Comme on va manipuler souvent un « point » de la carte, on va créer un type dédié, qui correspond à un tableau contenant les coordonnées x et y :

# A mettre au début du code
from typing import List, Tuple

# Type sur mesure
Point = Tuple[int, int]

Conversion héxadécimale en Python

Les coordonnées de chaque escouade sont sous la forme hexadécimale, par exemple « 345_26d ». Chaque partie correspond à un axe (X et Y), séparés par un _. Pour les rendre exploitables, on doit les convertir en décimal :

def decrypt(coord: str) -> Point:
    x_hex, y_hex = coord.split('_')
    return (int(x_hex, 16), int(y_hex, 16))

Un peu d’explications :

  • Cette fonction prend en paramètre une chaîne de caractères et retourne un Point
  • On réalise un parsing sur coord grâce à la fonction split(…)
  • La fonction int(…, 16) permet de transformer une valeur hexadécimale en entier (hexadécimale = base 16).

345 devient alors 837 et 26d devient 621.

Parsing et identification de la zone sensible la plus proche

Chaque zone sensible (z1, z2, z3) est identifiée est donnée sous la forme x,y. On crée une fonction pour extraire proprement les valeurs x et y :

def parse_zone(zone_str: str) -> Point:
    x, y = map(int, zone_str.split(','))
    return (x, y)

Un peu d’explications :

  • Cette fonction prend en paramètre une chaîne de caractères et retourne un Point
  • La fonction map(…) applique la fonction int(…) à chaque élément obtenu après avoir découpé la chaîne sur la virgule grâce à split(…). Ce qui permet de convertir directement les deux valeurs en entiers avant de les affecter aux variables x et y.

Ensuite, il s’agissait de trouver, pour une escouade donnée, la zone sensible la plus proche. Pour cela, il fallait calculer la distance euclidienne (exactement, comme dans le challenge précédent !). Python propose aussi une méthode native pour réaliser cette opération :

# A mettre au début du code
import math

def closest_zone(escouade: Point, zones: List[Point]) -> Point:
    x, y = escouade
    distances = [math.dist((x, y), (zx, zy)) for zx, zy in zones]
    return zones[distances.index(min(distances))]

Un peu d’explications :

  • Cette fonction prend en paramètre le Point d’une escouade, et les zones qui sont une liste de Points
  • On extrait les valeurs x et y de l’escouade
  • On calcule la distance par rapport à chaque zone grâce à la fonction dist de la libraire math. distances pourrait donc contenir : [123.5, 442.1, 276.3]
  • On cherche la plus petite distance grâce à la fonction min(…) puis on récupère l’index correspondant avec la fonction index(…) et on retourne la zone sensible située à cette index dans la liste de départ zones.

Il reste ensuite à créer la fonction qui permet de repousser l’escouade :

def repel(escouade: Point, zone: Point) -> Point:
    x, y = escouade
    zx, zy = zone
    dx = 100 if x >= zx else -100
    dy = 100 if y >= zy else -100
    return (x + dx, y + dy)

Un peu d’explications :

  • Cette fonction prend en paramètre une escouade et une zone, qui sont tous deux des Point.
  • Pour ces 2 Point, on extrait x et y
  • On détermine s’il décaler x et y de plus ou moins 100 en fonction de la position de x par rapport à zx et de y par rapport à zy.
  • On retourne les coordonnées x et y altérées de dx et dy

Conversion héxadécimale en Python #2

def encrypt(point: Point) -> str:
    return f"{point[0]:x}_{point[1]:x}"

Un peu d’explications :

  • Cette fonction prend en paramètre un Point et retourne une chaîne de caractères
  • f »{point[0]:x} » est une f-string (chaîne formatée) :
    • :x indique qu’on veut afficher le nombre en base 16 (hexadécimal),
    • sans le préfixe 0x (contrairement à la fonction hex() qui en ajoute un).

Programme principal

Et voici le programme principal qui orchestre l’utilisation de toutes ces fonctions créées :

# On parse les zones sensibles
zones = [parse_zone(z) for z in [z1, z2, z3]]

# Traitement des données et utilisation des fonctions
results = []
for coord in coordinates:
    decrypted = decrypt(coord)
    nearest_zone = closest_zone(decrypted, zones)
    repelled = repel(decrypted, nearest_zone)
    encrypted = encrypt(repelled)
    results.append(encrypted)

# Affichage final
print('-'.join(results))

Pour chaque coordonnées :

  1. On décrypte
  2. On cherche la zone la plus proche
  3. On détermine les coordonnées de « repousse »
  4. On crypte
  5. On stocke dans results pour l’affichage final

Conclusion

Ce challenge nous a permis d’explorer plusieurs facettes essentielles du développement Python.

D’abord, la manipulation du système hexadécimal, à travers la conversion des coordonnées avec int(…, 16) et le formatage inverse via les f-strings (:x). Une belle occasion de comprendre comment Python gère naturellement les bases numériques.

Ensuite, on a structuré la logique en plusieurs fonctions indépendantes, chacune ayant un rôle clair. Cette décomposition rend le code plus lisible, testable et réutilisable : une bonne pratique qu’on retrouve dans tout projet bien conçu.

Enfin, on a introduit le typage des fonctions avec typing, qui documente le code tout en facilitant la détection d’erreurs et la complétion dans les IDE. Un excellent rappel que la clarté du code passe autant par la logique que par la précision de ses signatures !


Qui a codé ce superbe contenu ?

Keep learning

Other content to discover