diff --git a/src/assignment/week2/mastermind/evaluateGuess.kt b/src/assignment/week2/mastermind/evaluateGuess.kt new file mode 100644 index 0000000..fc19c1f --- /dev/null +++ b/src/assignment/week2/mastermind/evaluateGuess.kt @@ -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 = secret.map { CharMatch(it, null) } + diff --git a/src/assignment/week2/mastermind/playMastermind.kt b/src/assignment/week2/mastermind/playMastermind.kt new file mode 100644 index 0000000..8de5c72 --- /dev/null +++ b/src/assignment/week2/mastermind/playMastermind.kt @@ -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) + } + } + } +} diff --git a/src/assignment/week3/nicestring/NiceString.kt b/src/assignment/week3/nicestring/NiceString.kt new file mode 100644 index 0000000..8e5d0d2 --- /dev/null +++ b/src/assignment/week3/nicestring/NiceString.kt @@ -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 } diff --git a/src/assignment/week3/taxipark/TaxiPark.kt b/src/assignment/week3/taxipark/TaxiPark.kt new file mode 100644 index 0000000..81dbde0 --- /dev/null +++ b/src/assignment/week3/taxipark/TaxiPark.kt @@ -0,0 +1,24 @@ +package taxipark + +data class TaxiPark( + val allDrivers: Set, + val allPassengers: Set, + val trips: List) + +data class Driver(val name: String) +data class Passenger(val name: String) + +data class Trip( + val driver: Driver, + val passengers: Set, + // 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) +} \ No newline at end of file diff --git a/src/assignment/week3/taxipark/TaxiParkTask.kt b/src/assignment/week3/taxipark/TaxiParkTask.kt new file mode 100644 index 0000000..ee8cdef --- /dev/null +++ b/src/assignment/week3/taxipark/TaxiParkTask.kt @@ -0,0 +1,80 @@ +package taxipark + +/* + * Task #1. Find all the drivers who performed no trips. + */ +fun TaxiPark.findFakeDrivers(): Set = + 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 = + 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 = + 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 = 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 +} \ No newline at end of file diff --git a/src/assignment/week4/board/Board.kt b/src/assignment/week4/board/Board.kt new file mode 100644 index 0000000..03527be --- /dev/null +++ b/src/assignment/week4/board/Board.kt @@ -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 + + fun getRow(i: Int, jRange: IntProgression): List + fun getColumn(iRange: IntProgression, j: Int): List + + fun Cell.getNeighbour(direction: Direction): Cell? +} + +interface GameBoard : SquareBoard { + + operator fun get(cell: Cell): T? + operator fun set(cell: Cell, value: T?) + + fun filter(predicate: (T?) -> Boolean): Collection + fun find(predicate: (T?) -> Boolean): Cell? + fun any(predicate: (T?) -> Boolean): Boolean + fun all(predicate: (T?) -> Boolean): Boolean +} \ No newline at end of file diff --git a/src/assignment/week4/board/BoardImpl.kt b/src/assignment/week4/board/BoardImpl.kt new file mode 100644 index 0000000..e490861 --- /dev/null +++ b/src/assignment/week4/board/BoardImpl.kt @@ -0,0 +1,80 @@ +package board + +import kotlin.collections.component1 + +fun createSquareBoard(width: Int): SquareBoard = SquareBoardImpl(width) +fun createGameBoard(width: Int): GameBoard = GameBoardImpl(width) + +open class SquareBoardImpl(override val width: Int) : SquareBoard { + + protected val cells: List + + 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 = cells + + override fun getRow(i: Int, jRange: IntProgression): List { + 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 { + 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(width: Int) : SquareBoardImpl(width), GameBoard { + private val values: MutableMap + + init { + val map = mutableMapOf() + 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 = 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) } +} diff --git a/src/assignment/week5/games/game/Game.kt b/src/assignment/week5/games/game/Game.kt new file mode 100644 index 0000000..1970b84 --- /dev/null +++ b/src/assignment/week5/games/game/Game.kt @@ -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? +} diff --git a/src/assignment/week5/games/game2048/Game2048.kt b/src/assignment/week5/games/game2048/Game2048.kt new file mode 100644 index 0000000..916edd7 --- /dev/null +++ b/src/assignment/week5/games/game2048/Game2048.kt @@ -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 = RandomGame2048Initializer): Game = + Game2048(initializer) + +class Game2048(private val initializer: Game2048Initializer) : Game { + private val board = createGameBoard(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.addNewValue(initializer: Game2048Initializer) { + 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.moveValuesInRowOrColumn(rowOrColumn: List): 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.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 } +} diff --git a/src/assignment/week5/games/game2048/Game2048Helper.kt b/src/assignment/week5/games/game2048/Game2048Helper.kt new file mode 100644 index 0000000..208b4eb --- /dev/null +++ b/src/assignment/week5/games/game2048/Game2048Helper.kt @@ -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 List.moveAndMergeEqual(merge: (T) -> T): List { + val mutableList = mutableListOf() + 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 +} + diff --git a/src/assignment/week5/games/game2048/Game2048Initializer.kt b/src/assignment/week5/games/game2048/Game2048Initializer.kt new file mode 100644 index 0000000..f2afef0 --- /dev/null +++ b/src/assignment/week5/games/game2048/Game2048Initializer.kt @@ -0,0 +1,29 @@ +package games.game2048 + +import board.Cell +import board.GameBoard +import kotlin.random.Random + +interface Game2048Initializer { + /* + * Specifies the cell and the value that should be added to this cell. + */ + fun nextValue(board: GameBoard): Pair? +} + +object RandomGame2048Initializer : Game2048Initializer { + 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): Pair? = board + .filter { it == null } + .randomOrNull() + ?.to(generateRandomStartValue()) +} \ No newline at end of file diff --git a/src/assignment/week5/games/gameOfFifteen/GameOfFifteen.kt b/src/assignment/week5/games/gameOfFifteen/GameOfFifteen.kt new file mode 100644 index 0000000..1ea3aeb --- /dev/null +++ b/src/assignment/week5/games/gameOfFifteen/GameOfFifteen.kt @@ -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(4) + board.getAllCells().forEachIndexed { index, cell -> + if (index < initialPermutation.size) { + board[cell] = initialPermutation[index] + } + } + return GameOfFifteen(board) +} + +class GameOfFifteen(val board: GameBoard) : 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.moveValuesInRowOrColumn(rowOrColumn: List): 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.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 List.move(): List { + 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 +} diff --git a/src/assignment/week5/games/gameOfFifteen/GameOfFifteenHelper.kt b/src/assignment/week5/games/gameOfFifteen/GameOfFifteenHelper.kt new file mode 100644 index 0000000..2d6a549 --- /dev/null +++ b/src/assignment/week5/games/gameOfFifteen/GameOfFifteenHelper.kt @@ -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): Boolean = permutation.flatMapIndexed { i, first -> + ((i + 1) until permutation.size).filter { j -> first > permutation[j] } +}.count() % 2 == 0 diff --git a/src/assignment/week5/games/gameOfFifteen/GameOfFifteenInitializer.kt b/src/assignment/week5/games/gameOfFifteen/GameOfFifteenInitializer.kt new file mode 100644 index 0000000..c6e128a --- /dev/null +++ b/src/assignment/week5/games/gameOfFifteen/GameOfFifteenInitializer.kt @@ -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 +} + +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() + } +} + diff --git a/src/assignment/week5/games/ui/PlayGame.kt b/src/assignment/week5/games/ui/PlayGame.kt new file mode 100644 index 0000000..e9fa44f --- /dev/null +++ b/src/assignment/week5/games/ui/PlayGame.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/assignment/week5/games/ui/PlayGame2048.kt b/src/assignment/week5/games/ui/PlayGame2048.kt new file mode 100644 index 0000000..175fd3a --- /dev/null +++ b/src/assignment/week5/games/ui/PlayGame2048.kt @@ -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 = run { + val colors = listOf( + 0xeee4da, 0xede0c8, 0xf2b179, 0xf59563, 0xf67c5f, 0xf65e3b, + 0xedcf72, 0xedcc61, 0xedc850, 0xedc53f, 0xedc22e + ) + + val values: List = (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) +} \ No newline at end of file diff --git a/src/assignment/week5/games/ui/PlayGameOfFifteen.kt b/src/assignment/week5/games/ui/PlayGameOfFifteen.kt new file mode 100644 index 0000000..6f56d0f --- /dev/null +++ b/src/assignment/week5/games/ui/PlayGameOfFifteen.kt @@ -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) +} \ No newline at end of file