Georevision / app.py
Enoder's picture
Update app.py
8ba0889 verified
Raw
History Blame
53.9 kB
#!/usr/bin/env python3
"""
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ Gร‰O-Rร‰VISION โ€” Tectonique des Plaques โ•‘
โ•‘ 76 notions ยท Chapitres 3, 4, 5 & Synthรจse โ•‘
โ•‘ Mรฉthode : Ebbinghaus + Testing Effect + Interleaving โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
"""
import curses
import random
import json
import os
import re
import textwrap
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# DONNร‰ES โ€” 76 cartes
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
CARDS = [
# CH.3 โ€” Limites de plaques
{"id": 1, "ch": "3", "section": "CH.3 โ€” Limites de plaques",
"q": "Quels sont les 3 grands types de limites de plaques ?",
"hint": "Pense : naissance / destruction / collision",
"a": "1. DORSALES (divergence/accrรฉtion oceanique)\n2. FOSSES OCEANIQUES (convergence/subduction)\n3. CHAINES DE MONTAGNES (convergence/collision)"},
{"id": 2, "ch": "3", "section": "CH.3 โ€” Dorsales",
"q": "Quelles roches sont produites ร  la dorsale ? Quelle est leur texture ?",
"hint": "Deux roches, deux textures",
"a": "BASALTE : texture microlithique (refroidissement rapide en surface)\nGABBRO : texture grenue (refroidissement lent en profondeur)"},
{"id": 3, "ch": "3", "section": "CH.3 โ€” Dorsales",
"q": "Quel type de failles caractรฉrise les dorsales ? Pourquoi ?",
"hint": "Extension ou compression ?",
"a": "FAILLES NORMALES car la divergence crรฉe une EXTENSION (รฉtirement) de la croรปte."},
{"id": 4, "ch": "3", "section": "CH.3 โ€” Dorsales",
"q": "Caractรฉristiques sismiques et thermiques d'une dorsale ?",
"hint": "Profondeur + intensitรฉ + flux",
"a": "Sismicitรฉ SUPERFICIELLE et FAIBLE MAGNITUDE.\nFlux gรฉothermique ร‰LEVร‰ (magmatisme ascendant)."},
{"id": 5, "ch": "3", "section": "CH.3 โ€” Fosses",
"q": "Quelles roches plutoniques et volcaniques sont produites en subduction ?",
"hint": "4 roches, 2 textures diffรฉrentes",
"a": "Plutoniques (grenues) : DIORITE, GRANITE\nVolcaniques (microlithiques) : ANDร‰SITE, RHYOLITE"},
{"id": 6, "ch": "3", "section": "CH.3 โ€” Fosses",
"q": "Comment se rรฉpartissent les foyers sismiques en zone de subduction ? Jusqu'oรน ?",
"hint": "Un plan cรฉlรจbre...",
"a": "Plan de WADATI-BENIOFF : plan inclinรฉ suivant la plaque plongeante.\nAtteint 700 KM de profondeur."},
{"id": 7, "ch": "3", "section": "CH.3 โ€” Fosses",
"q": "Dรฉcris le flux gรฉothermique en zone de subduction (deux zones).",
"hint": "Froid ici, chaud lร -bas",
"a": "BAS au niveau de la plaque froide qui plonge.\nFORT au niveau de l'arc volcanique sus-jacent."},
{"id": 8, "ch": "3", "section": "CH.3 โ€” Collision",
"q": "Caractรฉristiques gรฉologiques et sismiques d'une zone de collision ?",
"hint": "Type de failles + profondeur sรฉismes",
"a": "FAILLES INVERSES, flux gรฉothermique FAIBLE,\nsismicitรฉ principalement SUPERFICIELLE."},
{"id": 9, "ch": "3", "section": "CH.3 โ€” Points chauds",
"q": "Comment un alignement de volcans inactifs prouve-t-il le mouvement des plaques ?",
"hint": "ร‚ge + distance",
"a": "Le point chaud est FIXE dans le manteau profond.\nLa plaque se dรฉplace : l'รขge des volcans AUGMENTE avec la distance au volcan actif."},
{"id": 10, "ch": "3", "section": "CH.3 โ€” Anomalies magnรฉtiques",
"q": "Qu'est-ce qu'une anomalie magnรฉtique positive vs nรฉgative ?",
"hint": "Intensitรฉ mesurรฉe vs moyenne",
"a": "POSITIVE : intensitรฉ > moyenne โ†’ polaritรฉ NORMALE\nNร‰GATIVE : intensitรฉ < moyenne โ†’ polaritรฉ INVERSE (pรดles inversรฉs)"},
{"id": 11, "ch": "3", "section": "CH.3 โ€” Anomalies magnรฉtiques",
"q": "Dรฉcris le patron des anomalies magnรฉtiques autour d'une dorsale.",
"hint": "Symรฉtrie + image",
"a": "Bandes PARALLรˆLES ET SYMร‰TRIQUES par rapport ร  l'axe de la dorsale.\nComparables ร  un DOUBLE TAPIS ROULANT.\nBasaltes d'autant plus vieux qu'ils sont รฉloignรฉs."},
{"id": 12, "ch": "3", "section": "CH.3 โ€” Anomalies magnรฉtiques",
"q": "Que rรฉvรจle l'รขge et l'รฉpaisseur des sรฉdiments sur le plancher ocรฉanique ?",
"hint": "Loi simple distance/รขge",
"a": "ร‰paisseur et รขge des sรฉdiments AUGMENTENT avec la distance ร  la dorsale.\nPreuve de l'expansion ocรฉanique."},
{"id": 13, "ch": "3", "section": "CH.3 โ€” Gรฉodรฉsie spatiale",
"q": "Que permet la gรฉodรฉsie spatiale (GPS) en tectonique ?",
"hint": "Prรฉcision + deux types de vitesses",
"a": "Localisation au MILLIMรˆTRE PRรˆS.\nCalcul des vitesses ABSOLUES (repรจres fixes) et RELATIVES (entre deux plaques)."},
{"id": 14, "ch": "3", "section": "CH.3 โ€” Rรฉsumรฉ frontiรจres",
"q": "Quelle frontiรจre prรฉsente une vallรฉe axiale (rift) ? Quel mouvement ?",
"hint": "Rift = fossรฉ d'effondrement",
"a": "La DORSALE : mouvement de DIVERGENCE.\nLa vallรฉe axiale (rift) tรฉmoigne de l'extension de la croรปte."},
{"id": 15, "ch": "3", "section": "CH.3 โ€” Structure gรฉnรฉrale",
"q": "Lithosphรจre terrestre : structure de base et activitรฉ aux frontiรจres.",
"hint": "Dรฉcoupe + concentration",
"a": "Dรฉcoupรฉe en PLAQUES LITHOSPHร‰RIQUES RIGIDES.\nFrontiรจres concentrent l'essentiel de l'ACTIVITร‰ SISMIQUE ET VOLCANIQUE."},
{"id": 16, "ch": "3", "section": "CH.3 โ€” Anomalies magnรฉtiques",
"q": "Comment les basaltes enregistrent-ils le champ magnรฉtique ?",
"hint": "Moment clรฉ = refroidissement",
"a": "En se REFROIDISSANT, les minรฉraux magnรฉtiques (magnรฉtite) se figent dans la direction du champ magnรฉtique terrestre du moment."},
{"id": 17, "ch": "3", "section": "CH.3 โ€” Dorsales",
"q": "Que produit l'accrรฉtion ocรฉanique ร  la dorsale ?",
"hint": "Naรฎt quoi ?",
"a": "La CROร›TE OCร‰ANIQUE :\n- Basalte en surface\n- Gabbro en profondeur\n- Pรฉridotite mantellique sous-jacente"},
{"id": 18, "ch": "3", "section": "CH.3 โ€” Fosses",
"q": "Qu'est-ce qu'une fosse ocรฉanique ? Quel processus y est associรฉ ?",
"hint": "Plongement...",
"a": "Dรฉpression profonde marquant une ZONE DE SUBDUCTION :\nplongement d'une plaque ocรฉanique dense dans le manteau."},
{"id": 19, "ch": "3", "section": "CH.3 โ€” Collision",
"q": "Qu'est-ce qu'une chaรฎne de montagnes en termes tectoniques ?",
"hint": "Fermeture d'un ocรฉan",
"a": "Zone de COLLISION CONTINENTALE : rรฉsultat de la convergence de deux plaques continentales aprรจs fermeture d'un ocรฉan."},
# CH.4 โ€” Dorsales rapides
{"id": 20, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Quelle est la vitesse d'รฉcartement d'une dorsale rapide ?",
"hint": "10โ€“16 cm/an โ€” Quel ocรฉan ?",
"a": "10 ร  16 CM/AN.\nExemple : dorsale du Pacifique Est (Pacifique)."},
{"id": 21, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Morphologie d'une dorsale rapide : rift, bombement ?",
"hint": "Opposรฉ de la dorsale lente",
"a": "BOMBEMENT LARGE mais VALLร‰E AXIALE QUASI ABSENTE (magmatisme abondant comble le rift)."},
{"id": 22, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Dรฉcris le processus de fusion partielle sous une dorsale (รฉtapes).",
"hint": "Convection โ†’ dรฉcompression โ†’ solidus",
"a": "1. Remontรฉe des pรฉridotites par CONVECTION\n2. Dร‰COMPRESSION ADIABATIQUE : le gรฉotherme recoupe le solidus\n3. FUSION PARTIELLE (~15%) โ†’ gouttelettes de magma"},
{"id": 23, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Oรน se loge la chambre magmatique ? Quelles roches en sortent ?",
"hint": "Profondeur + 2 roches",
"a": "Entre 2 et 7 KM de profondeur.\nRefroidissement lent โ†’ GABBROS (grenu)\nRemontรฉe + refroidissement rapide โ†’ BASALTES (microlithique)"},
{"id": 24, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Quelle รฉpaisseur de croรปte produit une dorsale rapide ?",
"hint": "Chiffre prรฉcis en km",
"a": "Croรปte ocรฉanique rรฉguliรจre de 5 ร€ 7 KM d'รฉpaisseur."},
{"id": 25, "ch": "4", "section": "CH.4 โ€” Dorsales lentes",
"q": "Quelle est la vitesse d'รฉcartement d'une dorsale lente ?",
"hint": "1โ€“5 cm/an โ€” Quel ocรฉan ?",
"a": "1 ร  5 CM/AN.\nExemple : dorsale mรฉdio-atlantique (Atlantique)."},
{"id": 26, "ch": "4", "section": "CH.4 โ€” Dorsales lentes",
"q": "Morphologie d'une dorsale lente : rift, magmatisme, expansion.",
"hint": "Opposรฉ de rapide",
"a": "RIFT TRรˆS MARQUร‰ + grandes failles de dรฉtachement.\nMagmatisme Rร‰DUIT ET DISCONTINU.\nExpansion parfois assurรฉe par ร‰TIREMENT TECTONIQUE."},
{"id": 27, "ch": "4", "section": "CH.4 โ€” Dorsales lentes",
"q": "Qu'est-ce que la serpentinisation et quand se produit-elle ?",
"hint": "Roche + eau + dorsale lente",
"a": "Hydratation des Pร‰RIDOTITES (manteau) remontรฉes en surface (manque de magmatisme).\nL'eau transforme l'olivine en SERPENTINE."},
{"id": 28, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Qu'est-ce que le mรฉtamorphisme hydrothermal ?",
"hint": "Eau + chaleur + minรฉraux",
"a": "Transformation ร€ L'ร‰TAT SOLIDE des minรฉraux de la croรปte ocรฉanique par infiltration d'eau de mer chauffรฉe. Produit des minรฉraux HYDRATร‰S."},
{"id": 29, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Sรฉquence de faciรจs mรฉtamorphiques lors du vieillissement ocรฉanique ?",
"hint": "Deux tempรฉratures, deux faciรจs",
"a": "> 700ยฐC : faciรจs AMPHIBOLITE (hornblende)\n500-700ยฐC : faciรจs SCHISTE VERT (chlorite + actinote)"},
{"id": 30, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Comment รฉvolue la densitรฉ de la lithosphรจre ocรฉanique avec le temps ?",
"hint": "Refroidissement โ†’ รฉpaississement โ†’ densitรฉ",
"a": "La lithosphรจre SE REFROIDIT, S'ร‰PAISSIT (isotherme 1300ยฐC s'approfondit),\nsa DENSITร‰ AUGMENTE jusqu'ร  dรฉpasser celle de l'asthรฉnosphรจre."},
{"id": 31, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Oรน et comment naissent les fumeurs noirs ?",
"hint": "Cycle eau de mer",
"a": "Eau de mer s'infiltre, se Rร‰CHAUFFE au contact du magma,\nse charge en minรฉraux et ressort : SOURCES HYDROTHERMALES CHAUDES."},
{"id": 32, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Qu'est-ce que la dรฉcompression adiabatique ? Pourquoi dรฉclenche-t-elle la fusion ?",
"hint": "Pas de perte de chaleur",
"a": "Remontรฉe de matรฉriel SANS ร‰CHANGE DE CHALEUR.\nPression baisse โ†’ point de fusion (solidus) baisse aussi.\nLe gรฉotherme RECOUPE LE SOLIDUS โ†’ fusion partielle."},
{"id": 33, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Quelle est la signification de l'isotherme 1300ยฐC pour la lithosphรจre ?",
"hint": "Limite LAB",
"a": "LIMITE LITHOSPHรˆRE-ASTHร‰NOSPHรˆRE (LAB).\nElle S'APPROFONDIT avec le refroidissement de la plaque รฉloignรฉe de la dorsale."},
{"id": 34, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Basalte en coussin vs basalte en filon : quelle diffรฉrence ?",
"hint": "Eau vs fissure",
"a": "COUSSIN : refroidissement trรจs rapide au contact de l'eau (lave sous-marine).\nFILON (dyke) : injection de magma dans des fissures verticales."},
{"id": 35, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Quelle minรฉralogie correspond au faciรจs schiste vert ?",
"hint": "Tempรฉrature < 500ยฐC",
"a": "CHLORITE + ACTINOTE (amphibole calcique) : minรฉraux hydratรฉs de basse tempรฉrature."},
{"id": 36, "ch": "4", "section": "CH.4 โ€” Dorsales lentes",
"q": "Pourquoi une dorsale lente a-t-elle un rift trรจs marquรฉ ?",
"hint": "Magma insuffisant pour...",
"a": "Magmatisme trop faible pour COMBLER L'ESPACE crรฉรฉ par l'รฉcartement.\nL'extension tectonique forme un FOSSร‰ D'EFFONDREMENT PROFOND."},
{"id": 37, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Transformation minรฉrale : pyroxรจne + plagioclase โ†’ ? (hydrothermalisme > 700ยฐC)",
"hint": "Faciรจs amphibolite",
"a": "Pyroxรจne + plagioclase (gabbro) โ†’ HORNBLENDE (amphibole)\nSous l'action de l'hydrothermalisme ร  > 700ยฐC."},
{"id": 38, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "La pรฉridotite fond-elle entiรจrement sous une dorsale ? Quel pourcentage ?",
"hint": "Partielle !",
"a": "Non : seulement FUSION PARTIELLE (~15%).\nLe reste reste SOLIDE. C'est le liquide qui monte former la croรปte."},
{"id": 39, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Rรดle de la convection mantellique dans le fonctionnement d'une dorsale ?",
"hint": "Mouvement ascendant",
"a": "La CONVECTION assure la REMONTร‰E DES Pร‰RIDOTITES sous la dorsale,\ndรฉclenchant la dรฉcompression adiabatique et la fusion partielle."},
{"id": 40, "ch": "4", "section": "CH.4 โ€” Dorsales lentes",
"q": "Qu'est-ce qu'une faille de dรฉtachement aux dorsales lentes ?",
"hint": "Grande faille, faible pendage",
"a": "Grande FAILLE ร€ FAIBLE PENDAGE (presque horizontale)\npermettant l'exhumation de pรฉridotites directement sur le plancher ocรฉanique."},
# CH.5 โ€” Zones de convergence
{"id": 41, "ch": "5", "section": "CH.5 โ€” Marqueurs de subduction",
"q": "Quels sont les 3 marqueurs principaux d'une zone de subduction ?",
"hint": "1 relief / 1 alignement / 1 sismique",
"a": "1. FOSSE OCร‰ANIQUE profonde\n2. ALIGNEMENT VOLCANIQUE (arc insulaire ou cordillรจre)\n3. PLAN DE WADATI-BENIOFF (sismicitรฉ profonde)"},
{"id": 42, "ch": "5", "section": "CH.5 โ€” Marqueurs de subduction",
"q": "Pourquoi la sismicitรฉ suit-elle un plan inclinรฉ en subduction ?",
"hint": "Rigiditรฉ de la plaque",
"a": "La plaque reste RIGIDE en plongeant (elle est froide).\nLes contraintes gรฉnรจrent des sรฉismes le long du plan de contact : WADATI-BENIOFF."},
{"id": 43, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Pourquoi le volcanisme de subduction est-il explosif ?",
"hint": "Composition du magma",
"a": "Magma riche en SILICE (trรจs visqueux) et en GAZ DISSOUS.\nViscositรฉ รฉlevรฉe โ†’ dรฉgazage brutal โ†’ EXPLOSION VIOLENTE."},
{"id": 44, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Quelle est la chaรฎne de rรฉactions menant au volcanisme de subduction ?",
"hint": "Eau โ†’ pรฉridotite โ†’ magma",
"a": "1. Mรฉtamorphisme โ†’ libรฉration d'EAU\n2. Eau monte dans le COIN MANTELLIQUE\n3. Abaisse le point de fusion des pรฉridotites\n4. FUSION PARTIELLE โ†’ magma โ†’ volcanisme"},
{"id": 45, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Quelles sont les 4 roches de subduction et leur texture ?",
"hint": "2 volcaniques + 2 plutoniques",
"a": "Volcaniques (microlithique) : ANDร‰SITE, RHYOLITE\nPlutoniques (grenue) : DIORITE, GRANITE"},
{"id": 46, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Pourquoi existe-t-il une telle diversitรฉ de roches en subduction ?",
"hint": "2 processus",
"a": "CRISTALLISATION FRACTIONNร‰E (diffรฉrenciation du magma)\n+ CONTAMINATION par la croรปte continentale traversรฉe."},
{"id": 47, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Quel est le signe minรฉralogique caractรฉristique des roches de subduction ?",
"hint": "OH dans les minรฉraux",
"a": "Prรฉsence de MINร‰RAUX HYDRATร‰S portant des groupements OH\n(ex. : amphibole, biotite, muscovite)."},
{"id": 48, "ch": "5", "section": "CH.5 โ€” Mรฉtamorphisme",
"q": "Sรฉquence de faciรจs mรฉtamorphiques de la plaque subduite ?",
"hint": "3 faciรจs successifs",
"a": "SCHISTE VERT โ†’ SCHISTE BLEU (glaucophane, HP-BT) โ†’ ร‰CLOGITE (grenat + jadรฉite, > 50 km)"},
{"id": 49, "ch": "5", "section": "CH.5 โ€” Mรฉtamorphisme",
"q": "Quelle minรฉralogie caractรฉrise le faciรจs schiste bleu ?",
"hint": "Minรฉral bleu, HP-BT",
"a": "GLAUCOPHANE (amphibole sodique bleue) :\nindicateur de conditions HAUTE PRESSION โ€“ BASSE TEMPร‰RATURE."},
{"id": 50, "ch": "5", "section": "CH.5 โ€” Mรฉtamorphisme",
"q": "Minรฉralogie du faciรจs รฉclogite et profondeur de formation ?",
"hint": "> 50 km",
"a": "GRENAT + JADร‰ITE (pyroxรจne sodique)\nFormรฉs ร  > 50 KM de profondeur, sous trรจs haute pression."},
{"id": 51, "ch": "5", "section": "CH.5 โ€” Mรฉtamorphisme",
"q": "En quoi le contexte P-T de la subduction est-il unique ?",
"hint": "Comparer avec mรฉtamorphisme classique",
"a": "HAUTE PRESSION / BASSE TEMPร‰RATURE :\nLa plaque s'enfonce vite et reste froide.\nContraire du mรฉtamorphisme de contact (BT/BP)."},
{"id": 52, "ch": "5", "section": "CH.5 โ€” Moteur",
"q": "Quel est le moteur principal du mouvement des plaques ?",
"hint": "Force de tirage",
"a": "TRACTION DE LA PLAQUE SUBDUITE (slab pull) :\nPlaque froide et dense โ†’ gravitรฉ โ†’ tire la plaque par gravitรฉ,\nentraรฎne les mouvements descendants de la convection mantellique."},
{"id": 53, "ch": "5", "section": "CH.5 โ€” Moteur",
"q": "Quel lien y a-t-il entre subduction et convection mantellique ?",
"hint": "Cause-consรฉquence",
"a": "La subduction ENTRETIENT les courants descendants froids de la convection.\nLes plaques ne sont pas portรฉes passivement : elles TIRENT ACTIVEMENT."},
{"id": 54, "ch": "5", "section": "CH.5 โ€” Collision",
"q": "Comment se produit une collision continentale ?",
"hint": "Aprรจs quoi ?",
"a": "Aprรจs la FERMETURE COMPLรˆTE D'UN OCร‰AN par subduction.\nLes deux marges continentales entrent en contact : croรปte trop lรฉgรจre pour plonger."},
{"id": 55, "ch": "5", "section": "CH.5 โ€” Collision",
"q": "Quelles structures gรฉologiques tรฉmoignent d'une collision en surface ?",
"hint": "3 structures",
"a": "PLIS + FAILLES INVERSES + NAPPES DE CHARRIAGE\n(grandes masses de roches dรฉplacรฉes horizontalement)"},
{"id": 56, "ch": "5", "section": "CH.5 โ€” Collision",
"q": "Qu'est-ce qu'une racine crustale ? ร€ quelle profondeur descend le Moho ?",
"hint": "Iceberg crustal",
"a": "ร‰paississement de la croรปte vers le bas.\nLe MOHO peut descendre jusqu'ร  70 KM (normal : ~35 km)."},
{"id": 57, "ch": "5", "section": "CH.5 โ€” Collision",
"q": "Quels types de roches mรฉtamorphiques se forment en collision ?",
"hint": "Roches foliรฉes",
"a": "Roches FOLIร‰ES :\n- MICASCHISTES (mรฉtamorphisme faible ร  modรฉrรฉ)\n- GNEISS (fort mรฉtamorphisme)"},
{"id": 58, "ch": "5", "section": "CH.5 โ€” Marqueurs",
"q": "Diffรฉrence entre arc insulaire et cordillรจre en subduction ?",
"hint": "Ocรฉan-Ocรฉan vs Ocรฉan-Continent",
"a": "ARC INSULAIRE : subduction Ocรฉan/Ocรฉan โ†’ volcanisme en MER.\nCORDILLรˆRE : subduction Ocรฉan/Continent โ†’ volcanisme en BORDURE DE CONTINENT."},
{"id": 59, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Qu'est-ce que le coin mantellique et quel est son rรดle ?",
"hint": "Wedge",
"a": "Portion du MANTEAU DE LA PLAQUE CHEVAUCHANTE entre les deux plaques.\nReรงoit l'EAU libรฉrรฉe โ†’ pรฉridotite fond partiellement โ†’ magmatisme."},
{"id": 60, "ch": "5", "section": "CH.5 โ€” Risques",
"q": "Risque volcanique caractรฉristique des zones de subduction ?",
"hint": "Nuage mortel",
"a": "NUร‰ES ARDENTES (coulรฉes pyroclastiques) :\nmรฉlanges brรปlants de gaz, cendres, dรฉbris ร  trรจs grande vitesse.\nLiรฉ ร  la viscositรฉ รฉlevรฉe du magma."},
# SYNTHรˆSE
{"id": 61, "ch": "synth", "section": "SYNTHรˆSE โ€” Cycle lithosphรฉrique",
"q": "Rรฉsume en 5 รฉtapes le cycle de la lithosphรจre ocรฉanique.",
"hint": "Naissance โ†’ mort",
"a": "1. Naissance ร  la DORSALE (HT, BP)\n2. Hydratation + refroidissement โ†’ schistes verts\n3. Densification โ†’ SUBDUCTION\n4. Mรฉtamorphisme HP-BT (schiste bleu โ†’ รฉclogite) + libรฉration d'eau\n5. Dรฉclenchement du MAGMATISME โ†’ croรปte continentale"},
{"id": 62, "ch": "synth", "section": "SYNTHรˆSE โ€” Moteur",
"q": "Quel processus assure la continuitรฉ de la convection mantellique globale ?",
"hint": "Traction + plongement",
"a": "TRACTION DE LA PLAQUE PLONGEANTE (slab pull) :\nforce gravitaire exercรฉe par la plaque froide et dense,\nmoteur principal du cycle convectif global."},
{"id": 63, "ch": "synth", "section": "SYNTHรˆSE โ€” Eau",
"q": "Quel est le rรดle de l'eau dans le cycle de la lithosphรจre ?",
"hint": "Partout : dorsale โ†’ subduction",
"a": "DORSALE : hydratation hydrothermale (schistes verts)\nSUBDUCTION : expulsion par mรฉtamorphisme โ†’ abaissement du solidus des pรฉridotites โ†’ magmatisme"},
{"id": 64, "ch": "synth", "section": "SYNTHรˆSE โ€” Cycle",
"q": "Oรน naรฎt et oรน meurt la lithosphรจre ocรฉanique ?",
"hint": "Deux lieux opposรฉs",
"a": "NAISSANCE : ร  la DORSALE (accrรฉtion)\nMORT : ร  la FOSSE DE SUBDUCTION (rรฉintรฉgration dans le manteau)"},
{"id": 65, "ch": "synth", "section": "SYNTHรˆSE โ€” Croรปte continentale",
"q": "Comment la croรปte continentale est-elle crรฉรฉe via la subduction ?",
"hint": "Eau โ†’ fusion โ†’ granite",
"a": "Eau libรฉrรฉe โ†’ fusion coin mantellique โ†’ magma riche en silice โ†’ volcanisme + intrusions\nโ†’ GRANITE ET DIORITE = croรปte continentale"},
# Notions complรฉmentaires
{"id": 66, "ch": "3", "section": "CH.3 โ€” Anomalies magnรฉtiques",
"q": "Qu'est-ce qu'une polaritรฉ magnรฉtique normale vs inverse ?",
"hint": "Pรดles Nord et Sud",
"a": "NORMALE : orientation actuelle (pรดle magnรฉtique Nord โ‰ˆ Nord gรฉographique)\nINVERSE : pรดles NORD ET SUD MAGNร‰TIQUES INVERSร‰S"},
{"id": 67, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "Pourquoi la lithosphรจre ocรฉanique devient-elle plus dense avec le temps ?",
"hint": "Refroidissement + manteau",
"a": "En se refroidissant, elle intรจgre du manteau refroidi par sa base.\nMinรฉraux se contractent โ†’ densitรฉ dรฉpasse celle de l'ASTHร‰NOSPHรˆRE (~3,3 g/cmยณ)."},
{"id": 68, "ch": "5", "section": "CH.5 โ€” Mรฉtamorphisme",
"q": "Pourquoi le mรฉtamorphisme de subduction est-il HP-BT ?",
"hint": "Vitesse d'enfouissement",
"a": "La plaque S'ENFONCE RAPIDEMENT : pression augmente vite\nmais plaque froide ne se rรฉchauffe pas assez โ†’ HAUTE PRESSION, BASSE TEMPร‰RATURE."},
{"id": 69, "ch": "5", "section": "CH.5 โ€” Collision",
"q": "Pourquoi la croรปte continentale ne peut-elle pas subduire ?",
"hint": "Densitรฉ vs manteau",
"a": "Croรปte continentale : ~2,7 g/cmยณ\nManteau : ~3,3 g/cmยณ\nElle est MOINS DENSE โ†’ FLOTTE โ†’ ne peut pas plonger โ†’ COLLISION."},
{"id": 70, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Qu'est-ce que le gรฉotherme et le solidus dans le contexte des dorsales ?",
"hint": "Deux courbes sur un graphique P-T",
"a": "Gร‰OTHERME : tempรฉrature en fonction de la profondeur.\nSOLIDUS : courbe limite en dessous de laquelle la roche est solide.\nSi gรฉotherme RECOUPE le solidus โ†’ LA ROCHE FOND."},
{"id": 71, "ch": "3", "section": "CH.3 โ€” Preuves expansion",
"q": "Quels indices prouvent l'expansion ocรฉanique (3 types) ?",
"hint": "Magnรฉtique + sรฉdimentaire + gรฉodรฉsique",
"a": "1. ANOMALIES MAGNร‰TIQUES SYMร‰TRIQUES\n2. ร‚GE/ร‰PAISSEUR croissants des sรฉdiments\n3. MESURES GPS (gรฉodรฉsie spatiale)"},
{"id": 72, "ch": "4", "section": "CH.4 โ€” Dorsales rapides",
"q": "Composition minรฉralogique initiale d'un gabbro (avant mรฉtamorphisme) ?",
"hint": "2 minรฉraux principaux",
"a": "PYROXรˆNE (clinopyroxรจne) + PLAGIOCLASE (feldspath calcique).\nRemplacรฉs par hornblende puis chlorite + actinote lors du mรฉtamorphisme hydrothermal."},
{"id": 73, "ch": "5", "section": "CH.5 โ€” Moteur",
"q": "Quelle force initie le mouvement de la plaque vers la fosse ?",
"hint": "Densitรฉ + gravitรฉ",
"a": "Plaque ocรฉanique vieillie : PLUS DENSE QUE L'ASTHร‰NOSPHรˆRE.\nGravitรฉ โ†’ PLONGEMENT SPONTANร‰ (slab pull) โ†’ tire le reste de la plaque."},
{"id": 74, "ch": "5", "section": "CH.5 โ€” Magmatisme",
"q": "Quelle roche est l'รฉquivalent plutonique de l'andรฉsite ?",
"hint": "Roche ร  grain visible",
"a": "La DIORITE : mรชme composition que l'andรฉsite\nmais refroidissement lent en profondeur โ†’ texture GRENUE."},
{"id": 75, "ch": "4", "section": "CH.4 โ€” Vieillissement",
"q": "ร€ quelle tempรฉrature se place la limite faciรจs amphibolite / faciรจs schiste vert ?",
"hint": "Deux seuils de tempรฉrature",
"a": "AMPHIBOLITE : > 700ยฐC\nSCHISTE VERT : entre 300 et 500ยฐC\nTransition : 500โ€“700ยฐC"},
{"id": 76, "ch": "synth", "section": "SYNTHรˆSE โ€” Les 3 faciรจs du voyage",
"q": "Cite les 3 faciรจs jalonnant le parcours d'une plaque ocรฉanique (dorsale โ†’ arc).",
"hint": "Dorsale โ†’ plaque รขgรฉe โ†’ subduction",
"a": "1. SCHISTE VERT (hydrothermalisme dorsale + vieillissement)\n2. SCHISTE BLEU (dรฉbut subduction, HP-BT)\n3. ร‰CLOGITE (subduction profonde > 50 km)"},
]
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# COULEURS ET CONSTANTES
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
SAVE_FILE = os.path.expanduser("~/.geo_revision_save.json")
CH_LABELS = {
"3": "CH.3 Mobilitรฉ",
"4": "CH.4 Divergence",
"5": "CH.5 Convergence",
"synth": "SYNTHรˆSE",
}
STATUS_LABELS = {None: "Non vu", 0: "ร€ revoir", 1: "Hรฉsitant", 2: "Maรฎtrisรฉ"}
WRONG_POOL = [
"Cristallisation fractionnรฉe", "Plan de Wadati-Benioff", "Dรฉcompression adiabatique",
"Fusion partielle", "Serpentinisation", "Dorsale mรฉdio-atlantique", "Glaucophane",
"Faille inverse", "Faille normale", "Basalte en coussin", "Jadรฉite", "Grenat",
"Hornblende", "Chlorite", "Actinote", "Gabbro", "Andรฉsite", "Diorite",
"Schiste bleu", "Schiste vert", "ร‰clogite", "Slab pull", "Coin mantellique",
"Chambre magmatique", "Isotherme 1300ยฐC", "Solidus", "Gรฉotherme",
"Arc insulaire", "Cordillรจre", "Nuรฉe ardente", "5 ร  7 km", "10 ร  16 cm/an",
"1 ร  5 cm/an", "700 km de profondeur", "50 km de profondeur",
"Fumeurs noirs", "Micaschiste", "Gneiss", "Pรฉridotite", "Pyroxรจne",
"Plagioclase", "Serpentine", "Nappe de charriage", "Racine crustale",
]
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# SAUVEGARDE
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def load_progress():
if os.path.exists(SAVE_FILE):
try:
with open(SAVE_FILE) as f:
data = json.load(f)
return {int(k): v for k, v in data.items()}
except Exception:
pass
return {}
def save_progress(status):
try:
with open(SAVE_FILE, "w") as f:
json.dump(status, f)
except Exception:
pass
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# UTILITAIRES CURSES
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def init_colors():
curses.start_color()
curses.use_default_colors()
# Paires : (numรฉro, fg, bg)
curses.init_pair(1, curses.COLOR_WHITE, -1) # normal
curses.init_pair(2, curses.COLOR_CYAN, -1) # titre/accent
curses.init_pair(3, curses.COLOR_GREEN, -1) # maรฎtrisรฉ
curses.init_pair(4, curses.COLOR_YELLOW, -1) # hรฉsitant / ch.3
curses.init_pair(5, curses.COLOR_RED, -1) # ร  revoir
curses.init_pair(6, curses.COLOR_MAGENTA, -1) # ch.4 / hint
curses.init_pair(7, curses.COLOR_BLUE, -1) # ch.5
curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_BLUE) # sรฉlectionnรฉ
curses.init_pair(9, curses.COLOR_BLACK, curses.COLOR_GREEN) # correct
curses.init_pair(10, curses.COLOR_WHITE, curses.COLOR_RED) # faux
curses.init_pair(11, curses.COLOR_CYAN, curses.COLOR_BLACK) # header bg
curses.init_pair(12, curses.COLOR_WHITE, curses.COLOR_CYAN) # bouton actif
C_NORMAL = lambda: curses.color_pair(1)
C_TITLE = lambda: curses.color_pair(2) | curses.A_BOLD
C_GREEN = lambda: curses.color_pair(3) | curses.A_BOLD
C_YELLOW = lambda: curses.color_pair(4) | curses.A_BOLD
C_RED = lambda: curses.color_pair(5) | curses.A_BOLD
C_MAGENTA = lambda: curses.color_pair(6)
C_BLUE = lambda: curses.color_pair(7)
C_SEL = lambda: curses.color_pair(8) | curses.A_BOLD
C_OK = lambda: curses.color_pair(9) | curses.A_BOLD
C_FAIL = lambda: curses.color_pair(10) | curses.A_BOLD
C_HEADER = lambda: curses.color_pair(11) | curses.A_BOLD
C_BTN = lambda: curses.color_pair(12) | curses.A_BOLD
def ch_color(ch):
return {
"3": C_YELLOW(),
"4": C_MAGENTA(),
"5": C_BLUE(),
"synth": C_GREEN(),
}.get(ch, C_NORMAL())
def safe_addstr(win, y, x, text, attr=0):
h, w = win.getmaxyx()
if y < 0 or y >= h or x < 0:
return
max_len = w - x - 1
if max_len <= 0:
return
try:
win.addstr(y, x, text[:max_len], attr)
except curses.error:
pass
def draw_box(win, y, x, h, w, title="", color=None):
attr = color or C_NORMAL()
h2, w2 = win.getmaxyx()
# top
try:
safe_addstr(win, y, x, "โ•”" + "โ•" * (w-2) + "โ•—", attr)
for i in range(1, h-1):
if y+i < h2:
safe_addstr(win, y+i, x, "โ•‘", attr)
safe_addstr(win, y+i, x+w-1, "โ•‘", attr)
safe_addstr(win, y+h-1, x, "โ•š" + "โ•" * (w-2) + "โ•", attr)
if title:
t = f" {title} "
tx = x + (w - len(t)) // 2
safe_addstr(win, y, tx, t, attr | curses.A_BOLD)
except curses.error:
pass
def draw_header(win, title):
h, w = win.getmaxyx()
try:
win.attron(C_HEADER())
win.addstr(0, 0, " " * w)
safe_addstr(win, 0, 0, f" Gร‰O-Rร‰VISION โ”‚ {title}", C_HEADER())
win.attroff(C_HEADER())
except curses.error:
pass
def wrap_text(text, width):
lines = []
for paragraph in text.split("\n"):
if len(paragraph) <= width:
lines.append(paragraph)
else:
wrapped = textwrap.wrap(paragraph, width)
lines.extend(wrapped if wrapped else [""])
return lines
def get_stats(status):
good = sum(1 for v in status.values() if v == 2)
ok = sum(1 for v in status.values() if v == 1)
bad = sum(1 for v in status.values() if v == 0)
unseen = 76 - good - ok - bad
return good, ok, bad, unseen
def draw_stats_bar(win, y, status):
good, ok, bad, unseen = get_stats(status)
h, w = win.getmaxyx()
bar = (
f" โœ“ Maรฎtrisรฉs:{good:3} "
f"~ Hรฉsitants:{ok:3} "
f"โœ— ร€ revoir:{bad:3} "
f"? Non vus:{unseen:3} "
f"Total: 76"
)
safe_addstr(win, y, 0, bar.ljust(w), C_NORMAL())
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ร‰CRAN MENU PRINCIPAL
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def menu_screen(stdscr, status):
options = [
("1", "Flashcards", "Rรฉvision active par carte โ€” toutes chapitres"),
("2", "Flashcards CH.3", "Chapitre 3 : Mobilitรฉ horizontale"),
("3", "Flashcards CH.4", "Chapitre 4 : Zones de divergence"),
("4", "Flashcards CH.5", "Chapitre 5 : Zones de convergence"),
("5", "Flashcards Synthรจse", "Cycle lithosphรฉrique complet"),
("6", "Flashcards ร€ revoir", "Seulement les cartes non maรฎtrisรฉes"),
("7", "Quiz QCM", "Entraรฎnement avec 4 propositions"),
("8", "Rรฉcapitulatif", "Toutes les 76 notions en lecture"),
("9", "Carte de progression", "Visualisation de l'avancement"),
("0", "Rรฉinitialiser", "Effacer la progression"),
("Q", "Quitter", ""),
]
sel = 0
while True:
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, "MENU PRINCIPAL")
# Logo
logo = [
"โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—",
"โ•‘ Gร‰O-Rร‰VISION โ€” Tectonique des Plaques โ•‘",
"โ•‘ 76 notions ยท CH.3 ยท CH.4 ยท CH.5 ยท Synthรจse โ•‘",
"โ•‘ Mรฉthode : Ebbinghaus + Testing Effect โ•‘",
"โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•",
]
for i, line in enumerate(logo):
safe_addstr(stdscr, 2+i, max(0,(w-len(line))//2), line, C_TITLE())
# Stats
draw_stats_bar(stdscr, 8, status)
# Options
for i, (key, name, desc) in enumerate(options):
y = 10 + i
attr = C_SEL() if i == sel else C_NORMAL()
prefix = "โ–ถ " if i == sel else " "
line = f"{prefix}[{key}] {name:<30} {desc}"
safe_addstr(stdscr, y, 2, line[:w-4], attr)
safe_addstr(stdscr, h-2, 2, "โ†‘โ†“ pour naviguer ยท ENTRร‰E pour sรฉlectionner", C_MAGENTA())
k = stdscr.getch()
if k == curses.KEY_UP:
sel = (sel - 1) % len(options)
elif k == curses.KEY_DOWN:
sel = (sel + 1) % len(options)
elif k in (curses.KEY_ENTER, 10, 13):
key = options[sel][0]
return key
elif k in range(256):
c = chr(k).upper()
for i, (key, _, _) in enumerate(options):
if c == key.upper():
return key
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# FLASHCARD
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def flashcard_screen(stdscr, status, cards, title="Flashcards"):
if not cards:
stdscr.clear()
draw_header(stdscr, title)
safe_addstr(stdscr, 5, 4, "Aucune carte dans ce filtre.", C_YELLOW())
safe_addstr(stdscr, 7, 4, "Appuie sur une touche pour revenir.", C_NORMAL())
stdscr.getch()
return status
deck = cards[:]
random.shuffle(deck)
idx = 0
flipped = False
while True:
card = deck[idx]
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, f"{title} [{idx+1}/{len(deck)}]")
draw_stats_bar(stdscr, 1, status)
# Barre progression
pct = (idx + 1) / len(deck)
bar_w = min(w - 4, 60)
filled = int(pct * bar_w)
bar = "โ–ˆ" * filled + "โ–‘" * (bar_w - filled)
safe_addstr(stdscr, 2, 2, f"[{bar}] {int(pct*100)}%", C_CYAN() if hasattr(curses, 'COLOR_CYAN') else C_NORMAL())
# Encart chapitre
safe_addstr(stdscr, 4, 2, f"โ–  {card['section']}", ch_color(card["ch"]))
# Statut de la carte
s = status.get(card["id"])
s_colors = {None: C_NORMAL(), 0: C_RED(), 1: C_YELLOW(), 2: C_GREEN()}
s_label = STATUS_LABELS.get(s, "Non vu")
safe_addstr(stdscr, 4, w-20, f"[#{card['id']:02d}] {s_label}", s_colors.get(s, C_NORMAL()))
if not flipped:
# Recto : question
draw_box(stdscr, 5, 1, min(12, h-14), w-2, "QUESTION", C_TITLE())
q_lines = wrap_text(card["q"], w-6)
for i, line in enumerate(q_lines[:6]):
safe_addstr(stdscr, 7+i, 4, line, C_NORMAL() | curses.A_BOLD)
if card.get("hint"):
safe_addstr(stdscr, 13, 4, f"๐Ÿ’ก {card['hint']}", C_MAGENTA())
safe_addstr(stdscr, 15, 4, "[ ESPACE ] Rรฉvรฉler la rรฉponse", C_TITLE())
safe_addstr(stdscr, 16, 4, "[ โ† / โ†’ ] Carte prรฉcรฉdente / suivante [ M ] Mรฉlanger [ Q ] Menu", C_MAGENTA())
else:
# Verso : rรฉponse
draw_box(stdscr, 5, 1, 3, w-2, "QUESTION", C_YELLOW())
q_short = card["q"][:w-6]
safe_addstr(stdscr, 6, 4, q_short, C_YELLOW())
draw_box(stdscr, 8, 1, min(11, h-16), w-2, "Rร‰PONSE", C_GREEN())
a_lines = wrap_text(card["a"], w-6)
for i, line in enumerate(a_lines[:7]):
safe_addstr(stdscr, 10+i, 4, line, C_NORMAL())
# Boutons notation
y_btn = h - 6
safe_addstr(stdscr, y_btn, 2, "[ 1 ] โœ— ร€ REVOIR", C_RED())
safe_addstr(stdscr, y_btn, 25, "[ 2 ] ~ Hร‰SITANT", C_YELLOW())
safe_addstr(stdscr, y_btn, 50, "[ 3 ] โœ“ MAรŽTRISร‰", C_GREEN())
safe_addstr(stdscr, y_btn+1, 2, "[ โ† / โ†’ ] Naviguer [ ESPACE ] Retourner [ Q ] Menu", C_MAGENTA())
k = stdscr.getch()
if k == ord(' '):
flipped = not flipped
elif k == curses.KEY_RIGHT or k == ord('n') or k == ord('N'):
idx = (idx + 1) % len(deck)
flipped = False
elif k == curses.KEY_LEFT or k == ord('p') or k == ord('P'):
idx = (idx - 1) % len(deck)
flipped = False
elif k == ord('m') or k == ord('M'):
random.shuffle(deck)
idx = 0
flipped = False
elif k == ord('1') and flipped:
status[card["id"]] = 0
save_progress(status)
flipped = False
idx = (idx + 1) % len(deck)
elif k == ord('2') and flipped:
status[card["id"]] = 1
save_progress(status)
flipped = False
idx = (idx + 1) % len(deck)
elif k == ord('3') and flipped:
status[card["id"]] = 2
save_progress(status)
flipped = False
idx = (idx + 1) % len(deck)
elif k == ord('q') or k == ord('Q'):
break
return status
def C_CYAN():
return curses.color_pair(2)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# QUIZ QCM
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def build_quiz_question(card):
"""Crรฉe une question QCM ร  partir d'une carte."""
first_line = card["a"].split("\n")[0].strip()
# Nettoie les annotations type "1. "
correct = re.sub(r"^\d+\.\s*", "", first_line).strip()
if len(correct) > 70:
correct = correct[:70] + "..."
# Gรฉnรจre 3 mauvaises rรฉponses diffรฉrentes
pool = [w for w in WRONG_POOL if w.lower() not in correct.lower()]
random.shuffle(pool)
wrongs = pool[:3]
options = wrongs + [correct]
random.shuffle(options)
return {
"question": card["q"],
"correct": correct,
"options": options,
"explanation": card["a"],
"section": card["section"],
"ch": card["ch"],
}
def quiz_screen(stdscr, status, cards=None):
if cards is None:
cards = CARDS[:]
random.shuffle(cards)
score = 0
total = len(cards)
wrong_ids = []
for qi, card in enumerate(cards):
qdata = build_quiz_question(card)
sel = 0
answered = False
chosen = None
while True:
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, f"QUIZ [{qi+1}/{total}] Score: {score}/{qi}")
draw_stats_bar(stdscr, 1, status)
# Section
safe_addstr(stdscr, 3, 2, f"โ–  {qdata['section']}", ch_color(qdata["ch"]))
# Question
draw_box(stdscr, 4, 1, 4, w-2, "QUESTION", C_TITLE())
q_lines = wrap_text(qdata["question"], w-6)
for i, line in enumerate(q_lines[:2]):
safe_addstr(stdscr, 5+i, 4, line, C_NORMAL() | curses.A_BOLD)
# Options
for i, opt in enumerate(qdata["options"]):
y = 9 + i * 2
prefix = " "
if not answered:
attr = C_SEL() if i == sel else C_NORMAL()
prefix = "โ–ถ " if i == sel else " "
else:
if opt == qdata["correct"]:
attr = C_GREEN()
prefix = "โœ“ "
elif opt == chosen and chosen != qdata["correct"]:
attr = C_RED()
prefix = "โœ— "
else:
attr = C_NORMAL()
prefix = " "
opt_short = opt[:w-10]
safe_addstr(stdscr, y, 2, f"{prefix}[{'ABCD'[i]}] {opt_short}", attr)
if answered:
# Explication
exp_y = 18
if chosen == qdata["correct"]:
safe_addstr(stdscr, exp_y, 2, "โœ“ BONNE Rร‰PONSE !", C_GREEN())
else:
safe_addstr(stdscr, exp_y, 2, f"โœ— Rรฉponse correcte : {qdata['correct'][:w-30]}", C_RED())
exp_lines = wrap_text(qdata["explanation"], w-6)
for i, line in enumerate(exp_lines[:3]):
safe_addstr(stdscr, exp_y+1+i, 4, line, C_MAGENTA())
safe_addstr(stdscr, h-2, 2, "[ ENTRร‰E ou ESPACE ] Question suivante [ Q ] Arrรชter", C_TITLE())
else:
safe_addstr(stdscr, h-2, 2, "[ โ†‘โ†“ ] Naviguer [ ENTRร‰E ] Valider [ Q ] Arrรชter", C_TITLE())
k = stdscr.getch()
if not answered:
if k == curses.KEY_UP:
sel = (sel - 1) % 4
elif k == curses.KEY_DOWN:
sel = (sel + 1) % 4
elif k in range(ord('a'), ord('e')):
sel = k - ord('a')
elif k in range(ord('A'), ord('E')):
sel = k - ord('A')
elif k in (curses.KEY_ENTER, 10, 13, ord(' ')):
answered = True
chosen = qdata["options"][sel]
if chosen == qdata["correct"]:
score += 1
status[card["id"]] = max(status.get(card["id"], 0) or 0, 2)
else:
wrong_ids.append(card["id"])
status[card["id"]] = 0
save_progress(status)
elif k == ord('q') or k == ord('Q'):
break
else:
if k in (curses.KEY_ENTER, 10, 13, ord(' ')):
break
elif k == ord('q') or k == ord('Q'):
# Montre rรฉsultat intermรฉdiaire
qi = total
break
if k == ord('q') or k == ord('Q'):
break
# Rรฉsultat final
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, "Rร‰SULTAT DU QUIZ")
pct = int(score / total * 100) if total > 0 else 0
color = C_GREEN() if pct >= 80 else C_YELLOW() if pct >= 50 else C_RED()
draw_box(stdscr, 3, w//2-20, 12, 40, "SCORE FINAL", C_TITLE())
score_str = f"{pct}%"
safe_addstr(stdscr, 6, w//2 - len(score_str)//2, score_str, color | curses.A_BOLD)
detail = f"{score} bonnes rรฉponses sur {total}"
safe_addstr(stdscr, 8, w//2 - len(detail)//2, detail, C_NORMAL())
msg = ("๐Ÿ† Excellent ! Chapitres maรฎtrisรฉs !" if pct >= 80 else
"๐Ÿ‘ Bien ! Quelques rรฉvisions restent utiles." if pct >= 60 else
"โš  Des lacunes โ€” retourne aux flashcards !" if pct >= 40 else
"๐Ÿ“š Reprends les flashcards et rรฉvise tout !")
safe_addstr(stdscr, 10, w//2 - len(msg)//2, msg[:w-4], C_TITLE())
if wrong_ids:
safe_addstr(stdscr, 16, 2, f"Cartes ร  revoir : {', '.join(f'#{i}' for i in wrong_ids[:15])}", C_RED())
safe_addstr(stdscr, h-2, 2, "Appuie sur une touche pour revenir au menu.", C_MAGENTA())
stdscr.getch()
return status
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Rร‰CAPITULATIF
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def recap_screen(stdscr, status):
lines = []
groups = [
("3", "โ•โ•โ• CHAPITRE 3 โ€” Mobilitรฉ horizontale des plaques โ•โ•โ•"),
("4", "โ•โ•โ• CHAPITRE 4 โ€” Dynamique des zones de divergence โ•โ•โ•"),
("5", "โ•โ•โ• CHAPITRE 5 โ€” Dynamique des zones de convergence โ•โ•โ•"),
("synth", "โ•โ•โ• SYNTHรˆSE โ€” Cycle de la lithosphรจre ocรฉanique โ•โ•โ•"),
]
for ch, title in groups:
lines.append(("title", ch, title))
for c in CARDS:
if c["ch"] == ch:
lines.append(("section", ch, f" โ”€ {c['section']}"))
lines.append(("question", ch, f" #{c['id']:02d} Q: {c['q']}"))
for al in c["a"].split("\n"):
lines.append(("answer", ch, f" โ†’ {al}"))
lines.append(("sep", ch, ""))
scroll = 0
while True:
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, "Rร‰CAPITULATIF COMPLET โ€” 76 notions")
draw_stats_bar(stdscr, 1, status)
visible = h - 5
for i, (ltype, ch, text) in enumerate(lines[scroll:scroll+visible]):
y = 3 + i
text_trunc = text[:w-2]
if ltype == "title":
safe_addstr(stdscr, y, 0, text_trunc.ljust(w-1), ch_color(ch) | curses.A_BOLD)
elif ltype == "section":
safe_addstr(stdscr, y, 0, text_trunc, C_CYAN())
elif ltype == "question":
safe_addstr(stdscr, y, 0, text_trunc, C_NORMAL() | curses.A_BOLD)
elif ltype == "answer":
safe_addstr(stdscr, y, 0, text_trunc, C_MAGENTA())
else:
pass
pct_scroll = int(scroll / max(1, len(lines) - visible) * 100)
safe_addstr(stdscr, h-1, 2, f"โ†‘โ†“/PgUp/PgDn : dรฉfiler ({pct_scroll}%) Q : menu", C_TITLE())
k = stdscr.getch()
if k == curses.KEY_UP:
scroll = max(0, scroll - 1)
elif k == curses.KEY_DOWN:
scroll = min(len(lines) - visible, scroll + 1)
elif k == curses.KEY_PPAGE:
scroll = max(0, scroll - visible)
elif k == curses.KEY_NPAGE:
scroll = min(len(lines) - visible, scroll + visible)
elif k == ord('q') or k == ord('Q'):
break
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# CARTE DE PROGRESSION
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def map_screen(stdscr, status):
while True:
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, "CARTE DE PROGRESSION")
draw_stats_bar(stdscr, 1, status)
safe_addstr(stdscr, 3, 2, "Lรฉgende :", C_NORMAL() | curses.A_BOLD)
safe_addstr(stdscr, 3, 14, "[โ–ˆโ–ˆ] Non vu", C_NORMAL())
safe_addstr(stdscr, 3, 28, "[โ–ˆโ–ˆ] ร€ revoir", C_RED())
safe_addstr(stdscr, 3, 44, "[~~] Hรฉsitant", C_YELLOW())
safe_addstr(stdscr, 3, 60, "[OK] Maรฎtrisรฉ", C_GREEN())
# Grille
cols = min(19, (w - 4) // 5)
for c in CARDS:
idx = c["id"] - 1
row = idx // cols
col = idx % cols
y = 5 + row * 2
x = 2 + col * 5
if y >= h - 3:
break
s = status.get(c["id"])
if s == 2:
attr = C_GREEN()
label = f"OK "
elif s == 1:
attr = C_YELLOW()
label = f"~~ "
elif s == 0:
attr = C_RED()
label = f"โœ— "
else:
attr = C_NORMAL()
label = f" "
safe_addstr(stdscr, y, x, f"{c['id']:02d}", attr | curses.A_BOLD)
safe_addstr(stdscr, y+1, x, label, attr)
# Rรฉsumรฉ par chapitre
y_sum = h - 8
safe_addstr(stdscr, y_sum, 2, "Par chapitre :", C_TITLE())
for ch in ["3", "4", "5", "synth"]:
ch_cards = [c for c in CARDS if c["ch"] == ch]
ch_good = sum(1 for c in ch_cards if status.get(c["id"]) == 2)
ch_ok = sum(1 for c in ch_cards if status.get(c["id"]) == 1)
line = f"{CH_LABELS[ch]:20s} โœ“{ch_good:3d} ~{ch_ok:3d} /{len(ch_cards)}"
safe_addstr(stdscr, y_sum+1 + list(["3","4","5","synth"]).index(ch),
4, line, ch_color(ch))
safe_addstr(stdscr, h-1, 2, "Q : revenir au menu", C_MAGENTA())
k = stdscr.getch()
if k == ord('q') or k == ord('Q'):
break
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Rร‰INITIALISATION
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def confirm_reset(stdscr, status):
stdscr.clear()
h, w = stdscr.getmaxyx()
draw_header(stdscr, "Rร‰INITIALISATION")
safe_addstr(stdscr, 5, 4, "โš  Effacer toute la progression ?", C_RED())
safe_addstr(stdscr, 7, 4, "[ O ] Oui, effacer [ N ] Annuler", C_NORMAL())
k = stdscr.getch()
if k in (ord('o'), ord('O')):
status.clear()
if os.path.exists(SAVE_FILE):
os.remove(SAVE_FILE)
safe_addstr(stdscr, 9, 4, "Progression effacรฉe.", C_GREEN())
stdscr.refresh()
curses.napms(1000)
return status
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# MAIN
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
def main(stdscr):
curses.curs_set(0)
stdscr.keypad(True)
init_colors()
status = load_progress()
while True:
choice = menu_screen(stdscr, status)
if choice == "1":
status = flashcard_screen(stdscr, status, CARDS[:], "Flashcards โ€” Tout")
elif choice == "2":
cards = [c for c in CARDS if c["ch"] == "3"]
status = flashcard_screen(stdscr, status, cards, "Flashcards โ€” CH.3")
elif choice == "3":
cards = [c for c in CARDS if c["ch"] == "4"]
status = flashcard_screen(stdscr, status, cards, "Flashcards โ€” CH.4")
elif choice == "4":
cards = [c for c in CARDS if c["ch"] == "5"]
status = flashcard_screen(stdscr, status, cards, "Flashcards โ€” CH.5")
elif choice == "5":
cards = [c for c in CARDS if c["ch"] == "synth"]
status = flashcard_screen(stdscr, status, cards, "Flashcards โ€” Synthรจse")
elif choice == "6":
cards = [c for c in CARDS if status.get(c["id"]) in (None, 0)]
if not cards:
cards = CARDS[:]
status = flashcard_screen(stdscr, status, cards, "Flashcards โ€” ร€ revoir")
elif choice == "7":
status = quiz_screen(stdscr, status)
elif choice == "8":
recap_screen(stdscr, status)
elif choice == "9":
map_screen(stdscr, status)
elif choice == "0":
status = confirm_reset(stdscr, status)
elif choice in ("Q", "q"):
break
save_progress(status)
if __name__ == "__main__":
curses.wrapper(main)