1 The Problem
We want a tiny game: the computer thinks of a number between 1 and 100, and the player keeps guessing. After each guess the computer says “too high” or “too low” until the player gets it right. Simple — but it teaches the three things every program does: ask, decide, and repeat.
2 How to Think About It
Before writing any code, picture the game as a loop with a decision inside it. You do not need to know Kotlin yet — you just need to know the shape of what happens:
3 The Build — explained part by part
Here is the complete game. Read each part’s note below — you should understand the whole thing from the notes alone.
import kotlin.random.Random
import java.io.PrintStream
/**
* Plays one game against a known secret, reading guesses from [guesses]
* instead of a real terminal — kept separate from `main` so it is directly
* testable with no randomness and no typed input involved.
*
* @return the number of guesses it took, or null if [guesses] ran out first
*/
fun playGame(secret: Int, guesses: List<Int>): Int? {
var taken = 0
for (guess in guesses) {
taken++
if (guess == secret) return taken
}
return null
}
fun main() {
System.setOut(PrintStream(System.out, true, "UTF-8")) // keep the em dash readable on any locale
val secretNumber = Random.nextInt(1, 101) // 1..100 inclusive
var guessesTaken = 0
println("I am thinking of a number between 1 and 100.")
while (true) {
print("Your guess: ")
val line = readLine() ?: run {
println("\nNo more input — goodbye.")
return
}
val guess = line.trim().toIntOrNull()
if (guess == null) {
println("Please type a whole number.")
continue
}
guessesTaken++
when {
guess < secretNumber -> println("Too low. Try a bigger number.")
guess > secretNumber -> println("Too high. Try a smaller number.")
else -> {
println("Correct! You got it in $guessesTaken guesses.")
return
}
}
}
}kotlinc on your own machine instead; the “Run It” section explains exactly how.Random.nextInt(1, 101) picks a whole number from 1 up to (but not including) 101 — i.e. 1 to 100.fun playGame(secret: Int, guesses: List<Int>): Int? — a testable version of the game, pulled out of the interactive loop exactly like the Python/Go versions. Its return type,
Int?, is Kotlin's nullable type: “either a number of guesses, or nothing at all” — and the compiler forces every caller to check before treating the result as a real Int.val line = readLine() ?: run { ...; return } — the Elvis operator
?: supplies what to do when readLine() returns null (input has run out): here, print a message and return from main entirely, so the loop can never spin forever on exhausted input.when { guess < secretNumber -> ...; guess > secretNumber -> ...; else -> ... } — a condition-only
when (no subject) reads like a clean chain of if/else if, picking the first branch whose condition is true.
kotlin.random.Random is fine for a game; for anything where unpredictability actually matters (see the password generator project), reach for java.security.SecureRandom instead.readLine() returned null and break out (or return) — otherwise a piped-in file with too few lines causes an infinite loop with no new prompts, since a plain, unchecked loop keeps asking.Int when the function can also return “no answer”.Int? so the compiler reminds every caller to check (with ?., ?:, or an explicit if) before using the result.4 Test & Prove Each Part
How do we know this works? We pull the real logic into small, plain functions and check each one against cases we already know the answer to.
null, not a crashimport kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlin.test.assertTrue
class GuessingGameTest {
@Test
fun correctFirstTry() {
assertEquals(1, playGame(42, listOf(42)))
}
@Test
fun takesThreeTries() {
assertEquals(3, playGame(50, listOf(10, 90, 50)))
}
@Test
fun neverGuesses() {
assertNull(playGame(7, listOf(1, 2, 3)))
}
@Test
fun secretIsInRange() {
repeat(1000) {
val n = Random.nextInt(1, 101)
assertTrue(n in 1..100)
}
}
}Compile with kotlinc guessingGame.kt guessingGameTest.kt -include-runtime -d guessingGame.jar and run with JUnit's own runner. Because playGame takes the secret and the list of guesses as plain arguments, the tests never have to deal with real randomness or real typed input — they hand it fixed numbers and check the return value.
5 The Interface
Even a tiny program has an interface — the way a person interacts with it. Here is its contract, documented plainly, the same way a professional would describe any tool.
What it expects
A number like 42, typed and then Enter pressed.What it returns
"Too low. Try a bigger number."
"Too high. Try a smaller number."
"Correct! You got it in N guesses."6 Run It & Automate It
Save the code as guessingGame.kt and compile it with kotlinc guessingGame.kt -include-runtime -d guessingGame.jar.
kotlinc guessingGame.kt -include-runtime -d guessingGame.jar && java -jar guessingGame.jarKeep guessing until you find the secret number between 1 and 100.
A CI tool like Jenkins compiles and tests automatically whenever the code changes — every line below has a plain explanation.
I am thinking of a number between 1 and 100.
Your guess: 50
Too low. Try a bigger number.
Your guess: 75
Too high. Try a smaller number.
Your guess: 63
Correct! You got it in 3 guesses.return (or break) as soon as readLine() comes back null (see the code above).line.trim().toIntOrNull() returns null for non-numeric text; this program's loop prints “Please type a whole number.” and asks again rather than crashing — type an actual whole number to continue.// Jenkinsfile — compiles and tests automatically every time the code changes.
pipeline {
agent any // run on any available machine
stages {
stage('Get the code') {
steps { checkout scm } // download the latest code
}
stage('Set up Kotlin') {
steps {
sh 'kotlinc -version' // confirm the compiler is installed
}
}
stage('Compile and test') {
steps {
sh 'kotlinc guessingGame.kt guessingGameTest.kt -include-runtime -d build.jar' // one real JVM jar, no build tool required
sh 'java -cp build.jar:kotlin-test-junit.jar:junit.jar org.junit.runner.JUnitCore GuessingGameTest'
}
}
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
You have a working number guessing game. Extend it:
- Limit the attempts. End the game after 10 wrong guesses. (Teaches: a counter plus an early exit.)
- Add difficulty levels. Let the player choose a range like 1–1000. (Teaches: parameterising
Random.nextInt.) - Give a “warmer/colder” hint. Compare how close each guess is to the last one. (Teaches: keeping extra state between loop iterations.)
- Play again. After a win, ask “Play again? (y/n)” and restart. (Teaches: wrapping the whole game in an outer loop.)
playGame takes plain data, not live input), Kotlin’s nullable Int? for “a value or nothing”, and the Elvis operator ?: for a default when a nullable value is missing. Related reference: Null Safety & Types, Functions in Kotlin.