# AP1 — Défis ★

## Avant de commencer

Ce notebook est facultatif. Commencez par le tronc commun du cycle AP1–AP3,
puis choisissez un défi si vous souhaitez approfondir. Une recherche expliquée
et testée peut être jointe au rendu du 6 décembre 2026 pour un bonus ; elle
n'est pas nécessaire pour valider le minimum demandé.

Ces défis sont volontairement durs : plus secs que les exercices de rendu,
avec peu d’indices. C'est fait exprès. Rejouer une recette
vue en cours ne vous apprendrait rien de neuf ; ici, on vous demande de
chercher. Prenez-les dans l'ordre que vous voulez, sautez celui qui vous
ennuie, revenez-y plus tard. Certains résistent, et le dernier est censé
occuper un très bon élève une bonne heure, au besoin chez lui.

Si vous séchez une heure sur un défi, ne concluez pas que vous êtes lent :
c'est plutôt le signe que ça travaille. Les
cellules de vérification contrôlent vos résultats sur les entrées données.
Certaines boucles illustrent aussi une formule de référence sur plusieurs
cas ; elles ne réexécutent pas votre propre formule. Pour vérifier que votre
raisonnement se généralise, choisissez une autre entrée, calculez le résultat
attendu à la main, puis réévaluez votre solution dans une cellule séparée.

Contrainte volontaire pour ces défis : utilisez les types, les atomes, les
opérateurs, `div`/`rem`, les tuples, l'immutabilité et les motifs avec `=`.
Même si nous avons déjà rencontré la récursion, ne l'utilisez pas ici ;
laissez également `for` et `Enum` aux cellules de vérification fournies.
Cette contrainte invite à chercher une formule. Les défis restent une réserve,
après les cinq carnets du parcours, jusqu'à `ap1-05-pile-calculatrice.livemd`.

## Défi 1 — Le jour de la semaine, n jours plus tard

Les jours de la semaine se numérotent `0 = lundi`, `1 = mardi`, …, `6 =
dimanche`. On vous donne un jour de départ `depart` (un entier de `0` à `6`)
et un décalage `n` (un entier `≥ 0`, possiblement énorme). Calculez le
numéro du jour obtenu `n` jours plus tard.

Une seule ligne doit suffire. Pas de `if`, pas de liste des sept jours à
dérouler à la main.

```elixir
depart = 3
n = 10

# À COMPLÉTER : lie `jour` au numéro (0..6) du jour situé n jours après depart.
jour = nil

jour
```

Vérification :

```elixir
cas = [{0, 0, 0}, {3, 10, 6}, {6, 1, 0}, {0, 7, 0}, {5, 0, 5}, {2, 700, 2}, {4, 1_000_000, 5}]

for {d, k, attendu} <- cas do
  # on recalcule `jour` avec les mêmes règles que la cellule
  depart = d
  n = k
  jour = rem(depart + n, 7)

  if jour != attendu do
    raise "depart=#{d}, n=#{k} : attendu #{attendu}, la formule donnerait #{jour}"
  end
end

# Vérifiez ensuite la valeur produite par votre cellule pour depart=3 et n=10.
if jour < 0 or jour > 6 do
  raise "ton `jour` (#{inspect(jour)}) sort de l'intervalle 0..6 — un numéro de jour vit dans 0..6"
end

if jour != rem(3 + 10, 7) do
  raise "avec depart=3 et n=10, `jour` devrait valoir #{rem(3 + 10, 7)}, obtenu #{inspect(jour)}"
end

IO.puts("✅ jour = #{jour} — la roue des sept jours tourne juste")
```

> En une phrase (double-cliquez pour éditer) : pourquoi `rem(_, 7)` suffit-il
> à faire « tourner » la semaine sans jamais tester la fin de la liste des
> jours ?
>
> → …

## Défi 2 — Devinez l'ordre du monde

Elixir sait comparer avec `<` **deux valeurs de types différents**, selon un
ordre total défini par le langage. Nous n'en avons pas besoin dans le
parcours principal : ce défi propose de le découvrir. Faites une prédiction,
puis laissez la machine trancher et cherchez une règle qui explique les
résultats ; il ne s'agit pas de deviner une convention connue à l'avance.

Remplacez chaque `nil` par le booléen (`true` ou `false`) que **vous pariez**.
Ne lancez pas d'abord un essai dans un coin : le jeu, c'est de deviner, puis
de confronter.

```elixir
# À COMPLÉTER : mets true ou false selon ce que TU crois, sans tricher.
a = nil    # (1)  1 < :atome
b = nil    # (2)  :atome < "chaine"
c = nil    # (3)  3.0 < 3          (attention : flottant vs entier)
d = nil    # (4)  {1, 2} < [1, 2]  (un tuple, une liste)
e = nil    # (5)  nil < false      (deux atomes déguisés)
f = nil    # (6)  :ok == "ok"

{a, b, c, d, e, f}
```

Vérification :

```elixir
verite = {
  1 < :atome,
  :atome < "chaine",
  3.0 < 3,
  {1, 2} < [1, 2],
  nil < false,
  :ok == "ok"
}

tes_reponses = {a, b, c, d, e, f}

if tes_reponses != verite do
  # on ne dit pas laquelle : à toi de rejouer chaque comparaison à la main
  raise """
  Au moins une prédiction est fausse.
  Tu as parié #{inspect(tes_reponses)}.
  Rejoue chaque comparaison une par une dans une cellule à toi (a =, b =, …)
  et compare à ton pari : où t'es-tu trompé, et pourquoi cet ordre-là ?
  """
end

IO.puts("✅ Six sur six — tu as l'ordre total d'Elixir dans les doigts")
```

> En une phrase : l'ordre total d'Elixir range `nombre < atome < ... < tuple
> < ... < liste < chaîne`. En quoi le fait que **comparer ne plante jamais**
> (même entre types) change-t-il ce qu'on peut faire d'une collection
> hétérogène ?
>
> → …

## Défi 3 — Un état de jeu, en atomes et tuples seulement

On veut représenter l'état complet d'une partie de **morpion** (tic-tac-toe)
en une seule valeur, **sans map, sans chaîne** : uniquement des **atomes** et
des **tuples imbriqués**. Une case vaut `:vide`, `:x` ou `:o`. Le plateau est
3×3.

Construisez `partie`, un tuple à deux éléments : d'abord le **plateau**
(un tuple de 3 lignes, chaque ligne étant un tuple de 3 cases), puis le
**joueur dont c'est le tour** (`:x` ou `:o`). Représentez cette position
précise :

```
 x | o | .
-----------
 . | x | .
-----------
 o | . | .        à :o de jouer
```

```elixir
# À COMPLÉTER : `partie` = {plateau, tour}, plateau = {ligne0, ligne1, ligne2},
# chaque ligne = {case_gauche, case_milieu, case_droite}. Cases: :vide, :x, :o.
partie = nil

partie
```

Vérification :

```elixir
{plateau, tour} = partie

# propriété 1 : structure exacte 3x3 en tuples
if tuple_size(plateau) != 3 do
  raise "le plateau doit avoir 3 lignes, il en a #{tuple_size(plateau)}"
end

l0 = elem(plateau, 0)
l1 = elem(plateau, 1)
l2 = elem(plateau, 2)

for {ligne, i} <- [{l0, 0}, {l1, 1}, {l2, 2}] do
  if not is_tuple(ligne) or tuple_size(ligne) != 3 do
    raise "la ligne #{i} doit être un tuple de 3 cases, obtenu #{inspect(ligne)}"
  end
end

# propriété 2 : chaque case est un atome autorisé (pas de chaîne, pas de nombre)
toutes = [
  elem(l0, 0), elem(l0, 1), elem(l0, 2),
  elem(l1, 0), elem(l1, 1), elem(l1, 2),
  elem(l2, 0), elem(l2, 1), elem(l2, 2)
]

for c <- toutes do
  if c not in [:vide, :x, :o] do
    raise "case interdite : #{inspect(c)} — seuls :vide, :x, :o sont permis (des atomes, pas des chaînes)"
  end
end

# propriété 3 : la position dessinée
attendu = {
  {:x, :o, :vide},
  {:vide, :x, :vide},
  {:o, :vide, :vide}
}

if plateau != attendu do
  raise "le plateau ne correspond pas au dessin.\nAttendu #{inspect(attendu)}\nObtenu  #{inspect(plateau)}"
end

if tour != :o do
  raise "c'est à :o de jouer, ton `tour` vaut #{inspect(tour)}"
end

IO.puts("✅ Position encodée en pur atomes+tuples, et c'est bien à :o de jouer")
```

> En une phrase : pourquoi encoder une case par l'atome `:vide` plutôt que
> par la chaîne `"vide"` ou l'entier `0` — qu'est-ce qu'on y gagne quand on
> comparera deux états de jeu ?
>
> → …

## Défi 4 — Le petit chiffre de César

On chiffre une lettre représentée par un entier `0..25` (`a=0`, `b=1`, …,
`z=25`) en la décalant de `k` positions dans l'alphabet, **en boucle** :
après `z`, on repart à `a`. Le décalage `k` peut être **négatif** (déchiffrer)
ou **plus grand que 26**.

Écrivez la formule qui donne la lettre chiffrée `c` (toujours dans `0..25`),
pour n'importe quel entier `k` — positif, négatif, énorme. C'est le cœur du
défi : un `rem` naïf ne suffit **pas**.

```elixir
lettre = 0     # 'a'
k = -1         # décale d'un cran vers l'arrière : on doit retomber sur 'z' (25)

# À COMPLÉTER : lie `c` à la lettre chiffrée, dans 0..25, pour TOUT k entier.
c = nil

c
```

Vérification :

```elixir
# une fonction de référence qui reproduit exactement la règle attendue
chiffre = fn lettre, k -> rem(rem(lettre + k, 26) + 26, 26) end

cas = [
  {0, 1, 1},        # a + 1 = b
  {0, -1, 25},      # a - 1 = z  (le cas qui casse un rem naïf)
  {25, 1, 0},       # z + 1 = a
  {0, 26, 0},       # tour complet
  {10, -100, 14},   # gros décalage négatif
  {13, 13, 0},      # n + 13 = a
  {5, 0, 5}         # k = 0 : rien ne bouge
]

for {l, kk, attendu} <- cas do
  if chiffre.(l, kk) != attendu do
    raise "règle attendue cassée sur (lettre=#{l}, k=#{kk}) : #{chiffre.(l, kk)} au lieu de #{attendu}"
  end
end

# ta valeur, sur le cas de la cellule (lettre=0, k=-1) : doit valoir 25
if c != 25 do
  raise """
  Avec lettre=0 (a) et k=-1, on doit retomber sur 25 (z), tu as #{inspect(c)}.
  Piège : rem(0 + (-1), 26) vaut #{rem(0 + (-1), 26)} en Elixir, pas 25.
  Comment forcer un reste toujours positif ?
  """
end

if c < 0 or c > 25 do
  raise "une lettre chiffrée vit dans 0..25, ton `c` vaut #{inspect(c)}"
end

IO.puts("✅ César en boucle : a-1 retombe bien sur z, même avec un k négatif")
```

> En une phrase : `rem(-1, 26)` vaut `-1` en Elixir (le reste garde le signe
> du dividende). Comment votre formule transforme-t-elle ce `-1` en `25`, et
> pourquoi le `+ 26` puis un second `rem` suffisent-ils pour **tout** `k` ?
>
> → …

## Défi 5 — L'échiquier torique (le gros morceau)

Un pion vit sur un plateau **8×8** dont les bords se **recollent** : sortir
par la droite fait réapparaître à gauche, sortir par le haut fait réapparaître
en bas. C'est un **tore**. Une position est un tuple `{x, y}` avec `x` et `y`
dans `0..7`. Un déplacement est un tuple `{dx, dy}`, avec `dx`, `dy` des
entiers **quelconques** (négatifs, > 8, peu importe).

**Sans définir de fonction**, par contrainte volontaire de ce défi, et en
n'utilisant que `=`, `div`/`rem`, l'arithmétique et l'extraction de tuples,
appliquez **dans l'ordre** les trois déplacements ci-dessous à la position de
départ, en rebindant `pos` à chaque étape. La position finale doit être un
tuple `{x, y}` avec `x, y` dans `0..7`.

Le piège est le même qu'au défi 4, mais en deux dimensions et enchaîné : un
`rem` naïf vous donnera des coordonnées négatives, et comme vous **rebindez**
`pos`, une seule étape fausse contamine tout ce qui suit.

```elixir
pos = {0, 0}

deplacements = [{1, 2}, {-3, -5}, {100, -1}]
# appliquer {1,2} puis {-3,-5} puis {100,-1}, dans cet ordre.

# À COMPLÉTER (1) : extrais x, y de pos et dx, dy du 1er déplacement {1, 2},
#   puis rebinde pos à la nouvelle position torique.
{x, y} = pos
# ...

# À COMPLÉTER (2) : même chose avec {-3, -5}.

# À COMPLÉTER (3) : même chose avec {100, -1}.

pos
```

Vérification :

```elixir
# référence : un pas torique sur 8x8
pas = fn {x, y}, {dx, dy} ->
  {rem(rem(x + dx, 8) + 8, 8), rem(rem(y + dy, 8) + 8, 8)}
end

# on déroule la séquence attendue
p0 = {0, 0}
p1 = pas.(p0, {1, 2})     # {1, 2}
p2 = pas.(p1, {-3, -5})   # {rem(-2..),..} -> {6, 5}
p3 = pas.(p2, {100, -1})  # {rem(106,8)->2 ... , 4}
attendu = p3

if pos != attendu do
  raise """
  Position finale attendue #{inspect(attendu)}, obtenu #{inspect(pos)}.
  Déroule à la main, déplacement par déplacement, en notant {x, y} à chaque étape :
    {0,0} --{1,2}--> ?  --{-3,-5}--> ?  --{100,-1}--> ?
  Un des restes est-il devenu négatif ? (rappelle-toi rem(-2, 8) = -2, pas 6.)
  """
end

# Contrôle des bornes sur la position finale de la séquence demandée.
{fx, fy} = pos
if fx < 0 or fx > 7 or fy < 0 or fy > 7 do
  raise "la position finale #{inspect(pos)} sort du plateau 0..7 — le recollement des bords a échoué"
end

# Cas supplémentaires de la formule de référence ; votre code n'est pas rejoué ici.
controles = [
  {{0, 0}, {-1, -1}, {7, 7}},
  {{7, 7}, {1, 1}, {0, 0}},
  {{3, 4}, {16, -8}, {3, 4}},
  {{0, 0}, {-9, 9}, {7, 1}}
]

for {p, d, att} <- controles do
  if pas.(p, d) != att do
    raise "la règle torique de référence est incohérente sur #{inspect({p, d})} — signale-le à l'enseignant"
  end
end

IO.puts("✅ Trois pas sur le tore, bords recollés, position finale #{inspect(pos)} bien dans 0..7")
```

> En une phrase : l'immutabilité dit qu'une valeur ne change jamais. Pourtant
> vous avez « déplacé » le pion. Qu'avez-vous fait *à la place* de modifier
> `pos`, et pourquoi l'ordre des trois rebindings est-il ce qui donne le
> résultat ?
>
> → …
