⚔ MonsterBattles Docs
Phuc PhamGitHub ↗
WEEK 01 · KOTLIN

Kotlin Fundamentals

Leer Kotlin vanaf main() tot en met null safety, functieargumenten en een eerste werkende MonsterBattles-berekening.

31 onderwerpenConsolevoorbeeldenMonsterBattles-prototype

Wat bouwen we deze week?

We maken een zelfstandig uitvoerbaar Kotlin-consoleprogramma met Pyron en Aquara. We bepalen wie begint en simuleren het volledige gevecht met opeenvolgende aanvallen, HP-updates en een winnaar. We gebruiken bewust nog geen classes: die komen in Week 02.

Onze vaste spelregel: damage = maxOf(1, attack - defense). HP komt nooit onder nul. Bij gelijke Speed begint het eerst gekozen monster.

De 31 Kotlin-onderwerpen

De voorbeelden zijn zelfstandige console-demo’s, tenzij een onderwerp uitdrukkelijk als conceptueel is gemarkeerd. De getoonde uitvoer is de verwachte uitvoer; een daadwerkelijk uitgevoerd testresultaat wordt afzonderlijk vermeld.

01Program entry point: main()

Wat is het? Een zelfstandig Kotlin-consoleprogramma begint doorgaans bij main(). De JVM roept dit startpunt aan.

Hoe werkt het? Een top-level fun main() is het entrypoint van een console-app; de statements in het functieblok worden in volgorde uitgevoerd.

Main.ktKOTLIN
fun main() {
    // Start the demonstration program.
    println("MonsterBattles started")
}
▸ Terminal / outputVerwachte uitvoer
MonsterBattles started

Waarom deze aanpak? We starten kleine demo’s zonder de Ktor-server te hoeven opstarten.

Wanneer anders? Voor serverprogramma’s gebruikt Ktor een Application-module; die introduceren we pas in Week 04.

Toepassing in MonsterBattles: We starten kleine demo’s zonder de Ktor-server te hoeven opstarten.

02Output: print() en println()

Wat is het? print() schrijft zonder nieuwe regel; println() voegt een regeleinde toe.

Hoe werkt het? print schrijft zonder afsluitend regeleinde. println schrijft een waarde en daarna een regeleinde. Het tweede argument is hier niet nodig.

Output.ktKOTLIN
fun main() {
    print("Monster: ")
    println("Pyron")
    println("Ready to battle")
}
▸ Terminal / outputVerwachte uitvoer
Monster: Pyron
Ready to battle

Waarom deze aanpak? Een leesbare battlelog vraagt om bewust gebruik van nieuwe regels.

Wanneer anders? Voor gestructureerde output naar een client gebruiken we later JSON in plaats van console-tekst.

Toepassing in MonsterBattles: Een leesbare battlelog vraagt om bewust gebruik van nieuwe regels.

03Packages en imports

Wat is het? Een package groepeert gerelateerde Kotlin-bestanden. Met import gebruik je declaraties uit andere packages.

Hoe werkt het? package staat bovenaan het Kotlin-bestand. Een import maakt een declaratie uit een andere package beschikbaar zonder overal de gekwalificeerde naam te typen.

DamageCalculator.ktKOTLIN
package nl.monsterbattles.demo

import kotlin.math.max

fun main() {
    // max is imported from kotlin.math.
    println(max(1, 18))
}
▸ Terminal / outputVerwachte uitvoer
18

Waarom deze aanpak? Later plaatsen we de damageberekening in een eigen package.

Wanneer anders? Een volledig gekwalificeerde naam kan een import vervangen, maar is bij herhaald gebruik minder leesbaar.

Toepassing in MonsterBattles: Later plaatsen we de damageberekening in een eigen package.

04Variabelen: val en var

Wat is het? val kan niet opnieuw worden toegewezen; var wel. Een val-referentie betekent niet dat de inhoud van een object immutable is.

Hoe werkt het? Bij val mag je de variabele niet opnieuw toewijzen. var maakt nieuwe toewijzingen mogelijk; de variabelen blijven wel statisch getypeerd.

Variables.ktKOTLIN
fun main() {
    val monsterName = "Pyron"
    var currentHp = 100
    currentHp -= 20
    println("$monsterName: $currentHp HP")
}
▸ Terminal / outputVerwachte uitvoer
Pyron: 80 HP

Waarom deze aanpak? De naam van Pyron blijft gelijk, terwijl zijn actuele HP kan veranderen.

Wanneer anders? Gebruik val tenzij de variabele zelf opnieuw moet worden toegewezen; mutable objectinhoud is een afzonderlijke keuze.

Toepassing in MonsterBattles: De naam van Pyron blijft gelijk, terwijl zijn actuele HP kan veranderen.

05Constants: const val

Wat is het? const val legt een compile-time constante vast. Het mag op top-level of in een object/companion object staan.

Hoe werkt het? const val wordt tijdens compilatie vastgelegd. Gebruik het alleen voor toegestane compile-time waarden op top-level of in object/companion object.

Constants.ktKOTLIN
const val GAME_NAME = "MonsterBattles"

fun main() {
    println("Welcome to $GAME_NAME")
}
▸ Terminal / outputVerwachte uitvoer
Welcome to MonsterBattles

Waarom deze aanpak? Een vaste naam of configuratiewaarde die compile-time bekend is, kan hiermee worden gedefinieerd.

Wanneer anders? Voor waarden die pas bij het starten bekend zijn gebruik je val in plaats van const val.

Toepassing in MonsterBattles: Een vaste naam of configuratiewaarde die compile-time bekend is, kan hiermee worden gedefinieerd.

06Basic types

Wat is het? Kotlin heeft onder andere Int, Long, Double, Float, Boolean, Char en String. Typen bepalen welke bewerkingen toegestaan zijn.

Hoe werkt het? Int en Long zijn gehele getallen met verschillende bereiken. Double en Float zijn floating-point types; Char is één teken en String is tekst.

BasicTypes.ktKOTLIN
fun main() {
    val hp: Int = 100
    val totalBattles: Long = 2L
    val ratio: Double = 0.5
    val bonus: Float = 1.5f
    val alive: Boolean = true
    val rank: Char = 'A'
    val name: String = "Pyron"
    println("$name | $hp | $totalBattles | $ratio | $bonus | $alive | $rank")
}
▸ Terminal / outputVerwachte uitvoer
Pyron | 100 | 2 | 0.5 | 1.5 | true | A

Waarom deze aanpak? Monsterstats zijn hele getallen; een naam is tekst.

Wanneer anders? Voor geldbedragen is floating point doorgaans minder geschikt; monsterstats zijn veilig als Int gemodelleerd.

Toepassing in MonsterBattles: Monsterstats zijn hele getallen; een naam is tekst.

07Type inference

Wat is het? De Kotlin-compiler kan vaak het type uit een waarde afleiden. Je kunt het type ook expliciet noteren.

Hoe werkt het? Bij val hp = 100 leidt de compiler Int af. val hp: Int = 100 schrijft exact hetzelfde type expliciet uit.

TypeInference.ktKOTLIN
fun main() {
    val attack = 30            // Inferred as Int.
    val defense: Int = 12     // Explicit Int.
    println(attack - defense)
}
▸ Terminal / outputVerwachte uitvoer
18

Waarom deze aanpak? We houden eenvoudige monsterberekeningen compact zonder typeveiligheid te verliezen.

Wanneer anders? Expliciete typen zijn nuttig bij API-contracten of als de bedoelde numerieke soort anders onduidelijk blijft.

Toepassing in MonsterBattles: We houden eenvoudige monsterberekeningen compact zonder typeveiligheid te verliezen.

08Operators

Wat is het? Rekenoperatoren zijn onder andere +, -, *, / en %. Vergelijkingen produceren een Boolean.

Hoe werkt het? Kotlin rekent eerst de expressie rechts uit en past daarna de toewijzing toe. Bij geheel-getaldeling gaat de fractie verloren.

Operators.ktKOTLIN
fun main() {
    val attack = 30
    val defense = 12
    val rawDamage = attack - defense
    println(rawDamage > 0)
    println(7 % 3)
}
▸ Terminal / outputVerwachte uitvoer
true
1

Waarom deze aanpak? Damage is een aftrekking; HP gebruikt assignment-operators.

Wanneer anders? Gebruik een aparte functie wanneer dezelfde berekening op meerdere plekken nodig is.

Toepassing in MonsterBattles: Damage is een aftrekking; HP gebruikt assignment-operators.

09Boolean operators

Wat is het? && betekent EN, || betekent OF en ! draait een Boolean om.

Hoe werkt het? && vereist twee ware voorwaarden; || minstens één. ! keert true en false om. Haakjes maken samengestelde voorwaarden leesbaar.

BooleanOperators.ktKOTLIN
fun main() {
    val firstExists = true
    val secondExists = true
    val sameMonster = false
    val canBattle = firstExists && secondExists && !sameMonster
    println(canBattle)
}
▸ Terminal / outputVerwachte uitvoer
true

Waarom deze aanpak? Voor een geldige battle moeten beide monsters bestaan én van elkaar verschillen.

Wanneer anders? Een if-expressie is vaak duidelijker wanneer slechts één keuze moet worden gemaakt.

Toepassing in MonsterBattles: Voor een geldige battle moeten beide monsters bestaan én van elkaar verschillen.

10Short-circuit evaluation

Wat is het? Bij && wordt rechts alleen geëvalueerd als links true is; bij || alleen als links false is. De functies and() en or() doen dat niet op dezelfde manier.

Hoe werkt het? Kotlin evalueert bij && de rechterkant alleen als de linkerkant true is. Bij || wordt rechts alleen uitgevoerd wanneer links false is.

ShortCircuit.ktKOTLIN
fun main() {
    val selectedMonster: String? = null
    // The right-hand side is skipped when null.
    val valid = selectedMonster != null && selectedMonster.length > 2
    println(valid)
}
▸ Terminal / outputVerwachte uitvoer
false

Waarom deze aanpak? We voorkomen een onnodige of gevaarlijke tweede controle.

Wanneer anders? and() en or() bestaan ook voor Boolean, maar evalueren beide argumenten; gebruik meestal && en ||.

Toepassing in MonsterBattles: We voorkomen een onnodige of gevaarlijke tweede controle.

11String templates

Wat is het? Met $variable voeg je een waarde in tekst in en met ${expression} een berekende expressie.

Hoe werkt het? Een gewone variabele volgt op $. Voor bewerkingen gebruik je ${...}. Kotlin zet de waarde zelf om naar tekst.

StringTemplates.ktKOTLIN
fun main() {
    val name = "Aquara"
    val hp = 110
    println("$name has ${hp - 18} HP remaining")
}
▸ Terminal / outputVerwachte uitvoer
Aquara has 92 HP remaining

Waarom deze aanpak? Battlelogs kunnen de actuele HP en damage tonen.

Wanneer anders? Voor dynamisch gestructureerde informatie in de API gebruiken we in Week 04 DTO’s en serialisatie.

Toepassing in MonsterBattles: Battlelogs kunnen de actuele HP en damage tonen.

12Functies en returntypes

Wat is het? Een functie heeft parameters en kan een waarde teruggeven. Een functie zonder betekenisvolle returnwaarde gebruikt Unit.

Hoe werkt het? Functies groeperen herbruikbare logica. Parameters geven invoer door; na de dubbele punt staat het expliciete returntype.

Functions.ktKOTLIN
fun calculateDamage(attack: Int, defense: Int): Int {
    return maxOf(1, attack - defense)
}

fun main() {
    println(calculateDamage(30, 12))
}
▸ Terminal / outputVerwachte uitvoer
18

Waarom deze aanpak? De damageberekening krijgt een herbruikbare functie.

Wanneer anders? Eenvoudige formules kunnen single-expression functions worden; langere algoritmen zijn duidelijker met een body.

Toepassing in MonsterBattles: De damageberekening krijgt een herbruikbare functie.

13Single-expression functions

Wat is het? Een functie met één expressie kan met = worden geschreven. Kotlin kan daarbij vaak het returntype afleiden.

Hoe werkt het? Gebruik = na de functiedeclaratie voor één expressie. Het resultaat van de expressie wordt automatisch geretourneerd.

ExpressionFunction.ktKOTLIN
fun isAlive(hp: Int): Boolean = hp > 0

fun main() {
    println(isAlive(92))
}
▸ Terminal / outputVerwachte uitvoer
true

Waarom deze aanpak? Eenvoudige controles blijven kort en leesbaar.

Wanneer anders? Voor meerdere stappen of lokale variabelen gebruik je een functie met accolades en return.

Toepassing in MonsterBattles: Eenvoudige controles blijven kort en leesbaar.

14Default en named arguments

Wat is het? Een parameter kan een standaardwaarde hebben. Named arguments maken de betekenis van argumenten expliciet.

Hoe werkt het? Default arguments krijgen een waarde wanneer de aanroeper het argument weglaat. Named arguments leggen bij de call de betekenis vast.

Arguments.ktKOTLIN
fun hit(attack: Int, defense: Int = 10) = maxOf(1, attack - defense)

fun main() {
    println(hit(attack = 30))
    println(hit(defense = 12, attack = 30))
}
▸ Terminal / outputVerwachte uitvoer
20
18

Waarom deze aanpak? We gebruiken veilige standaardwaarden in een testberekening.

Wanneer anders? Positionele argumenten zijn prima bij evidente kleine calls; named arguments helpen vooral bij meerdere gelijksoortige parameters.

Toepassing in MonsterBattles: We gebruiken veilige standaardwaarden in een testberekening.

15if en else

Wat is het? Een if/else kiest een tak op basis van een Boolean-voorwaarde.

Hoe werkt het? if controleert een Boolean en voert één gekozen blok uit. Het else-blok is optioneel wanneer er geen waarde geproduceerd hoeft te worden.

IfElse.ktKOTLIN
fun main() {
    val pyronSpeed = 110
    val aquaraSpeed = 95
    if (pyronSpeed > aquaraSpeed) {
        println("Pyron starts")
    } else {
        println("Aquara starts")
    }
}
▸ Terminal / outputVerwachte uitvoer
Pyron starts

Waarom deze aanpak? We bepalen welke monsternaam als eerste in het battlelog komt.

Wanneer anders? Gebruik when voor meerdere betekenisvolle vertakkingen; niet elke tweekeuze hoeft een when te worden.

Toepassing in MonsterBattles: We bepalen welke monsternaam als eerste in het battlelog komt.

16if als expression

Wat is het? In Kotlin kan if een waarde produceren. Beide takken leveren dan een passend resultaat.

Hoe werkt het? Een if-expressie produceert direct de uitkomst van de gekozen tak. Gebruik dit voor één beslissing in plaats van een variabele stapsgewijs te muteren.

IfExpression.ktKOTLIN
fun main() {
    val starter = if (110 >= 95) "Pyron" else "Aquara"
    println(starter)
}
▸ Terminal / outputVerwachte uitvoer
Pyron

Waarom deze aanpak? We kiezen de starter zonder eerst een veranderbare variabele te maken.

Wanneer anders? Bij veel zij-effecten kan een gewone if/else duidelijker zijn dan een expression.

Toepassing in MonsterBattles: We kiezen de starter zonder eerst een veranderbare variabele te maken.

17when: twee vormen

Wat is het? when(value) vergelijkt één waarde; when zonder argument gebruikt voorwaarden per tak. Een exhaustieve when-expressie behandelt alle gevallen.

Hoe werkt het? when(value) vergelijkt één onderwerp met takken. when zonder argument evalueert per tak een Boolean-voorwaarde.

When.ktKOTLIN
fun main() {
    val element = "FIRE"
    val display = when (element) {
        "FIRE" -> "Fire monster"
        "WATER" -> "Water monster"
        "WIND" -> "Wind monster"
        else -> "Unknown monster"
    }
    println(display)
    println(when { 110 > 95 -> "Pyron starts"; else -> "Aquara starts" })
}
▸ Terminal / outputVerwachte uitvoer
Fire monster
Pyron starts

Waarom deze aanpak? We tonen op basis van een element de juiste beschrijving.

Wanneer anders? Voor precies twee vertakkingen is if/else vaak korter.

Toepassing in MonsterBattles: We tonen op basis van een element de juiste beschrijving.

18Loops: for, while en do-while

Wat is het? for doorloopt een bereik of collectie. while test vooraf; do-while voert zijn body minimaal één keer uit.

Hoe werkt het? for doorloopt een range of collectie, while controleert vooraf, do-while controleert pas na de eerste iteratie.

Loops.ktKOTLIN
fun main() {
    for (turn in 1..2) println("Turn $turn")
    var hp = 2
    while (hp > 0) hp--
    println("HP: $hp")
    do { println("Battle ended") } while (false)
}
▸ Terminal / outputVerwachte uitvoer
Turn 1
Turn 2
HP: 0
Battle ended

Waarom deze aanpak? Een battle bevat opeenvolgende aanvallen en moet een stopconditie hebben.

Wanneer anders? Gebruik for voor bekende ranges en while wanneer de stopvoorwaarde afhangt van veranderlijke toestand.

Toepassing in MonsterBattles: Een battle bevat opeenvolgende aanvallen en moet een stopconditie hebben.

19Loop control en labels

Wat is het? break stopt een loop; continue gaat naar de volgende iteratie. Een label kan de bedoelde loop expliciet aanwijzen.

Hoe werkt het? break stopt een loop; continue slaat de rest van de huidige iteratie over. Een label helpt bij geneste loops de juiste loop te kiezen.

LoopControl.ktKOTLIN
fun main() {
    for (turn in 1..5) {
        if (turn == 2) continue
        if (turn == 4) break
        println("Turn $turn")
    }
}
▸ Terminal / outputVerwachte uitvoer
Turn 1
Turn 3

Waarom deze aanpak? We slaan ongeldige acties over of stoppen zodra een gevecht voorbij is.

Wanneer anders? Een hulpfunctie of return kan geneste labels vermijden en de bedoeling leesbaarder maken.

Toepassing in MonsterBattles: We slaan ongeldige acties over of stoppen zodra een gevecht voorbij is.

20Ranges en progressions

Wat is het? 1..3 bevat de eindwaarde; until sluit die uit. downTo en step sturen richting en stapgrootte.

Hoe werkt het? .. bevat beide grenzen; until sluit de bovengrens uit; downTo gaat terug; step verandert de stapgrootte; in test lidmaatschap.

Ranges.ktKOTLIN
fun main() {
    println((1..3).toList())
    println((1 until 3).toList())
    println((5 downTo 1 step 2).toList())
}
▸ Terminal / outputVerwachte uitvoer
[1, 2, 3]
[1, 2]
[5, 3, 1]

Waarom deze aanpak? We kunnen de beurtenteller en aflopende waarden eenvoudig doorlopen.

Wanneer anders? Voor indexen gebruik je indices of until, zodat je niet per ongeluk buiten een collectie loopt.

Toepassing in MonsterBattles: We kunnen de beurtenteller en aflopende waarden eenvoudig doorlopen.

21Nullable en non-nullable types

Wat is het? String kan niet null zijn; String? wel. Een null-check kan een smart cast mogelijk maken.

Hoe werkt het? String verbiedt null; String? staat null expliciet toe. De compiler dwingt af dat je met het mogelijke ontbreken van een waarde omgaat.

Nullable.ktKOTLIN
fun main() {
    val monsterName: String? = "Pyron"
    if (monsterName != null) {
        println(monsterName.length)
    }
}
▸ Terminal / outputVerwachte uitvoer
5

Waarom deze aanpak? Een niet gevonden monster kan later als nullable resultaat terugkomen.

Wanneer anders? Gebruik een non-nullable type als de waarde volgens het contract verplicht is.

Toepassing in MonsterBattles: Een niet gevonden monster kan later als nullable resultaat terugkomen.

22Safe calls: ?.

Wat is het? Een safe call voert de property- of functieaanroep alleen uit als de ontvanger niet null is.

Hoe werkt het? De safe-call operator ?. roept een methode alleen aan wanneer de ontvanger niet null is. Anders is de totale expressie null.

SafeCalls.ktKOTLIN
fun main() {
    val name: String? = null
    println(name?.length)
}
▸ Terminal / outputVerwachte uitvoer
null

Waarom deze aanpak? Ontbrekende informatie breekt de weergave van een monster niet.

Wanneer anders? Bij verplichte input is een expliciete null-check met requireNotNull nuttiger dan stilzwijgend doorslaan.

Toepassing in MonsterBattles: Ontbrekende informatie breekt de weergave van een monster niet.

23Elvis operator: ?:

Wat is het? De Elvis-operator gebruikt de linkerkant als die niet null is en anders de rechterkant.

Hoe werkt het? De Elvis-operator ?: gebruikt rechts een fallback wanneer links null is. Je kunt rechts ook return of throw gebruiken.

Elvis.ktKOTLIN
fun main() {
    val monsterName: String? = null
    println(monsterName ?: "Unknown Monster")
}
▸ Terminal / outputVerwachte uitvoer
Unknown Monster

Waarom deze aanpak? We tonen een standaardnaam wanneer een monsternaam ontbreekt.

Wanneer anders? Bij fouten die je niet wilt verbergen, is requireNotNull een beter alternatief dan een kunstmatige fallback.

Toepassing in MonsterBattles: We tonen een standaardnaam wanneer een monsternaam ontbreekt.

24Not-null assertion: !!

Wat is het? !! dwingt een nullable waarde naar een non-null type maar veroorzaakt een NullPointerException als die toch null is.

Hoe werkt het? !! vertelt de compiler dat jij garandeert dat een waarde niet null is. Is die toch null, dan volgt een exception.

NotNullAssertion.ktKOTLIN
fun main() {
    val name: String? = "Pyron"
    // This succeeds, but a null value would crash.
    println(name!!.length)
}
▸ Terminal / outputVerwachte uitvoer
5

Waarom deze aanpak? Een API hoort ontbrekende monsters gecontroleerd af te handelen, niet met !! te crashen.

Wanneer anders? Veiligere keuzes zijn ?. en ?:, of een expliciete controle met if.

Toepassing in MonsterBattles: Een API hoort ontbrekende monsters gecontroleerd af te handelen, niet met !! te crashen.

25Type checks en smart casts

Wat is het? Met is en !is controleer je het type. Na een geldige controle mag Kotlin vaak automatisch casten.

Hoe werkt het? is controleert het runtime-type. Binnen een tak waarin de compiler het type kan afleiden is meestal geen expliciete cast nodig.

TypeChecks.ktKOTLIN
fun main() {
    val value: Any = "Pyron"
    if (value is String) {
        println(value.uppercase())
    }
}
▸ Terminal / outputVerwachte uitvoer
PYRON

Waarom deze aanpak? We kunnen onbekende invoer herkennen voordat we ermee rekenen.

Wanneer anders? Bij interfaces met meerdere implementaties werkt polymorfisme vaak beter dan overal is-checks.

Toepassing in MonsterBattles: We kunnen onbekende invoer herkennen voordat we ermee rekenen.

26Safe casts: as?

Wat is het? as? geeft null wanneer een cast mislukt, in plaats van een ClassCastException.

Hoe werkt het? as? levert null als een cast niet geldig is. De gewone as-cast zou bij een verkeerd type een exception geven.

SafeCasts.ktKOTLIN
fun main() {
    val value: Any = 100
    val name = value as? String
    println(name ?: "No monster name")
}
▸ Terminal / outputVerwachte uitvoer
No monster name

Waarom deze aanpak? We verwerken data waarvan het type niet vooraf vaststaat.

Wanneer anders? Als een type gegarandeerd klopt kan een gewone cast passend zijn; controleer die aanname zorgvuldig.

Toepassing in MonsterBattles: We verwerken data waarvan het type niet vooraf vaststaat.

27Speciale types: Any, Unit, Nothing

Wat is het? Any is een algemeen niet-null basistype. Unit is het resultaat van functies zonder betekenisvolle waarde. Nothing duidt code aan die nooit normaal terugkeert.

Hoe werkt het? Any is het algemene non-null supertype, Unit geeft aan dat geen zinvolle waarde wordt geretourneerd, Nothing wordt nooit normaal geproduceerd.

SpecialTypes.ktKOTLIN
fun announce(value: Any): Unit {
    println(value)
}

fun fail(message: String): Nothing = error(message)

fun main() {
    announce("Monster ready")
    // fail("Not ready") would throw and never return.
}
▸ Terminal / outputVerwachte uitvoer
Monster ready

Waarom deze aanpak? We begrijpen hiermee typecontracten van validatie en foutfuncties.

Wanneer anders? Kies concrete types zoals String of Int wanneer je het contract weet; Any verliest specifieke type-informatie.

Toepassing in MonsterBattles: We begrijpen hiermee typecontracten van validatie en foutfuncties.

28TODO() en Nothing

Wat is het? TODO() is een placeholder die compileert maar bij uitvoering NotImplementedError gooit. De returnwaarde heeft type Nothing.

Hoe werkt het? TODO() is een expliciete placeholder die een NotImplementedError gooit. Door het returntype Nothing kan de compiler hem op plekken accepteren waar een waarde verwacht wordt.

TodoDemo.ktKOTLIN
fun unfinishedBattle(): Int = TODO("Implement battle rules")

fun main() {
    // Never execute a TODO placeholder in the finished app.
    println("TODO() compiles, but throws when called")
}
▸ Terminal / outputVerwachte uitvoer
TODO() compiles, but throws when called

Waarom deze aanpak? Tijdens het opzetten van projectstructuur maken we duidelijk welke implementatie nog ontbreekt.

Wanneer anders? Gebruik TODO() alleen tijdelijk; zet het niet in codepaden die gebruikers kunnen uitvoeren.

Toepassing in MonsterBattles: Tijdens het opzetten van projectstructuur maken we duidelijk welke implementatie nog ontbreekt.

29Validatie: require, check en error

Wat is het? require controleert argumenten, check controleert toestand, error gooit een IllegalStateException. requireNotNull controleert verplichte niet-nullwaarden.

Hoe werkt het? require controleert argumenten; check controleert de toestand; error gooit een IllegalStateException. Gebruik fouttypes die passen bij de oorzaak.

Validation.ktKOTLIN
fun calculateDamage(attack: Int, defense: Int): Int {
    require(attack >= 0) { "Attack must be non-negative" }
    require(defense >= 0) { "Defense must be non-negative" }
    return maxOf(1, attack - defense)
}

fun main() {
    println(calculateDamage(30, 12))
}
▸ Terminal / outputVerwachte uitvoer
18

Waarom deze aanpak? We voorkomen negatieve Attack en Defense en ongeldige HP.

Wanneer anders? In de HTTP-laag vertalen we validatiefouten later naar een gestandaardiseerde 400-response.

Toepassing in MonsterBattles: We voorkomen negatieve Attack en Defense en ongeldige HP.

30Introductie List en MutableList

Wat is het? List biedt een read-only interface. MutableList ondersteunt wijzigingen. Een val kan nog steeds naar een MutableList verwijzen.

Hoe werkt het? List is read-only vanuit die referentie, niet per definitie diep immutable. MutableList biedt add, remove en andere mutaties.

CollectionsIntro.ktKOTLIN
fun main() {
    val names: List<String> = listOf("Pyron", "Aquara")
    val selected = mutableListOf("Pyron")
    selected.add("Aquara")
    println(names.size)
    println(selected)
}
▸ Terminal / outputVerwachte uitvoer
2
[Pyron, Aquara]

Waarom deze aanpak? We bewaren een eerste verzameling monsternamen.

Wanneer anders? Gebruik Set voor unieke elementen en Map voor lookup op sleutel; dat behandelen we uitgebreid in Week 05.

Toepassing in MonsterBattles: We bewaren een eerste verzameling monsternamen.

31Complete monsterberekening

Wat is het? Dit samenhangende programma gebruikt de basisconcepten uit Week 01. De volledige objectgeoriënteerde battle-engine volgt pas in Week 03.

Hoe werkt het? De demo bewaart beide monsters in losse variabelen; de loop houdt bij welke deelnemer aanvalt, vermindert HP en stopt bij nul. Er zijn nog geen domeinclasses.

MonsterDemo.ktKOTLIN
fun calculateDamage(attack: Int, defense: Int): Int =
    maxOf(1, attack - defense)

fun main() {
    val pyronName = "Pyron"
    val pyronAttack = 30
    val pyronDefense = 10
    val pyronSpeed = 110
    var pyronHp = 100

    val aquaraName = "Aquara"
    val aquaraAttack = 25
    val aquaraDefense = 12
    val aquaraSpeed = 95
    var aquaraHp = 110

    // At equal speed, the first selected monster begins.
    var pyronTurn = pyronSpeed >= aquaraSpeed
    var turns = 0
    var rounds = 0
    println("${if (pyronTurn) pyronName else aquaraName} starts")

    while (pyronHp > 0 && aquaraHp > 0) {
        if (turns % 2 == 0) rounds++
        turns++
        if (pyronTurn) {
            val damage = calculateDamage(pyronAttack, aquaraDefense)
            aquaraHp = maxOf(0, aquaraHp - damage)
            println("Turn $turns: $pyronName hits $aquaraName for $damage (HP: $aquaraHp)")
        } else {
            val damage = calculateDamage(aquaraAttack, pyronDefense)
            pyronHp = maxOf(0, pyronHp - damage)
            println("Turn $turns: $aquaraName hits $pyronName for $damage (HP: $pyronHp)")
        }
        pyronTurn = !pyronTurn
    }

    println("Winner: ${if (pyronHp > 0) pyronName else aquaraName}")
    println("Rounds: $rounds | Turns: $turns")
    println("Final HP: $pyronName=$pyronHp, $aquaraName=$aquaraHp")
}
▸ Terminal / outputVerwachte uitvoer
Pyron starts
Turn 1: Pyron hits Aquara for 18 (HP: 92)
Turn 2: Aquara hits Pyron for 15 (HP: 85)
Turn 3: Pyron hits Aquara for 18 (HP: 74)
Turn 4: Aquara hits Pyron for 15 (HP: 70)
Turn 5: Pyron hits Aquara for 18 (HP: 56)
Turn 6: Aquara hits Pyron for 15 (HP: 55)
Turn 7: Pyron hits Aquara for 18 (HP: 38)
Turn 8: Aquara hits Pyron for 15 (HP: 40)
Turn 9: Pyron hits Aquara for 18 (HP: 20)
Turn 10: Aquara hits Pyron for 15 (HP: 25)
Turn 11: Pyron hits Aquara for 18 (HP: 2)
Turn 12: Aquara hits Pyron for 15 (HP: 10)
Turn 13: Pyron hits Aquara for 18 (HP: 0)
Winner: Pyron
Rounds: 7 | Turns: 13
Final HP: Pyron=10, Aquara=0

Waarom deze aanpak? Het vormt de eerste werkende MonsterBattles-prototype zonder classes.

Wanneer anders? Vanaf Week 02 gebruiken we Monster en Stats; in Week 03 verplaatsen we de volledige loop naar BattleService.

Toepassing in MonsterBattles: Het vormt de eerste werkende MonsterBattles-prototype zonder classes.

Cheat Sheet

SyntaxBetekenis
val / varNiet opnieuw toewijzen / wel opnieuw toewijzen
const valCompile-time constante
funFunctie declareren
if / whenVoorwaardelijke expressies
for / while / do-whileHerhalingen
1..5 / 1 until 5Inclusieve / exclusieve eindgrens
?. / ?:Safe call / standaard bij null
!!Not-null assertion; kan crashen
is / as?Typecheck / veilige cast
Any / Unit / NothingAlgemeen type / geen betekenisvolle return / keert niet normaal terug
require / checkArgumentvoorwaarde / toestandscontrole
List / MutableListRead-only interface / wijzigbare interface
MonsterBattles Docs · Code, comments & output