Added obligatory assignments

This commit is contained in:
Martin Berg Alstad
2025-07-31 12:09:49 +02:00
committed by Martin Berg Alstad
parent 64d9e4ada7
commit 92c416ab8b
17 changed files with 815 additions and 0 deletions

View File

@ -0,0 +1,90 @@
package games.game2048
import board.Cell
import board.Direction
import board.GameBoard
import board.createGameBoard
import games.game.Game
/*
* Your task is to implement the game 2048 https://en.wikipedia.org/wiki/2048_(video_game).
* Implement the utility methods below.
*
* After implementing it you can try to play the game running 'PlayGame2048'.
*/
fun newGame2048(initializer: Game2048Initializer<Int> = RandomGame2048Initializer): Game =
Game2048(initializer)
class Game2048(private val initializer: Game2048Initializer<Int>) : Game {
private val board = createGameBoard<Int?>(4)
override fun initialize() {
repeat(2) {
board.addNewValue(initializer)
}
}
override fun canMove() = board.any { it == null }
override fun hasWon() = board.any { it == 2048 }
override fun processMove(direction: Direction) {
if (board.moveValues(direction)) {
board.addNewValue(initializer)
}
}
override fun get(i: Int, j: Int): Int? = board.run { get(getCell(i, j)) }
}
/*
* Add a new value produced by 'initializer' to a specified cell in a board.
*/
fun GameBoard<Int?>.addNewValue(initializer: Game2048Initializer<Int>) {
val (cell, value) = initializer.nextValue(this) ?: return
this[cell] = value
}
/*
* Update the values stored in a board,
* so that the values were "moved" in a specified rowOrColumn only.
* Use the helper function 'moveAndMergeEqual' (in Game2048Helper.kt).
* The values should be moved to the beginning of the row (or column),
* in the same manner as in the function 'moveAndMergeEqual'.
* Return 'true' if the values were moved and 'false' otherwise.
*/
fun GameBoard<Int?>.moveValuesInRowOrColumn(rowOrColumn: List<Cell>): Boolean {
val values = rowOrColumn.map { this[it] }
val merged = values.moveAndMergeEqual { it * 2 }
for ((index, cell) in rowOrColumn.withIndex()) {
this[cell] = merged.getOrNull(index)
}
return merged.mapIndexed { index, value -> value != values[index] }.any { it }
}
/*
* Update the values stored in a board,
* so that the values were "moved" to the specified direction
* following the rules of the 2048 game .
* Use the 'moveValuesInRowOrColumn' function above.
* Return 'true' if the values were moved and 'false' otherwise.
*/
fun GameBoard<Int?>.moveValues(direction: Direction): Boolean = when (direction) {
Direction.UP -> 1.rangeTo(width)
.map { j -> moveValuesInRowOrColumn(getColumn(1..width, j)) }
.any { it }
Direction.RIGHT -> 1.rangeTo(width)
.map { i -> moveValuesInRowOrColumn(getRow(i, width downTo 1)) }
.any { it }
Direction.DOWN -> 1.rangeTo(width)
.map { j -> moveValuesInRowOrColumn(getColumn(width downTo 1, j)) }
.any { it }
Direction.LEFT -> 1.rangeTo(width)
.map { i -> moveValuesInRowOrColumn(getRow(i, 1..width)) }
.any { it }
}

View File

@ -0,0 +1,41 @@
package games.game2048
/*
* This function moves all the non-null elements to the beginning of the list
* (by removing nulls) and merges equal elements.
* The parameter 'merge' specifies the way how to merge equal elements:
* it returns a new element that should be present in the resulting list
* instead of two merged elements.
*
* If the function 'merge("a")' returns "aa",
* then the function 'moveAndMergeEqual' transforms the input in the following way:
* a, a, b -> aa, b
* a, null -> a
* b, null, a, a -> b, aa
* a, a, null, a -> aa, a
* a, null, a, a -> aa, a
*
* You can find more examples in 'TestGame2048Helper'.
*/
fun <T : Any> List<T?>.moveAndMergeEqual(merge: (T) -> T): List<T> {
val mutableList = mutableListOf<T>()
val oldList = filterNotNull()
var index = 0
while (true) {
if (index >= oldList.size) break
val first = oldList[index]
val second = oldList.getOrNull(index + 1)
if (first == second) {
index += 2
mutableList.add(merge(first))
} else {
index += 1
mutableList.add(first)
}
}
return mutableList
}

View File

@ -0,0 +1,29 @@
package games.game2048
import board.Cell
import board.GameBoard
import kotlin.random.Random
interface Game2048Initializer<T> {
/*
* Specifies the cell and the value that should be added to this cell.
*/
fun nextValue(board: GameBoard<T?>): Pair<Cell, T>?
}
object RandomGame2048Initializer : Game2048Initializer<Int> {
private fun generateRandomStartValue(): Int =
if (Random.nextInt(10) == 9) 4 else 2
/*
* Generate a random value and a random cell among free cells
* that given value should be added to.
* The value should be 2 for 90% cases, and 4 for the rest of the cases.
* Use the 'generateRandomStartValue' function above.
* If the board is full return null.
*/
override fun nextValue(board: GameBoard<Int?>): Pair<Cell, Int>? = board
.filter { it == null }
.randomOrNull()
?.to(generateRandomStartValue())
}