├── .idea └── uiDesigner.xml ├── src ├── App.kt ├── Card.kt ├── Deck.kt ├── FoundationPile.kt ├── GameModel.kt ├── GamePresenter.kt ├── GameView.kt └── TableauPile.kt └── test ├── GameTest.kt └── TableauPileTest.kt /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/App.kt: -------------------------------------------------------------------------------- 1 | fun main(args: Array) { 2 | GameModel.resetGame() 3 | GamePresenter.onDeckTap() 4 | GameModel.debugPrint() 5 | } 6 | -------------------------------------------------------------------------------- /src/Card.kt: -------------------------------------------------------------------------------- 1 | val clubs = "Clubs" 2 | val diamonds = "Diamonds" 3 | val hearts = "Hearts" 4 | val spades = "Spades" 5 | val redSuits = arrayOf(diamonds, hearts) 6 | val blackSuits = arrayOf(clubs, spades) 7 | val cardsMap = mapOf(0 to "A", 1 to "2", 2 to "3" , 3 to "4", 4 to "5", 5 to "6", 6 to "7", 7 to "8", 8 to "9", 9 to "10", 10 to "J", 11 to "Q", 12 to "K") 8 | 9 | data class Card(val value: Int, val suit: String, var faceUp: Boolean = false) { 10 | override fun toString(): String = 11 | if (faceUp) "${cardsMap[value]}".padEnd(2) + "${getSuitChar(suit)}" else "xxx" 12 | 13 | private fun getSuitChar(suit: String): String = when (suit) { 14 | diamonds -> "\u2666" 15 | clubs -> "\u2663" 16 | hearts -> "\u2665" 17 | spades -> "\u2660" 18 | else -> "incorrect suit" 19 | } 20 | } -------------------------------------------------------------------------------- /src/Deck.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | 3 | class Deck { 4 | val cards = Array(52, { Card(it % 13, getSuit(it)) }) 5 | var cardsInDeck: MutableList = cards.toMutableList() 6 | 7 | private fun getSuit(i: Int) = when(i / 13) { 8 | 0 -> clubs 9 | 1 -> diamonds 10 | 2 -> hearts 11 | else -> spades 12 | } 13 | 14 | fun drawCard(): Card = cardsInDeck.removeAt(0) 15 | 16 | fun reset() { 17 | cardsInDeck = cards.toMutableList() 18 | cardsInDeck.forEach { it.faceUp = false } 19 | Collections.shuffle(cardsInDeck) 20 | } 21 | } -------------------------------------------------------------------------------- /src/FoundationPile.kt: -------------------------------------------------------------------------------- 1 | class FoundationPile(val suit: String) { 2 | val cards: MutableList = mutableListOf() 3 | 4 | fun reset() { 5 | cards.clear() 6 | } 7 | 8 | fun removeCard(card: Card) { 9 | cards.remove(card) 10 | } 11 | 12 | fun addCard(card: Card): Boolean { 13 | var nextValue = 0 14 | if (cards.size > 0) { 15 | nextValue = cards.last().value + 1 16 | } 17 | if (card.suit == suit && card.value == nextValue) { 18 | cards.add(card) 19 | return true 20 | } 21 | return false 22 | } 23 | } -------------------------------------------------------------------------------- /src/GameModel.kt: -------------------------------------------------------------------------------- 1 | object GameModel { 2 | val deck = Deck() 3 | val wastePile: MutableList = mutableListOf() 4 | val foundationPiles = arrayOf(FoundationPile(clubs), FoundationPile(diamonds), 5 | FoundationPile(hearts), FoundationPile(spades)) 6 | val tableauPiles = Array(7, { TableauPile() }) 7 | 8 | fun resetGame() { 9 | wastePile.clear() 10 | foundationPiles.forEach { it.reset() } 11 | deck.reset() 12 | 13 | tableauPiles.forEachIndexed { i, tableauPile -> 14 | val cardsInPile: MutableList = Array(i + 1, { deck.drawCard() }).toMutableList() 15 | tableauPiles[i] = TableauPile(cardsInPile) 16 | } 17 | } 18 | 19 | fun onDeckTap() { 20 | if (deck.cardsInDeck.size > 0) { 21 | val card = deck.drawCard() 22 | card.faceUp = true 23 | wastePile.add(card) 24 | } else { 25 | deck.cardsInDeck = wastePile.toMutableList() 26 | wastePile.clear() 27 | } 28 | } 29 | 30 | fun onWasteTap() { 31 | if (wastePile.size > 0) { 32 | val card = wastePile.last() 33 | if (playCard(card)) { 34 | wastePile.remove(card) 35 | } 36 | } 37 | } 38 | 39 | fun onFoundationTap(foundationIndex: Int) { 40 | val foundationPile = foundationPiles[foundationIndex] 41 | if (foundationPile.cards.size > 0) { 42 | val card = foundationPile.cards.last() 43 | if (playCard(card)) { 44 | foundationPile.removeCard(card) 45 | } 46 | } 47 | } 48 | 49 | fun onTableauTap(tableauIndex: Int, cardIndex: Int) { 50 | val tableauPile = tableauPiles[tableauIndex] 51 | if (tableauPile.cards.size > 0) { 52 | val cards = tableauPile.cards.subList(cardIndex, tableauPile.cards.lastIndex + 1) 53 | if (playCards(cards)) { 54 | tableauPile.removeCards(cardIndex) 55 | } 56 | } 57 | } 58 | 59 | private fun playCards(cards: MutableList): Boolean { 60 | if (cards.size == 1) { 61 | return playCard(cards.first()) 62 | } else { 63 | tableauPiles.forEach { 64 | if (it.addCards(cards)) { 65 | return true 66 | } 67 | } 68 | } 69 | return false 70 | } 71 | 72 | private fun playCard(card: Card): Boolean { 73 | foundationPiles.forEach { 74 | if (it.addCard(card)) { 75 | return true 76 | } 77 | } 78 | 79 | tableauPiles.forEach { 80 | if (it.addCards(mutableListOf(card))) { 81 | return true 82 | } 83 | } 84 | return false 85 | } 86 | 87 | fun debugPrint() { 88 | var firstLine = if(wastePile.size > 0) "${wastePile.last()}" else "___" 89 | firstLine = firstLine.padEnd(18) 90 | foundationPiles.forEach { 91 | firstLine += if(it.cards.size > 0) "${it.cards.last()}" else "___" 92 | firstLine += " " 93 | } 94 | println(firstLine) 95 | println() 96 | for (i in 0..12) { 97 | var row = "" 98 | tableauPiles.forEach { 99 | row += if (it.cards.size > i) "${it.cards[i]}" else " " 100 | row += " " 101 | } 102 | println(row) 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /src/GamePresenter.kt: -------------------------------------------------------------------------------- 1 | object GamePresenter { 2 | var view: GameView? = null 3 | 4 | fun setGameView(gameView: GameView) { 5 | view = gameView 6 | } 7 | 8 | fun onDeckTap() { 9 | GameModel.onDeckTap() 10 | view?.update() 11 | } 12 | 13 | fun onWasteTap() { 14 | GameModel.onWasteTap() 15 | view?.update() 16 | } 17 | 18 | fun onFoundationTap(foundationIndex: Int) { 19 | GameModel.onFoundationTap(foundationIndex) 20 | view?.update() 21 | } 22 | 23 | fun onTableauTap(tableauIndex: Int, cardIndex: Int) { 24 | GameModel.onTableauTap(tableauIndex, cardIndex) 25 | view?.update() 26 | } 27 | } -------------------------------------------------------------------------------- /src/GameView.kt: -------------------------------------------------------------------------------- 1 | interface GameView { 2 | fun update(gameModel: GameModel = GameModel) 3 | } -------------------------------------------------------------------------------- /src/TableauPile.kt: -------------------------------------------------------------------------------- 1 | class TableauPile(var cards: MutableList = mutableListOf()) { 2 | 3 | init { 4 | if (cards.size > 0) 5 | cards.last().faceUp = true 6 | } 7 | 8 | fun addCards(newCards: MutableList): Boolean { 9 | if (cards.size > 0) { 10 | if (newCards.first().value == cards.last().value - 1 && 11 | suitCheck(newCards.first(), cards.last())) { 12 | cards.addAll(newCards) 13 | return true 14 | } 15 | } else if (newCards.first().value == 12) { 16 | cards.addAll(newCards) 17 | return true 18 | } 19 | return false 20 | } 21 | 22 | fun removeCards(tappedIndex: Int) { 23 | for (i in tappedIndex..cards.lastIndex) { 24 | cards.removeAt(tappedIndex) 25 | } 26 | if (cards.size > 0) { 27 | cards.last().faceUp = true 28 | } 29 | } 30 | 31 | private fun suitCheck(c1: Card, c2: Card): Boolean { 32 | if ((redSuits.contains(c1.suit) && blackSuits.contains(c2.suit)) || 33 | (blackSuits.contains(c1.suit) && redSuits.contains(c2.suit))) { 34 | return true 35 | } 36 | return false 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /test/GameTest.kt: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | 3 | class GameTest { 4 | 5 | @Test 6 | fun kingInFirstFoundationPile() { 7 | // arrange 8 | var numGames = 0 9 | val maxGames = 10000 10 | 11 | // act 12 | for (i in 1..maxGames) { 13 | numGames++ 14 | GameModel.resetGame() 15 | for (j in 1..100) { 16 | GamePresenter.onDeckTap() 17 | GamePresenter.onWasteTap() 18 | GameModel.tableauPiles.forEachIndexed { index, tableauPile -> 19 | GamePresenter.onTableauTap(index, tableauPile.cards.lastIndex) 20 | } 21 | } 22 | if (GameModel.foundationPiles[0].cards.size == 13) { 23 | break 24 | } 25 | } 26 | 27 | // assert 28 | GameModel.debugPrint() 29 | println("# Games: $numGames") 30 | assert(numGames < maxGames) 31 | } 32 | } -------------------------------------------------------------------------------- /test/TableauPileTest.kt: -------------------------------------------------------------------------------- 1 | import org.junit.Test 2 | 3 | import org.junit.Assert.* 4 | 5 | class TableauPileTest { 6 | @Test 7 | fun addCards() { 8 | // arrange 9 | val tableauPile = TableauPile(mutableListOf(Card(12, spades))) 10 | val cards = mutableListOf(Card(11, hearts)) 11 | 12 | // act 13 | tableauPile.addCards(cards) 14 | 15 | // assert 16 | assertEquals(2, tableauPile.cards.size) 17 | } 18 | 19 | @Test 20 | fun removeCards() { 21 | // arrange 22 | val tableauPile = TableauPile(mutableListOf(Card(4, clubs), Card(3, diamonds), Card(2,spades))) 23 | 24 | // act 25 | tableauPile.removeCards(1) 26 | 27 | // assert 28 | assertEquals(mutableListOf(Card(4, clubs, true)), tableauPile.cards) 29 | } 30 | 31 | } --------------------------------------------------------------------------------