Added obligatory assignments
This commit is contained in:
43
src/assignment/week2/mastermind/evaluateGuess.kt
Normal file
43
src/assignment/week2/mastermind/evaluateGuess.kt
Normal 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) }
|
||||
|
57
src/assignment/week2/mastermind/playMastermind.kt
Normal file
57
src/assignment/week2/mastermind/playMastermind.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/assignment/week3/nicestring/NiceString.kt
Normal file
25
src/assignment/week3/nicestring/NiceString.kt
Normal 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 }
|
24
src/assignment/week3/taxipark/TaxiPark.kt
Normal file
24
src/assignment/week3/taxipark/TaxiPark.kt
Normal 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)
|
||||
}
|
80
src/assignment/week3/taxipark/TaxiParkTask.kt
Normal file
80
src/assignment/week3/taxipark/TaxiParkTask.kt
Normal 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
|
||||
}
|
41
src/assignment/week4/board/Board.kt
Normal file
41
src/assignment/week4/board/Board.kt
Normal 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
|
||||
}
|
80
src/assignment/week4/board/BoardImpl.kt
Normal file
80
src/assignment/week4/board/BoardImpl.kt
Normal 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) }
|
||||
}
|
11
src/assignment/week5/games/game/Game.kt
Normal file
11
src/assignment/week5/games/game/Game.kt
Normal 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?
|
||||
}
|
90
src/assignment/week5/games/game2048/Game2048.kt
Normal file
90
src/assignment/week5/games/game2048/Game2048.kt
Normal 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 }
|
||||
}
|
41
src/assignment/week5/games/game2048/Game2048Helper.kt
Normal file
41
src/assignment/week5/games/game2048/Game2048Helper.kt
Normal 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
|
||||
}
|
||||
|
29
src/assignment/week5/games/game2048/Game2048Initializer.kt
Normal file
29
src/assignment/week5/games/game2048/Game2048Initializer.kt
Normal 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())
|
||||
}
|
98
src/assignment/week5/games/gameOfFifteen/GameOfFifteen.kt
Normal file
98
src/assignment/week5/games/gameOfFifteen/GameOfFifteen.kt
Normal 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
|
||||
}
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
110
src/assignment/week5/games/ui/PlayGame.kt
Normal file
110
src/assignment/week5/games/ui/PlayGame.kt
Normal 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
|
||||
}
|
||||
}
|
25
src/assignment/week5/games/ui/PlayGame2048.kt
Normal file
25
src/assignment/week5/games/ui/PlayGame2048.kt
Normal 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)
|
||||
}
|
23
src/assignment/week5/games/ui/PlayGameOfFifteen.kt
Normal file
23
src/assignment/week5/games/ui/PlayGameOfFifteen.kt
Normal 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)
|
||||
}
|
Reference in New Issue
Block a user