├── .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 | }
--------------------------------------------------------------------------------