Spaces:
Sleeping
Sleeping
| #!/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) |