Added obligatory assignments

This commit is contained in:
Martin Berg Alstad
2025-07-31 12:09:49 +02:00
parent 80c14c76ba
commit 8d21fcc954
17 changed files with 815 additions and 0 deletions

View File

@ -0,0 +1,43 @@
package mastermind
data class Evaluation(val rightPosition: Int, val wrongPosition: Int)
enum class Match {
RIGHT_POSITION,
WRONG_POSITION,
}
data class CharMatch(val char: Char, var match: Match?)
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
continue
}
for ((index, secretChar) in secret.withIndex()) {
if (secretMatches[index].match == null && secretChar == guessChar) {
secretMatches[index].match = Match.WRONG_POSITION
break
}
}
}
return Evaluation(
secretMatches.filter { it.match == Match.RIGHT_POSITION }.size,
secretMatches.filter { it.match == Match.WRONG_POSITION }.size
)
}
fun createMatches(secret: String): List<CharMatch> = secret.map { CharMatch(it, null) }

View File

@ -0,0 +1,57 @@
package mastermind
import kotlin.random.Random
val ALPHABET = 'A'..'F'
const val CODE_LENGTH = 4
fun main() {
val differentLetters = false
playMastermind(differentLetters)
}
fun playMastermind(
differentLetters: Boolean,
secret: String = generateSecret(differentLetters)
) {
var evaluation: Evaluation
do {
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.")
guess = readLine()!!
}
evaluation = evaluateGuess(secret, guess)
if (evaluation.isComplete()) {
println("The guess is correct!")
} else {
println("Right positions: ${evaluation.rightPosition}; " +
"wrong positions: ${evaluation.wrongPosition}.")
}
} while (!evaluation.isComplete())
}
fun Evaluation.isComplete(): Boolean = rightPosition == CODE_LENGTH
fun hasErrorsInInput(guess: String): Boolean {
val possibleLetters = ALPHABET.toSet()
return guess.length != CODE_LENGTH || guess.any { it !in possibleLetters }
}
fun generateSecret(differentLetters: Boolean): String {
val chars = ALPHABET.toMutableList()
return buildString {
for (i in 1..CODE_LENGTH) {
val letter = chars[Random.nextInt(chars.size)]
append(letter)
if (differentLetters) {
chars.remove(letter)
}
}
}
}

View File

@ -0,0 +1,25 @@
package nicestring
fun String.isNice(): Boolean {
return listOf(
::containsNotNiceSubstring,
::containsAtLeastThreeVowels,
::containsDoubleLetter
)
.count { it() } >= 2
}
fun String.containsNotNiceSubstring(): Boolean {
val illegalSubStrings = listOf("bu", "ba", "be")
return this.zipWithNext()
.map { (first, second) -> "$first$second" }
.none { it in illegalSubStrings }
}
fun String.containsAtLeastThreeVowels(): Boolean {
val vovels = "aeiou".toCharArray()
return this.count { char -> char in vovels } >= 3
}
fun String.containsDoubleLetter(): Boolean = this.zipWithNext()
.any { (first, second) -> first == second }

View File

@ -0,0 +1,24 @@
package taxipark
data class TaxiPark(
val allDrivers: Set<Driver>,
val allPassengers: Set<Passenger>,
val trips: List<Trip>)
data class Driver(val name: String)
data class Passenger(val name: String)
data class Trip(
val driver: Driver,
val passengers: Set<Passenger>,
// the trip duration in minutes
val duration: Int,
// the trip distance in km
val distance: Double,
// the percentage of discount (in 0.0..1.0 if not null)
val discount: Double? = null
) {
// the total cost of the trip
val cost: Double
get() = (1 - (discount ?: 0.0)) * (duration + distance)
}

View File

@ -0,0 +1,80 @@
package taxipark
/*
* Task #1. Find all the drivers who performed no trips.
*/
fun TaxiPark.findFakeDrivers(): Set<Driver> =
this.allDrivers
.filter { driver -> this.trips.none { trip -> trip.driver == driver } }
.toSet()
/*
* Task #2. Find all the clients who completed at least the given number of trips.
*/
fun TaxiPark.findFaithfulPassengers(minTrips: Int): Set<Passenger> =
this.allPassengers
.filter { passenger -> this.trips.count { trip -> trip.passengers.contains(passenger) } >= minTrips }
.toSet()
/*
* Task #3. Find all the passengers, who were taken by a given driver more than once.
*/
fun TaxiPark.findFrequentPassengers(driver: Driver): Set<Passenger> =
this.allPassengers
.filter { passenger ->
this.trips
.filter { trip -> trip.passengers.contains(passenger) }
.count { trip -> trip.driver == driver } > 1
}
.toSet()
/*
* Task #4. Find the passengers who had a discount for majority of their trips.
*/
fun TaxiPark.findSmartPassengers(): Set<Passenger> = this.allPassengers
.filter(fun(passenger: Passenger): Boolean {
val (discounted, nonDiscounted) = this.trips
.filter { it.passengers.contains(passenger) }
.partition { it.discount != null }
if (nonDiscounted.isEmpty()) {
return discounted.isNotEmpty()
}
return (discounted.size.toDouble()) / nonDiscounted.size > 1.0
})
.toSet()
/*
* Task #5. Find the most frequent trip duration among minute periods 0..9, 10..19, 20..29, and so on.
* Return any period if many are the most frequent, return `null` if there're no trips.
*/
fun TaxiPark.findTheMostFrequentTripDurationPeriod(): IntRange? {
val maxDuration = this.trips
.map { trip -> trip.duration / 10 }
.groupBy { it }
.maxByOrNull { it.value.size }
val startRange = maxDuration?.let { it.key * 10 }
return startRange?.let { it.rangeTo(it + 9) }
}
/*
* Task #6.
* Check whether 20% of the drivers contribute 80% of the income.
*/
fun TaxiPark.checkParetoPrinciple(): Boolean {
if (this.trips.isEmpty()) {
return false
}
val totalIncome = this.trips.sumOf(Trip::cost)
val top20Percent = this.trips
.groupBy { it.driver }
.values
.map { trips -> trips.sumOf(Trip::cost) }
.sortedDescending()
.take((this.allDrivers.size * 0.2).toInt())
.sum()
return top20Percent >= totalIncome * 0.8
}

View File

@ -0,0 +1,41 @@
package board
data class Cell(val i: Int, val j: Int) {
override fun toString()= "($i, $j)"
}
enum class Direction {
UP, DOWN, RIGHT, LEFT;
fun reversed() = when (this) {
UP -> DOWN
DOWN -> UP
RIGHT -> LEFT
LEFT -> RIGHT
}
}
interface SquareBoard {
val width: Int
fun getCellOrNull(i: Int, j: Int): Cell?
fun getCell(i: Int, j: Int): Cell
fun getAllCells(): Collection<Cell>
fun getRow(i: Int, jRange: IntProgression): List<Cell>
fun getColumn(iRange: IntProgression, j: Int): List<Cell>
fun Cell.getNeighbour(direction: Direction): Cell?
}
interface GameBoard<T> : SquareBoard {
operator fun get(cell: Cell): T?
operator fun set(cell: Cell, value: T?)
fun filter(predicate: (T?) -> Boolean): Collection<Cell>
fun find(predicate: (T?) -> Boolean): Cell?
fun any(predicate: (T?) -> Boolean): Boolean
fun all(predicate: (T?) -> Boolean): Boolean
}

View File

@ -0,0 +1,80 @@
package board
import kotlin.collections.component1
fun createSquareBoard(width: Int): SquareBoard = SquareBoardImpl(width)
fun <T> createGameBoard(width: Int): GameBoard<T> = GameBoardImpl(width)
open class SquareBoardImpl(override val width: Int) : SquareBoard {
protected val cells: List<Cell>
init {
val range = 1..width
cells = range
.flatMap { first -> range.map { second -> Cell(first, second) } }
.distinct()
}
override fun getCellOrNull(i: Int, j: Int): Cell? =
cells.firstOrNull { cell -> cell.i == i && cell.j == j }
override fun getCell(i: Int, j: Int): Cell =
getCellOrNull(i, j) ?: throw IllegalArgumentException("Range is out of bounds")
override fun getAllCells(): Collection<Cell> = cells
override fun getRow(i: Int, jRange: IntProgression): List<Cell> {
val cells = cells.filter { cell -> cell.i == i && cell.j in jRange }
return if (jRange.isIncreasing()) cells else cells.sortedByDescending { it.j }
}
override fun getColumn(iRange: IntProgression, j: Int): List<Cell> {
val cells = cells.filter { cell -> cell.j == j && cell.i in iRange }
return if (iRange.isIncreasing()) cells else cells.sortedByDescending { it.i }
}
override fun Cell.getNeighbour(direction: Direction): Cell? = when (direction) {
Direction.UP -> getCellOrNull(i - 1, j)
Direction.RIGHT -> getCellOrNull(i, j + 1)
Direction.DOWN -> getCellOrNull(i + 1, j)
Direction.LEFT -> getCellOrNull(i, j - 1)
}
private fun IntProgression.isIncreasing() = step > 0
}
class GameBoardImpl<T>(width: Int) : SquareBoardImpl(width), GameBoard<T> {
private val values: MutableMap<Cell, T?>
init {
val map = mutableMapOf<Cell, T?>()
cells.forEach { map.put(it, null) }
values = map
}
override fun get(cell: Cell): T? = values[cell]
override fun set(cell: Cell, value: T?) {
values[cell] = value
}
override fun filter(predicate: (T?) -> Boolean): Collection<Cell> = values
.filterValues(predicate)
.keys
override fun find(predicate: (T?) -> Boolean): Cell? = values
.entries
.firstOrNull { (_, value) -> predicate(value) }
?.key
override fun any(predicate: (T?) -> Boolean): Boolean = values
.entries
.any { (_, value) -> predicate(value) }
override fun all(predicate: (T?) -> Boolean): Boolean = values
.entries
.all { (_, value) -> predicate(value) }
}

View File

@ -0,0 +1,11 @@
package games.game
import board.Direction
interface Game {
fun initialize()
fun canMove(): Boolean
fun hasWon(): Boolean
fun processMove(direction: Direction)
operator fun get(i: Int, j: Int): Int?
}

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())
}

View File

@ -0,0 +1,98 @@
package games.gameOfFifteen
import board.Cell
import board.Direction
import board.GameBoard
import board.createGameBoard
import games.game.Game
/*
* Implement the Game of Fifteen (https://en.wikipedia.org/wiki/15_puzzle).
* When you finish, you can play the game by executing 'PlayGameOfFifteen'.
*/
fun newGameOfFifteen(initializer: GameOfFifteenInitializer = RandomGameInitializer()): Game {
val initialPermutation = initializer.initialPermutation
val board = createGameBoard<Int?>(4)
board.getAllCells().forEachIndexed { index, cell ->
if (index < initialPermutation.size) {
board[cell] = initialPermutation[index]
}
}
return GameOfFifteen(board)
}
class GameOfFifteen(val board: GameBoard<Int?>) : Game {
override fun initialize() {
}
override fun canMove(): Boolean = !hasWon()
override fun hasWon(): Boolean {
val cells = board.getAllCells()
var previous: Int? = null
for (cell in cells) {
val current = board[cell]
if (cell == cells.first()) {
previous = current
continue
}
if (cell == cells.last() && board[cell] == null) break
if (previous == null || current != previous + 1) return false
previous = current
}
return true
}
override fun processMove(direction: Direction) {
board.moveValues(direction)
}
override fun get(i: Int, j: Int): Int? {
return board[Cell(i, j)]
}
}
fun GameBoard<Int?>.moveValuesInRowOrColumn(rowOrColumn: List<Cell>): Boolean {
val values = rowOrColumn.map { this[it] }
val moved = values.move()
for ((index, cell) in rowOrColumn.withIndex()) {
this[cell] = moved.getOrNull(index)
}
return moved.mapIndexed { index, value -> value != values[index] }.any { it }
}
fun GameBoard<Int?>.moveValues(direction: Direction): Boolean = when (direction) {
Direction.UP -> 1.rangeTo(width)
.map { j -> moveValuesInRowOrColumn(getColumn(width downTo 1, j)) }
.any { it }
Direction.RIGHT -> 1.rangeTo(width)
.map { i -> moveValuesInRowOrColumn(getRow(i, 1..width)) }
.any { it }
Direction.DOWN -> 1.rangeTo(width)
.map { j -> moveValuesInRowOrColumn(getColumn(1..width, j)) }
.any { it }
Direction.LEFT -> 1.rangeTo(width)
.map { i -> moveValuesInRowOrColumn(getRow(i, width downTo 1)) }
.any { it }
}
/**
* Move single element
*/
fun <T : Any> List<T?>.move(): List<T?> {
val mutableList = this.toMutableList()
val nullIndex = mutableList.indexOf(null)
if (nullIndex != -1) {
val previousValue = mutableList[nullIndex - 1]
mutableList[nullIndex] = previousValue
mutableList[nullIndex - 1] = null
return mutableList
}
return mutableList
}

View File

@ -0,0 +1,15 @@
package games.gameOfFifteen
/*
* This function should return the parity of the permutation.
* true - the permutation is even
* false - the permutation is odd
* https://en.wikipedia.org/wiki/Parity_of_a_permutation
* If the game of fifteen is started with the wrong parity, you can't get the correct result
* (numbers sorted in the right order, empty cell at last).
* Thus the initial permutation should be correct.
*/
fun isEven(permutation: List<Int>): Boolean = permutation.flatMapIndexed { i, first ->
((i + 1) until permutation.size).filter { j -> first > permutation[j] }
}.count() % 2 == 0

View File

@ -0,0 +1,23 @@
package games.gameOfFifteen
interface GameOfFifteenInitializer {
/*
* Even permutation of numbers 1..15
* used to initialized the first 15 cells on a board.
* The last cell is empty.
*/
val initialPermutation: List<Int>
}
class RandomGameInitializer : GameOfFifteenInitializer {
/*
* Generate a random permutation from 1 to 15.
* `shuffled()` function might be helpful.
* If the permutation is not even, make it even (for instance,
* by swapping two numbers).
*/
override val initialPermutation by lazy {
(1..15).shuffled()
}
}

View File

@ -0,0 +1,110 @@
// drawing based on https://github.com/bulenkov/2048
package games.ui
import board.Direction
import games.game.Game
import java.awt.*
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.WindowConstants
class PlayGame(val game: Game, val settings: GameSettings) : JPanel() {
init {
isFocusable = true
addKeyListener(object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
if (game.hasWon() == false && game.canMove()) {
val direction = when (e.keyCode) {
KeyEvent.VK_LEFT -> Direction.LEFT
KeyEvent.VK_RIGHT -> Direction.RIGHT
KeyEvent.VK_DOWN -> Direction.DOWN
KeyEvent.VK_UP -> Direction.UP
else -> null
}
if (direction != null) {
game.processMove(direction)
}
}
repaint()
}
})
game.initialize()
}
override fun paint(g: Graphics) {
super.paint(g)
g.color = settings.backgroundColor
g.fillRect(0, 0, this.size.width, this.size.height)
for (y in 1..4) {
for (x in 1..4) {
drawTile(g as Graphics2D, game[y, x] ?: 0, x - 1, y - 1)
}
}
}
private fun offsetCoors(arg: Int): Int {
return arg * (TILES_MARGIN + TILE_SIZE) + TILES_MARGIN
}
private fun drawTile(g: Graphics2D, value: Int, x: Int, y: Int) {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE)
val xOffset = offsetCoors(x)
val yOffset = offsetCoors(y)
g.color = settings.getBackgroundColor(value)
g.fillRoundRect(xOffset, yOffset, TILE_SIZE, TILE_SIZE, 14, 14)
g.color = settings.getForegroundColor(value)
val size = if (value < 100) 36 else if (value < 1000) 32 else 24
val font = Font(FONT_NAME, Font.BOLD, size)
g.font = font
val s = value.toString()
val fm = getFontMetrics(font)
val w = fm.stringWidth(s)
val h = -fm.getLineMetrics(s, g).baselineOffsets[2].toInt()
if (value != 0)
g.drawString(s, xOffset + (TILE_SIZE - w) / 2, yOffset + TILE_SIZE - (TILE_SIZE - h) / 2 - 2)
if (game.hasWon() || game.canMove() == false) {
g.color = Color(255, 255, 255, 30)
g.fillRect(0, 0, width, height)
g.color = Color(78, 139, 202)
g.font = Font(FONT_NAME, Font.BOLD, 48)
if (game.hasWon()) {
g.drawString("You won!", 68, 150)
}
if (!game.canMove()) {
g.drawString("Game over!", 45, 160)
}
}
g.font = Font(FONT_NAME, Font.PLAIN, 18)
}
}
private val FONT_NAME = "Arial"
private val TILE_SIZE = 64
private val TILES_MARGIN = 16
abstract class GameSettings(val name: String, val backgroundColor: Color) {
abstract fun getBackgroundColor(value: Int): Color
abstract fun getForegroundColor(value: Int): Color
}
fun playGame(game: Game, settings: GameSettings) {
with(JFrame()) {
title = settings.name
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
setSize(340, 400)
isResizable = false
add(PlayGame(game, settings))
setLocationRelativeTo(null)
isVisible = true
}
}

View File

@ -0,0 +1,25 @@
package games.ui
import games.game2048.newGame2048
import java.awt.Color
object Game2048Settings : GameSettings("Game 2048", Color(0xbbada0)) {
private val emptyColor = Color(0xcdc1b4)
private val colors: Map<Int, Color> = run {
val colors = listOf(
0xeee4da, 0xede0c8, 0xf2b179, 0xf59563, 0xf67c5f, 0xf65e3b,
0xedcf72, 0xedcc61, 0xedc850, 0xedc53f, 0xedc22e
)
val values: List<Int> = (1..11).map { Math.pow(2.0, it.toDouble()).toInt() }
values.zip(colors.map { Color(it) }).toMap()
}
override fun getBackgroundColor(value: Int) = colors[value] ?: emptyColor
override fun getForegroundColor(value: Int) = if (value < 16) Color(0x776e65) else Color(0xf9f6f2)
}
fun main() {
playGame(newGame2048(), Game2048Settings)
}

View File

@ -0,0 +1,23 @@
package games.ui
import games.gameOfFifteen.newGameOfFifteen
import java.awt.Color
object GameOfFifteenSettings : GameSettings("Game of fifteen", Color(0x909090)) {
private val emptyColor = Color(0x787878)
private val firstColor = Color(0xC8C8C8)
private val secondColor = Color(0xCCCCFF)
private val foregroundColor = Color(0x545AA7)
override fun getBackgroundColor(value: Int) = when {
value == 0 -> emptyColor
((value - 1) / 4 + value % 4) % 2 == 0 -> firstColor
else -> secondColor
}
override fun getForegroundColor(value: Int) = foregroundColor
}
fun main() {
playGame(newGameOfFifteen(), GameOfFifteenSettings)
}