Clique pour ouvrir · Ctrl+F ou la barre ci-dessous
🔍
Aucun résultat pour cette recherche.
Kotlin — Bases du langage
data class
Équivalent d'un record Java — génère equals, hashCode, copy, toString automatiquement
kotlin
Une data class est une classe dont le seul but est de stocker des données. Kotlin génère automatiquement equals(), hashCode(), toString() et copy().
kotlin
// Déclaration
data class BullsAndCows(val bulls: Int, val cows: Int, val inexistent: Int)
data class Fish(val filename: String) {
    val fishName: String
        get() = filename.substringBeforeLast(".") // propriété calculée
}

// Utilisation
val result = BullsAndCows(bulls = 2, cows = 1, inexistent = 1)
val copy = result.copy(bulls = 4) // copie avec modification
val (b, c, i) = result // destructuring
Les val dans le constructeur sont immutables. Utilise var si tu veux modifier après création.
companion object
Équivalent des méthodes static Java — méthodes de fabrique, constantes de classe
kotlin
Le companion object est un objet singleton attaché à la classe. C'est là où on met les méthodes qu'on appellerait normalement static en Java. Très utilisé pour les méthodes de fabrique (factory) comme buildFromResource ou loadFishes.
kotlin
class LetterPicker(private val freqs: Map<Char, Int>) {

    fun pickLetters(n: Int): String { /* ... */ }

    companion object {
        // Méthode de fabrique — appelée comme LetterPicker.buildFromResource(...)
        fun buildFromResource(context: Context, resourceId: Int): LetterPicker {
            val map = mutableMapOf<Char, Int>()
            context.resources.openRawResource(resourceId)
                .bufferedReader().useLines { lines ->
                    lines.forEach { line ->
                        // parse "A 100" → map['A'] = 100
                    }
                }
            return LetterPicker(map)
        }

        // Pour Fish : charger depuis les assets
        fun loadFishes(context: Context): List<Fish> {
            return context.assets.list("fishes")
                ?.map { Fish(it) } ?: emptyList()
        }
    }
}

// Appel depuis l'extérieur
val picker = LetterPicker.buildFromResource(this, R.raw.frequencies)
typealias
Créer un synonyme pour un type existant
kotlin
Permet de renommer un type pour plus de clarté. Le type reste le même, c'est juste un alias.
kotlin
typealias Dictionary = List<String>

// Utilisation : Dictionary se comporte exactement comme List<String>
fun loadDictionary(): Dictionary = listOf("abc", "def").sorted()
Fonctions d'extension
Ajouter des méthodes à une classe existante sans la modifier
kotlin
Une fonction d'extension s'écrit avec NomDuType.maFonction(). À l'intérieur, this désigne l'objet sur lequel elle est appelée. Très utilisé dans les TPs pour String.containsAnagram, Context.loadDictionary, etc.
kotlin
// Extension sur String
fun String.containsAnagram(proposition: String): Boolean {
    val letters = this.toMutableList()
    for (c in proposition) {
        if (!letters.remove(c)) return false
    }
    return true
}

// Extension sur List générique
fun <T> List<T>.pickRandomElements(n: Int): List<T> =
    this.shuffled().take(n)

// Extension sur Context
fun Context.loadDictionary(assetPath: String): Dictionary {
    val inputStream = assets.open(assetPath)
    return inputStream.bufferedReader().readLines().sorted()
}

// Appel : s'utilise comme une méthode normale
"ESLRVEIOA".containsAnagram("VALORISEE") // true
listOf(1,2,3,4,5).pickRandomElements(3) // liste de 3 éléments aléatoires
Lambdas et fonctions d'ordre supérieur
Passer des fonctions en paramètre — forEach, map, filter, let
kotlin
En Kotlin, les fonctions sont des valeurs qu'on peut passer en paramètre. Le type (Int) -> Unit est une fonction qui prend un Int et ne retourne rien. Dans les composants Compose, les callbacks comme onChange ou onClick sont des lambdas.
kotlin
// Types de fonctions lambda
val f1: () -> Unit = { println("click") }      // pas de param, rien
val f2: (Int) -> Unit = { n -> println(n) }     // 1 param nommé
val f3: (Int) -> Int = { it * 2 }              // it = param implicite
val f4: (List<Int>) -> Unit = { list -> }       // param complexe

// Opérations classiques sur les listes
val nums = listOf(1, 2, 3, 4, 5)
nums.forEach { println(it) }                    // itérer
nums.map { it * 2 }                            // transformer → [2,4,6,8,10]
nums.filter { it > 2 }                         // filtrer → [3,4,5]
nums.indices.forEach { i -> println(nums[i]) } // avec index
nums.shuffled()                                 // mélanger
nums.take(3)                                   // premiers éléments
nums.chunked(2)                               // [[1,2],[3,4],[5]]

// let : applique une lambda sur une valeur nullable
someValue?.let { v -> println(v) }

// Passer lambda en dernier paramètre (trailing lambda)
fun doSomething(x: Int, action: (Int) -> Unit) { action(x) }
doSomething(5) { v -> println(v) } // lambda hors des parenthèses
Collections : List, Set, Map
Listes, ensembles, maps — mutable vs immutable
kotlin
Immutable = on ne peut pas modifier après création. Mutable = on peut modifier. Dans Compose, préfère les immutables pour les états et crée des copies pour les modifier.
kotlin
// List
val l1 = listOf(1, 2, 3)           // immutable
val l2 = mutableListOf(1, 2, 3)    // mutable
val l3 = l1.toMutableList()         // copie mutable
val l4 = l1 + listOf(4, 5)        // concaténation → nouvelle liste
val l5 = l1 + l1[0]               // ajouter 1 élément → nouvelle liste

// Set (pas de doublons)
val s1 = setOf(1, 2, 3)
val s2 = mutableSetOf<Fish>()
s2.add(fish); s2.remove(fish); fish in s2 // contient

// Map
val m1 = mapOf('A' to 100, 'B' to 50)
val m2 = mutableMapOf<Char, Int>()
m2['A'] = 100; m2.getOrDefault('Z', 0)

// Itérer une map
m1.forEach { (letter, freq) -> println("$letter: $freq") }

// BooleanArray (tableau primitif rapide)
val visibility = BooleanArray(20) { false }
visibility[5] = true
val newVisibility = visibility.copyOf() // copie (important pour Compose!)
Dans Compose, Compose ne détecte pas les modifications internes d'un tableau. Toujours créer une copie : array.copyOf() puis réaffecter.
Null safety — ?, ?., ?:, !!
Gérer les valeurs potentiellement nulles en Kotlin
kotlin
kotlin
var name: String? = null      // nullable (avec ?)
var name2: String = "ok"       // non-nullable (jamais null)

name?.length                    // safe call : null si name est null
name?.length ?: 0               // elvis : 0 si null
name!!.length                   // force (crash si null !)
name?.let { println(it) }       // exécute le bloc si non-null

// Depuis assets.list() qui retourne Array<String>?
context.assets.list("fishes")?.map { Fish(it) } ?: emptyList()
Coroutines Kotlin
suspend, delay, withContext — pour les opérations asynchrones
kotlinétat
Les coroutines permettent de faire des opérations asynchrones (réseau, fichiers, temporisations) sans bloquer l'UI. Dans Compose on les lance avec LaunchedEffect.
kotlin
// Fonction suspend = peut être suspendue sans bloquer le thread
suspend fun loadImageFromAssets(context: Context, path: String): ImageBitmap {
    return withContext(Dispatchers.IO) { // changer de thread (IO pour fichiers)
        context.assets.open(path).use {
            BitmapFactory.decodeStream(it).asImageBitmap()
        }
    }
}

// Dans LaunchedEffect — boucle avec delay (ex: barre de chargement)
LaunchedEffect(Unit) {
    var elapsed = 0L
    while (elapsed < duration) {
        delay(20L)            // attendre 20ms
        elapsed += 20L
        fillRatio = elapsed.toFloat() / duration
    }
    onLoaded()                // prévenir le parent
}
Jetpack Compose — Bases
@Composable — Créer un composant
Signature d'un composant, Modifier, @Preview
composeexam
Toute fonction annotée @Composable est un composant. Convention : le dernier paramètre est toujours modifier: Modifier = Modifier, et on le passe au composant racine de la fonction.
kotlin
// Composant simple (stateless)
@Composable
fun DigitDisplayer(value: Int, fontSize: TextUnit, modifier: Modifier = Modifier) {
    Text(
        text = value.toString(16), // affichage hexa
        fontSize = fontSize,
        modifier = modifier
            .background(colorForDigit(value))
            .padding(4.dp)
    )
}

// Preview — composant sans argument pour prévisualiser
@Preview @Composable
fun DigitDisplayerPreview() {
    Column {
        repeat(2) {
            Row { (0..15).forEach { DigitDisplayer(it, 30.sp) } }
        }
    }
}

// Appel dans onCreate (activité)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        GameManager() // composant racine
    }
}
Text — Afficher du texte
fontSize, fontWeight, color, textAlign
composeui
kotlin
Text(
    text = "Bonjour !",
    fontSize = 24.sp,
    fontWeight = FontWeight.Bold,
    color = Color.White,
    textAlign = TextAlign.Center,
    modifier = Modifier.fillMaxWidth()
)

// Avec une variable d'état
Text(text = "Compteur : $counter")
Text(text = "${result.bulls} 🐂 ${result.cows} 🐄")
Text(text = "?".repeat(codeLength), ...) // afficher ???
Button — Bouton cliquable
onClick, enabled, contenu via trailing lambda
composeui
kotlin
Button(
    onClick = { counter++ },
    enabled = !validated,        // désactivé si validé
    modifier = Modifier.fillMaxWidth()
) {
    Text("Valider")
}

// TextButton (sans fond)
TextButton(onClick = { onValidation(result) }) {
    Text("???", fontSize = fontSize)
}
Image — Afficher une image
Depuis drawable ou depuis les assets
composeui
kotlin
// Depuis res/drawable
Image(
    painter = painterResource(id = R.drawable.goldroid),
    contentDescription = "Goldroid",
    contentScale = ContentScale.Fit,
    modifier = Modifier.fillMaxSize()
)

// Depuis les assets (chargement asynchrone)
@Composable
fun getAssetImage(path: String): ImageBitmap? {
    val context = LocalContext.current
    var bitmap by remember { mutableStateOf<ImageBitmap?>(null) }
    LaunchedEffect(path) {
        bitmap = loadImageFromAssets(context, path)
    }
    return bitmap
}

// Utilisation dans FishDisplayer
val img = getAssetImage("fishes/${fish.filename}")
if (img != null) {
    Image(bitmap = img, contentDescription = fish.fishName, ...)
}
Slider — Curseur de sélection
Sélectionner une valeur dans une plage
composeui
kotlin
var nbPairs by remember { mutableFloatStateOf(8f) }

Slider(
    value = nbPairs,
    onValueChange = { nbPairs = it },
    valueRange = 2f..20f,
    steps = 17,             // nb de crans intermédiaires
    modifier = Modifier.fillMaxWidth()
)
Text("${nbPairs.toInt()} paires")
Layouts — Mise en page
Row et Column — Disposition linéaire
Horizontal (Row) ou vertical (Column), avec alignment et arrangement
composelayoutexam
kotlin
// Column = empilement vertical
Column(
    modifier = Modifier.fillMaxSize(),
    verticalArrangement = Arrangement.SpaceBetween,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Text("En haut")
    Text("Au milieu")
    Text("En bas")
}

// Row = disposition horizontale
Row(
    modifier = Modifier.fillMaxWidth(),
    horizontalArrangement = Arrangement.SpaceBetween,
    verticalAlignment = Alignment.CenterVertically
) {
    Text("Gauche")
    Spacer(Modifier.weight(1f)) // prend tout l'espace disponible
    Text("Droite")
}

// Arrangement options
// Arrangement.Start / End / Center / SpaceBetween / SpaceAround / SpaceEvenly
// Alignment.Start / End / CenterHorizontally / CenterVertically / Top / Bottom
Box — Superposition d'éléments
Empiler des composants les uns par-dessus les autres
composelayout
Utilisé pour superposer une légende sur une image, ou positionner des éléments en absolu dans un espace.
kotlin
Box(
    modifier = Modifier.fillMaxSize().clickable { onClick() }
) {
    // Arrière-plan
    Image(painter = painterResource(R.drawable.goldroid), ...)

    // Avant-plan — positionné en bas
    Text(
        text = message,
        modifier = Modifier
            .align(Alignment.BottomCenter) // positionner dans la Box
            .alpha(0.7f)
            .background(Color.Black)
            .fillMaxWidth()
    )
}
BoxWithConstraints — Connaître la taille disponible
Calculer le nombre de colonnes/lignes d'une grille selon l'espace disponible
composelayoutexam
Utilisé dans GoldroidGrid et FishGrid pour calculer combien de poissons mettre par rangée selon la largeur de l'écran.
kotlin
BoxWithConstraints(
    modifier = Modifier.fillMaxSize(),
    contentAlignment = Alignment.Center
) {
    val maxWidth = this.maxWidth
    val maxHeight = this.maxHeight
    val fishSize = 300.dp
    val nbCols = (maxWidth / fishSize).toInt().coerceAtLeast(1)
    val nbRows = (maxHeight / fishSize).toInt().coerceAtLeast(1)

    Column {
        repeat(nbRows) { row ->
            Row {
                repeat(nbCols) { col ->
                    FishDisplayer(
                        fish = fishes[row * nbCols + col],
                        modifier = Modifier.size(fishSize)
                    )
                }
            }
        }
    }
}
LazyColumn — Liste défilante
Équivalent RecyclerView — charge les items paresseusement
composelayoutexam
Utilise items() pour afficher une liste. L'avantage sur Column : seuls les items visibles sont rendus. Supporte le défilement nativement.
kotlin
LazyColumn(modifier = Modifier.fillMaxSize()) {
    // items() avec une liste
    items(fishes) { fish ->
        FishDisplayer(fish = fish)
    }

    // items avec chunked (pour une grille défilante)
    items(fishes.chunked(nbCols)) { fishGroup ->
        Row {
            fishGroup.forEach { fish ->
                FishWithAnagramDisplayer(
                    fish = fish,
                    modifier = Modifier.size(fishSize)
                )
            }
        }
    }

    // item() pour un élément unique (ex: en-tête)
    item { Text("En-tête", fontWeight = FontWeight.Bold) }
}

// Column scrollable (alternative simple)
Column(Modifier.verticalScroll(rememberScrollState())) {
    // ... items
}
Modifier — Styliser et positionner
padding, size, background, weight, clickable, alpha…
composelayoutexam
Le Modifier est chaînable : l'ordre compte ! padding avant background met le padding à l'intérieur du fond coloré, après = à l'extérieur.
kotlin
Modifier
    .fillMaxSize()           // prend tout l'espace parent
    .fillMaxWidth()          // toute la largeur
    .fillMaxHeight()         // toute la hauteur
    .size(300.dp)           // largeur + hauteur fixe
    .width(200.dp)          // largeur fixe
    .height(50.dp)          // hauteur fixe
    .weight(1f)             // dans Row/Column: prend 1 part de l'espace
    .weight(2f)             // prend 2 parts (le double)
    .padding(8.dp)           // marges intérieures
    .padding(horizontal = 16.dp, vertical = 4.dp)
    .background(Color.Blue)  // couleur de fond
    .background(Color(0xFF123456)) // couleur hexa
    .clickable { doSomething() }   // rend cliquable
    .alpha(0.5f)            // transparence (0=invisible, 1=opaque)
    .border(2.dp, Color.Black)
    .aspectRatio(1f)        // carré
    .aspectRatio(16f/9f)   // 16:9
    .wrapContentSize()       // taille minimale

// Gestion des clics longs — pour incrémentation continue
Modifier.pointerInput(Unit) {
    detectTapGestures(
        onTap = { onChange((value + 1) % base) },
        onLongPress = { /* ... */ }
    )
}
Gestion de l'état
remember + mutableStateOf — État local
Mémoriser une valeur entre les recompositions
composeétatexam
remember conserve la valeur entre les recompositions. Sans remember, la variable serait réinitialisée à chaque recomposition. mutableStateOf rend la valeur observable : quand elle change, Compose recompose.
kotlin
// Syntaxe de base
val counter by remember { mutableStateOf(0) }
// ou sans "by" (besoin de .value alors)
val counter = remember { mutableStateOf(0) }
counter.value++; println(counter.value)

// Variants optimisés
var count by remember { mutableIntStateOf(0) }
var ratio by remember { mutableFloatStateOf(0f) }
var letters by remember { mutableStateOf<List<Char>>(emptyList()) }
var bitmap by remember { mutableStateOf<ImageBitmap?>(null) }

// Exemple complet dans GoldroidCounter
@Composable
fun GoldroidCounter(modifier: Modifier = Modifier) {
    var counter by remember { mutableIntStateOf(0) }
    var startTime by remember { mutableStateOf<Long?>(null) }

    GoldroidDisplayer(
        message = "Clics : $counter",
        onClick = {
            if (startTime == null) startTime = SystemClock.elapsedRealtime()
            counter++
        },
        modifier = modifier.alpha(1f - counter / 100f)
    )
}
derivedStateOf — État calculé
Calculer un état à partir d'autres états (évite les recompositions inutiles)
composeétat
Utilisé dans MemoryBoardManager pour calculer le tableau de visibilité des cartes à partir des ensembles "paires trouvées" et "cartes retournées".
kotlin
var foundPairs by remember { mutableStateOf(setOf<Fish>()) }
var flippedCards by remember { mutableStateOf(setOf<MemoryCard>()) }

// Recalculé automatiquement quand foundPairs ou flippedCards changent
val visibility by remember {
    derivedStateOf {
        BooleanArray(cards.size) { i ->
            cards[i].fish in foundPairs || cards[i] in flippedCards
        }
    }
}
LaunchedEffect — Effets et coroutines
Exécuter du code asynchrone quand un état change
composeétatexam
Se lance quand les clés changent. Avec Unit comme clé = une seule fois au démarrage. Avec une variable comme clé = se relance à chaque changement de cette variable.
kotlin
// Une seule fois au démarrage
LaunchedEffect(Unit) {
    bitmap = loadImageFromAssets(context, path)
}

// Se relance si "path" change
LaunchedEffect(path) {
    bitmap = loadImageFromAssets(context, path)
}

// Barre de chargement animée
LaunchedEffect(Unit) {
    var elapsed = 0L
    while (elapsed < duration) {
        delay(20L)
        elapsed += 20L
        fillRatio = elapsed.toFloat() / duration
    }
    onLoaded()
}

// Temporisation pour cacher les cartes mémoire
LaunchedEffect(flippedCards) {
    if (flippedCards.size == 2) {
        val (c1, c2) = flippedCards.toList()
        if (c1.fish == c2.fish) {
            foundPairs = foundPairs + c1.fish  // trouvé !
            flippedCards = emptySet()
        } else {
            delay(2000L)                        // attendre 2s
            flippedCards = emptySet()           // remasquer
        }
    }
}
Pour "relancer" une LaunchedEffect (ex: réinitialiser la barre), change sa clé — par exemple passe un entier sessionId qui s'incrémente.
rememberSaveable / ViewModel — Survivre à la rotation
Conserver l'état quand l'écran tourne
composeétat
kotlin
// Option 1 : rememberSaveable (objets Serializable seulement)
var score by rememberSaveable { mutableIntStateOf(0) }

// Rendre une data class Serializable
@Serializable
data class MemoryCard(val fish: Fish, val sample: Int) : Serializable

// Option 2 : ViewModel (recommandé)
class BoardViewModel : ViewModel() {
    var foundPairs by mutableStateOf(setOf<Fish>())
    var attempts by mutableIntStateOf(0)
}

// Dans le composant — viewModel est recréé si besoin ou récupéré
@Composable
fun MemoryBoardManager(
    viewModel: BoardViewModel = viewModel()
) {
    // viewModel.foundPairs, viewModel.attempts...
}
key { } — Contrôler l'identité des composants
Éviter les recompositions inutiles lors d'un shuffle
composeétat
Par défaut, Compose identifie les composants par leur position dans la boucle. En utilisant key, on donne une identité unique → Compose déplace le composant au lieu de le recomposer.
kotlin
// Sans key : position 0 reste LetterDisplayer 0, même si lettre change
letters.forEach { letter ->
    LetterDisplayer(letter = letter)
}

// Avec key : chaque LetterDisplayer est lié à un identifiant unique
// (ici un index car la même lettre peut apparaître plusieurs fois)
letters.forEachIndexed { index, (letter, uniqueId) ->
    key(uniqueId) {
        LetterDisplayer(letter = letter) // conserve son état (couleur) lors du shuffle
    }
}
Patterns architecturaux
Stateless vs Stateful — Hoisting d'état
Le composant affiche, le parent gère l'état — pattern fondamental
composepatternexam
Dans les TPs, tous les Selector et Displayer sont stateless : ils reçoivent la valeur et notifient le parent via un callback. Le parent appelle onChange et met à jour son état, ce qui provoque une recomposition.
kotlin
// STATELESS : composant "bête" — affiche ce qu'on lui donne
@Composable
fun DigitSelector(
    value: Int,
    base: Int,
    fontSize: TextUnit,
    onChange: (Int) -> Unit  // callback vers le parent
) {
    DigitDisplayer(
        value = value,
        modifier = Modifier.clickable { onChange((value + 1) % base) }
    )
}

// STATEFUL : composant "intelligent" — gère son propre état
@Composable
fun CodeSelectorParent() {
    var code by remember { mutableStateOf(listOf(0, 0, 0, 0)) }

    CodeSelector(
        code = code,
        base = 10,
        fontSize = 30.sp,
        onChange = { newCode -> code = newCode } // mise à jour état
    )
}

// Pattern CodeSelector
@Composable
fun CodeSelector(
    code: List<Int>,
    base: Int,
    fontSize: TextUnit,
    onChange: (List<Int>) -> Unit
) {
    Row {
        code.forEachIndexed { index, digit ->
            DigitSelector(
                value = digit,
                base = base,
                fontSize = fontSize,
                onChange = { newVal ->
                    val newCode = code.toMutableList()
                    newCode[index] = newVal
                    onChange(newCode) // propager au parent avec nouvelle liste
                }
            )
        }
    }
}
CompositionLocal — Partager des données dans l'arbre
Éviter de passer base/fontSize à chaque composant
composepattern
kotlin
// Définir la data class et le local
data class GameConfig(val base: Int, val fontSize: TextUnit)

val LocalGameConfig = compositionLocalOf { GameConfig(10, 24.sp) }

// Fournir la valeur dans un composant parent
CompositionLocalProvider(LocalGameConfig provides GameConfig(base, fontSize)) {
    GameGrid(sessionId = sessionId, codeLength = codeLength, onEnd = onEnd)
}

// Lire la valeur dans n'importe quel composant enfant
val config = LocalGameConfig.current
Text("Base : ${config.base}", fontSize = config.fontSize)
Orientation portrait / paysage
Adapter l'UI selon l'orientation de l'écran
composepatternexam
Dans le sujet de l'examen, l'écran de fin doit afficher les éléments côte à côte en paysage et empilés en portrait.
kotlin
val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE

if (isLandscape) {
    Row(Modifier.fillMaxSize()) {
        CowImage(Modifier.weight(1f))
        ResultText(Modifier.weight(1f))
    }
} else {
    Column(Modifier.fillMaxSize()) {
        CowImage(Modifier.weight(0.6f))
        ResultText(Modifier.weight(0.4f))
    }
}
Machine à états — Gérer les écrans du jeu
Alterner entre chargement, jeu, fin de partie
composepatternexam
Pattern utilisé dans GameManager et MemoryGame pour afficher différents composants selon la phase du jeu.
kotlin
enum class GamePhase { LOADING, SETTINGS, PLAYING, WON, LOST }
// ou sealed class pour transporter des données
sealed class GameState {
    object Loading : GameState()
    object Playing : GameState()
    data class End(val won: Boolean, val time: Long) : GameState()
}

@Composable
fun GameManager(modifier: Modifier = Modifier) {
    var phase by remember { mutableStateOf<GameState>(GameState.Loading) }
    var sessionId by remember { mutableIntStateOf(0) }

    when (val p = phase) {
        is GameState.Loading ->
            GoldroidLoadScreen(onLoaded = { phase = GameState.Playing })

        is GameState.Playing ->
            GameGrid(
                sessionId = sessionId,
                codeLength = CODE_LENGTH,
                onEnd = { won, time -> phase = GameState.End(won, time) }
            )

        is GameState.End ->
            EndScreen(
                won = p.won,
                elapsedTime = p.time,
                onRestart = {
                    sessionId++   // nouvelle partie
                    phase = GameState.Playing
                }
            )
    }
}
Spécifique examen — Composants clés du sujet AN dernier
DigitDisplayer — Afficher un chiffre coloré
Couleur de fond liée au chiffre, affichage hexa
composeexam
1er exercice de l'ancien sujet (/3). Afficher un chiffre (0–15) en hexa avec couleur de fond déterministe selon la valeur.
kotlin
// Tableau de couleurs — 1 couleur par chiffre 0..15
val DIGIT_COLORS = listOf(
    Color(0xFFE74C3C), Color(0xFFE67E22), Color(0xFFF1C40F), Color(0xFF2ECC71),
    Color(0xFF1ABC9C), Color(0xFF3498DB), Color(0xFF9B59B6), Color(0xFF34495E),
    Color(0xFF95A5A6), Color(0xFFD35400), Color(0xFF27AE60), Color(0xFF16A085),
    Color(0xFF2980B9), Color(0xFF8E44AD), Color(0xFF2C3E50), Color(0xFF7F8C8D)
)

@Composable
fun DigitDisplayer(
    value: Int,
    fontSize: TextUnit,
    modifier: Modifier = Modifier
) {
    Box(
        modifier = modifier
            .background(DIGIT_COLORS[value])
            .padding(4.dp),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = value.toString(16),  // hexa : 0→"0", 10→"a", 15→"f"
            fontSize = fontSize,
            color = Color.White
        )
    }
}
pickSecretCode — Tirer un code sans répétition
Générer une liste de chiffres uniques tirés au sort
kotlinexam
Exercice du sujet (/3). Code secret de n chiffres distincts entre 0 et base-1. La version courte tient en 42 caractères.
kotlin
// Version complète
fun pickSecretCode(n: Int, base: Int): List<Int> {
    require(n <= base) { "n must be <= base" }
    return (0 until base).shuffled().take(n)
}

// Version courte (42 caractères dans le body) :
fun pickSecretCode(n: Int, base: Int) =
    (0 until base).shuffled().take(n)
CodeProposer — Saisir et valider une proposition
Affiche CodeSelector + bouton validation + résultat taureaux/vaches
composeexam
Exercice (/3). Composant stateless : si validated=false → bouton "???". Après validation → affiche 🐂🐄🚫.
kotlin
@Composable
fun CodeProposer(
    referenceCode: List<Int>,
    proposedCode: List<Int>,
    base: Int,
    fontSize: TextUnit,
    validated: Boolean,
    onProposedCodeChange: (List<Int>) -> Unit,
    onValidation: (BullsAndCows) -> Unit
) {
    Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
        // Sélecteur de code à gauche (prend tout l'espace restant)
        CodeSelector(
            code = proposedCode,
            base = base,
            fontSize = fontSize,
            onChange = if (!validated) onProposedCodeChange else { _ -> },
            modifier = Modifier.weight(1f)
        )

        // À droite : bouton ou résultat
        if (!validated) {
            TextButton(onClick = {
                val result = computeBullsAndCows(referenceCode, proposedCode)
                onValidation(result)
            }) {
                Text("???", fontSize = fontSize)
            }
        } else {
            val result = computeBullsAndCows(referenceCode, proposedCode)
            Text(
                text = "${"🐂".repeat(result.bulls)}${"🐄".repeat(result.cows)}${"🚫".repeat(result.inexistent)}",
                fontSize = fontSize
            )
        }
    }
}
GameGrid — Grille de jeu Bulls & Cows
Liste scrollable de CodeProposer + chronomètre + bouton abandon
composeétatexam
Exercice (/4). sessionId change → nouvelle partie. Chrono en temps réel. Bouton abandon. LazyColumn scrollable.
kotlin
@Composable
fun GameGrid(
    sessionId: Int,
    codeLength: Int,
    onEnd: (Boolean, Long) -> Unit  // won, elapsedMs
) {
    val base = 10
    // Relancer à chaque nouveau sessionId
    val secretCode = remember(sessionId) { pickSecretCode(codeLength, base) }
    val startTime = remember(sessionId) { SystemClock.elapsedRealtime() }

    // Liste des propositions : liste de (code, validated, result?)
    var proposals by remember(sessionId) {
        mutableStateOf(listOf(listOf(0, 0, 0, 0)))
    }
    var validated by remember(sessionId) { mutableStateOf(listOf(false)) }

    // Chronomètre en temps réel
    var elapsed by remember { mutableLongStateOf(0L) }
    LaunchedEffect(sessionId) {
        while (true) {
            delay(1000L)
            elapsed = (SystemClock.elapsedRealtime() - startTime) / 1000L
        }
    }

    Column(Modifier.fillMaxSize()) {
        Row(Modifier.fillMaxWidth()) {
            Text("⏱ ${elapsed}s", Modifier.weight(1f))
            Button(onClick = {
                onEnd(false, SystemClock.elapsedRealtime() - startTime)
            }) { Text("Abandonner") }
        }

        LazyColumn(Modifier.weight(1f)) {
            itemsIndexed(proposals) { index, code ->
                CodeProposer(
                    referenceCode = secretCode,
                    proposedCode = code,
                    base = base, fontSize = 24.sp,
                    validated = validated[index],
                    onProposedCodeChange = { newCode ->
                        proposals = proposals.toMutableList().also { it[index] = newCode }
                    },
                    onValidation = { result ->
                        validated = validated.toMutableList().also { it[index] = true }
                        if (result.bulls == codeLength) {
                            onEnd(true, SystemClock.elapsedRealtime() - startTime)
                        } else {
                            proposals = proposals + listOf(List(codeLength) { 0 })
                            validated = validated + listOf(false)
                        }
                    }
                )
            }
        }
    }
}
LoadBar / barre de progression
Barre qui se remplit progressivement avec weight
composeui
kotlin
@Composable
fun LoadBar(
    fillRatio: Float,           // entre 0f et 1f
    backgroundColor: Color = Color.White,
    foregroundColor: Color = Color.Blue,
    modifier: Modifier = Modifier
) {
    Row(modifier) {
        // Partie remplie
        if (fillRatio > 0f)
            Box(Modifier.weight(fillRatio).fillMaxHeight().background(foregroundColor))
        // Partie vide
        if (fillRatio < 1f)
            Box(Modifier.weight(1f - fillRatio).fillMaxHeight().background(backgroundColor))
    }
}

// GoldroidLoadScreen avec animation
@Composable
fun GoldroidLoadScreen(
    duration: Long = 5000L,
    onLoaded: () -> Unit,
    modifier: Modifier = Modifier
) {
    var fillRatio by remember { mutableFloatStateOf(0f) }

    LaunchedEffect(Unit) {
        var elapsed = 0L
        while (elapsed < duration) {
            delay(20L)
            elapsed += 20L
            fillRatio = elapsed.toFloat() / duration
        }
        onLoaded()
    }

    Column(modifier.fillMaxSize()) {
        Image(painterResource(R.drawable.goldroid), "loading",
              modifier = Modifier.weight(0.9f).fillMaxWidth())
        LoadBar(fillRatio = fillRatio,
               modifier = Modifier.weight(0.1f).fillMaxWidth())
    }
}
AmountBar — Barre de quantité verticale
Barre verticale remplie de bas en haut selon une valeur 0–1
composeuiexam
Exercice ♣ (/3). Barre verticale blanche remplie en noir de bas en haut.
kotlin
@Composable
fun AmountBar(value: Float, modifier: Modifier = Modifier) {
    Column(
        modifier
            .background(Color.White)
            .border(1.dp, Color.Gray)
    ) {
        // Partie vide en haut
        if (value < 1f)
            Box(Modifier.weight(1f - value).fillMaxWidth().background(Color.White))
        // Partie remplie en bas
        if (value > 0f)
            Box(Modifier.weight(value).fillMaxWidth().background(Color.Black))
    }
}
Divers — Utilitaires
Toast — Message court
Afficher une notification temporaire
ui
kotlin
val context = LocalContext.current

// Dans un callback (pas dans la composition directement)
Toast.makeText(context, "Bravo !", Toast.LENGTH_SHORT).show()

// Dans un bouton par exemple
Button(onClick = {
    Toast.makeText(context, "Félicitations !", Toast.LENGTH_LONG).show()
}) { Text("Valider") }
Intent — Lancer une autre activité
Passer des données à une autre Activity
kotlinpattern
kotlin
// Lancer une activité avec des données
fun startAnagramActivity(context: Context, fish: Fish) {
    val intent = Intent()
    intent.setClassName("com.example.myapp", "com.example.myapp.AnagramActivity")
    intent.putExtra("word", fish.fishName)
    intent.putExtra("anagram", fish.fishName.toCharArray().toMutableList().shuffled()
        .joinToString(""))
    try {
        context.startActivity(intent)
    } catch (e: Exception) {
        Toast.makeText(context, "App non trouvée", Toast.LENGTH_SHORT).show()
    }
}

// Lire les données dans l'activité de destination (dans onCreate)
val word = intent.getStringExtra("word") ?: "default"
val anagram = intent.getStringExtra("anagram") ?: ""
Générer une couleur aléatoire
Couleurs pastel (composantes R,G,B entre 128–255)
kotlincompose
kotlin
fun pickRandomColor(): Color {
    val r = (128..255).random()
    val g = (128..255).random()
    val b = (128..255).random()
    return Color(r, g, b)
}

// Dans LetterDisplayer — couleur fixée à l'installation
val backgroundColor by remember { mutableStateOf(pickRandomColor()) }
Mesurer le temps écoulé
SystemClock.elapsedRealtime() pour chronomètre
kotlin
kotlin
// Temps depuis le démarrage du système (en ms) — ne se réinitialise pas
val start = SystemClock.elapsedRealtime()
// ... plus tard
val elapsed = SystemClock.elapsedRealtime() - start  // en millisecondes
val elapsedSeconds = elapsed / 1000L

// Stocker comme état nullable (null = pas encore commencé)
var startTime by remember { mutableStateOf<Long?>(null) }
// Au premier clic :
if (startTime == null) startTime = SystemClock.elapsedRealtime()
Recherche dans une liste triée
binarySearch, binarySearchBy pour trouver rapidement
kotlin
kotlin
// Liste doit être TRIÉE pour binarySearch
val dictionary: List<String> = loadDictionary().sorted()

// binarySearch retourne l'index si trouvé, sinon un nombre négatif
val found = dictionary.binarySearch("VALORISEE") >= 0

// binarySearchBy (pour objets complexes)
val cumFreqs: List<CumulatedFrequency> = /* ... liste triée par value */
val idx = cumFreqs.binarySearchBy(pickedNumber) { it.value }
// Si idx < 0, la lettre est à l'index -(idx+1)
fun List<CumulatedFrequency>.findLetter(n: Int): Char {
    val idx = binarySearchBy(n) { it.value }
    return if (idx >= 0) this[idx].letter
    else this[-(idx + 1)].letter
}
Manipulations de String
toCharArray, split, joinToString, substringBeforeLast
kotlin
kotlin
val s = "GOLDROID"
s.toCharArray()                  // → CharArray ['G','O','L','D','R','O','I','D']
s.toList()                       // → List<Char>
s.length                         // → 8
s[0]                             // → 'G'
s.lowercase()                    // → "goldroid"
s.uppercase()                    // → "GOLDROID"
s.substringBeforeLast(".")      // "file.jpeg" → "file"
s.repeat(3)                     // → "GOLDROIDGOLDROIDGOLDROID"
"🐂".repeat(result.bulls)       // → "🐂🐂" si bulls=2
listOf('A', 'B').joinToString("") // → "AB"

// toString(base) pour convertir en hexadécimal
10.toString(16)                   // → "a"
15.toString(16)                   // → "f"
255.toString(16)                  // → "ff"
LocalContext — Obtenir le Context dans Compose
Accéder aux assets, resources, etc. depuis un composant
compose
kotlin
@Composable
fun MonComposant() {
    val context = LocalContext.current

    // Charger les fishes depuis assets
    val fishes = remember {
        context.assets.list("fishes")?.map { Fish(it) } ?: emptyList()
    }

    // Lire une ressource raw
    context.resources.openRawResource(R.raw.frequencies)

    // Lancer une activité
    context.startActivity(intent)
}
var vs val — Variables
val = immuable, var = mutable
kotlin
kotlin
val name = "Goldroid"     // immuable — ne peut pas être réassigné
var count = 0            // mutable — peut être modifié
count++; count = 5

// Type inféré automatiquement
val list = listOf(1, 2, 3) // List<Int> inféré
val map: Map<Char, Int> = mutableMapOf() // type explicite

// Constantes top-level
const val CODE_LENGTH = 4
const val BASE = 10
Setter personnalisé
Exécuter du code lors de l'affectation d'une variable
kotlin
Utilisé dans le TP Anagrammes pour mettre à jour automatiquement le TextView quand bestAnagrams change.
kotlin
var bestAnagrams = listOf<String>()
    set(value) {
        field = value  // "field" = la valeur interne
        // Mise à jour automatique du TextView
        textView.text = value.joinToString("\n")
    }

// Propriété calculée (get uniquement)
val fishName: String
    get() = filename.substringBeforeLast(".")