From 6ef68a5e05adf5ef6e655dd44f9b21d36fd51ce1 Mon Sep 17 00:00:00 2001 From: Martin Berg Alstad Date: Thu, 31 Jul 2025 15:46:05 +0200 Subject: [PATCH] Refacor Mastermind and implement functional style --- src/assignment/week2/mastermind/Common.kt | 3 + .../week2/mastermind/evaluateGuess.kt | 68 +++++++++++++------ .../mastermind/evaluateGuessFunctional.kt | 22 ++++++ .../week2/mastermind/playMastermind.kt | 20 +++--- 4 files changed, 83 insertions(+), 30 deletions(-) create mode 100644 src/assignment/week2/mastermind/Common.kt create mode 100644 src/assignment/week2/mastermind/evaluateGuessFunctional.kt diff --git a/src/assignment/week2/mastermind/Common.kt b/src/assignment/week2/mastermind/Common.kt new file mode 100644 index 0000000..c80f826 --- /dev/null +++ b/src/assignment/week2/mastermind/Common.kt @@ -0,0 +1,3 @@ +package assignment.week2.mastermind + +data class Evaluation(val rightPosition: Int, val wrongPosition: Int) diff --git a/src/assignment/week2/mastermind/evaluateGuess.kt b/src/assignment/week2/mastermind/evaluateGuess.kt index fc19c1f..0af4ffe 100644 --- a/src/assignment/week2/mastermind/evaluateGuess.kt +++ b/src/assignment/week2/mastermind/evaluateGuess.kt @@ -1,6 +1,4 @@ -package mastermind - -data class Evaluation(val rightPosition: Int, val wrongPosition: Int) +package assignment.week2.mastermind enum class Match { RIGHT_POSITION, @@ -13,31 +11,57 @@ fun evaluateGuess(secret: String, guess: String): Evaluation { val secretMatches = createMatches(secret) for ((index, guessChar) in guess.withIndex()) { - if (secretMatches[index].char == guessChar) { - if (secretMatches[index].match == Match.WRONG_POSITION) { - for ((index, secretChar) in secret.drop(index + 1).withIndex()) { - if (secretChar == guessChar) { - secretMatches[index].match = Match.WRONG_POSITION - break - } - } - } - secretMatches[index].match = Match.RIGHT_POSITION + val secretMatch = secretMatches[index] + if (secretMatch.char == guessChar) { + handleMatch(secretMatches, index) continue } - for ((index, secretChar) in secret.withIndex()) { - if (secretMatches[index].match == null && secretChar == guessChar) { - secretMatches[index].match = Match.WRONG_POSITION - break - } - } + handleNotMatch(secretMatches, guessChar) } return Evaluation( - secretMatches.filter { it.match == Match.RIGHT_POSITION }.size, - secretMatches.filter { it.match == Match.WRONG_POSITION }.size + secretMatches.countMatch(Match.RIGHT_POSITION), + secretMatches.countMatch(Match.WRONG_POSITION) ) } -fun createMatches(secret: String): List = secret.map { CharMatch(it, null) } +private fun handleNotMatch( + secretMatches: List, + guessChar: Char +) { + for (secretMatch in secretMatches) { + if (secretMatch.match == null && secretMatch.char == guessChar) { + secretMatch.match = Match.WRONG_POSITION + return + } + } +} +private fun handleMatch(secretMatches: List, index: Int) { + val secretMatch = secretMatches[index] + if (secretMatch.match == Match.WRONG_POSITION) { + moveWrongPosition( + secretMatches.drop(index + 1).map { it.char }, + secretMatch.char, + secretMatches + ) + } + secretMatch.match = Match.RIGHT_POSITION +} + +private fun List.countMatch(match: Match): Int = filter { it.match == match }.size + +private fun moveWrongPosition( + chars: List, + guessChar: Char, + secretMatches: List +) { + for ((index, secretChar) in chars.withIndex()) { + if (secretChar == guessChar) { + secretMatches[index].match = Match.WRONG_POSITION + return + } + } +} + +fun createMatches(secret: String): List = secret.map { CharMatch(it, null) } diff --git a/src/assignment/week2/mastermind/evaluateGuessFunctional.kt b/src/assignment/week2/mastermind/evaluateGuessFunctional.kt new file mode 100644 index 0000000..1ee8686 --- /dev/null +++ b/src/assignment/week2/mastermind/evaluateGuessFunctional.kt @@ -0,0 +1,22 @@ +package assignment.week2.mastermind + +fun evaluateGuessFunctional(secret: String, guess: String): Evaluation { + val rightPositions = secret.zip(guess).count { (sChar, gChar) -> sChar == gChar } + + val commonLetters = "ABCDEF".sumOf { ch -> + secret.count { it == ch }.coerceAtMost(guess.count { it == ch }) + } + return Evaluation(rightPositions, commonLetters - rightPositions) +} + +fun main() { + val result = Evaluation(rightPosition = 1, wrongPosition = 1) + evaluateGuessFunctional("BCDF", "ACEB") eq result + evaluateGuessFunctional("AAAF", "ABCA") eq result + evaluateGuessFunctional("ABCA", "AAAF") eq result +} + +private infix fun T.eq(other: T) { + if (this != other) throw AssertionError("Expected $this to equal $other") + println("OK") +} diff --git a/src/assignment/week2/mastermind/playMastermind.kt b/src/assignment/week2/mastermind/playMastermind.kt index 8de5c72..a45276c 100644 --- a/src/assignment/week2/mastermind/playMastermind.kt +++ b/src/assignment/week2/mastermind/playMastermind.kt @@ -1,4 +1,4 @@ -package mastermind +package assignment.week2.mastermind import kotlin.random.Random @@ -11,8 +11,8 @@ fun main() { } fun playMastermind( - differentLetters: Boolean, - secret: String = generateSecret(differentLetters) + differentLetters: Boolean, + secret: String = generateSecret(differentLetters) ) { var evaluation: Evaluation @@ -20,17 +20,21 @@ fun playMastermind( print("Your guess: ") var guess = readLine()!! while (hasErrorsInInput(guess)) { - println("Incorrect input: $guess. " + - "It should consist of $CODE_LENGTH characters from $ALPHABET. " + - "Please try again.") + println( + "Incorrect input: $guess. " + + "It should consist of $CODE_LENGTH characters from $ALPHABET. " + + "Please try again." + ) guess = readLine()!! } evaluation = evaluateGuess(secret, guess) if (evaluation.isComplete()) { println("The guess is correct!") } else { - println("Right positions: ${evaluation.rightPosition}; " + - "wrong positions: ${evaluation.wrongPosition}.") + println( + "Right positions: ${evaluation.rightPosition}; " + + "wrong positions: ${evaluation.wrongPosition}." + ) } } while (!evaluation.isComplete())