Skip to content

Commit f26e302

Browse files
authored
Merge pull request #69 from jetbrains-academy/add-card-trainer
Finish card trainer
2 parents 9f3ed8e + 58221f2 commit f26e302

File tree

12 files changed

+632
-21
lines changed

12 files changed

+632
-21
lines changed

cardTrainerServer/cardTrainerServerCardModel/task.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Finally, create a class `Card` in the `jetbrains.kotlin.course.card.trainer.card
88

99
- it must have two properties in the primary constructor:
1010
- `front` with `Front` type to store capitals;
11-
- `back` with `Back` type to store countries;
11+
- `back` with `Back` type to store countries.
1212

1313
If you have any difficulties, **hints will help you solve this task**.
1414

cardTrainerServer/cardTrainerServerCardService/task-info.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ files:
1919
visible: false
2020
- name: src/main/kotlin/jetbrains/kotlin/course/card/trainer/util/Countries.kt
2121
visible: false
22+
- name: test/Tests.kt
23+
visible: false
24+
- name: test/CardTestClass.kt
25+
visible: false
26+
- name: test/CardSequenceGeneratorTestClass.kt
27+
visible: false
28+
- name: test/CardServiceTestClass.kt
29+
visible: false

cardTrainerServer/cardTrainerServerCardService/test/CardServiceTestClass.kt

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@ internal val startNewGameMethod = TestMethod(
1212
returnTypeJava = "Card",
1313
)
1414

15+
internal val generateNewCardsSequenceMethod = TestMethod(
16+
name = "generateNewCardsSequence",
17+
returnType = KotlinType(
18+
"List",
19+
params = listOf("jetbrains.kotlin.course.card.trainer.card.Card")
20+
),
21+
returnTypeJava = "List",
22+
visibility = Visibility.PRIVATE
23+
)
24+
25+
internal val cardsVariable = Variable(
26+
name = "cards",
27+
javaType = "List",
28+
kotlinType = KotlinType(
29+
"MutableList",
30+
params = listOf("jetbrains.kotlin.course.card.trainer.card.Card")
31+
),
32+
// Because it is inside companion object
33+
visibility = Visibility.PRIVATE,
34+
mutability = VariableMutability.VAR,
35+
isStatic = true,
36+
)
37+
1538
internal val cardServiceTestClass = TestClass(
1639
"CardService",
1740
"jetbrains.kotlin.course.card.trainer.card",
@@ -27,19 +50,7 @@ internal val cardServiceTestClass = TestClass(
2750
mutability = VariableMutability.VAL,
2851
isStatic = true,
2952
),
30-
31-
Variable(
32-
name = "cards",
33-
javaType = "List",
34-
kotlinType = KotlinType(
35-
"MutableList",
36-
params = listOf("jetbrains.kotlin.course.card.trainer.card.Card")
37-
),
38-
// Because it is inside companion object
39-
visibility = Visibility.PRIVATE,
40-
mutability = VariableMutability.VAR,
41-
isStatic = true,
42-
),
53+
cardsVariable
4354
),
4455
customMethods = listOf(
4556
getNextCardMethod,
@@ -50,4 +61,7 @@ internal val cardServiceTestClass = TestClass(
5061
internal val cardServiceCompanionTestClass = TestClass(
5162
"CardService\$Companion",
5263
"jetbrains.kotlin.course.card.trainer.card",
64+
customMethods = listOf(
65+
generateNewCardsSequenceMethod
66+
)
5367
)

cardTrainerServer/cardTrainerServerCardService/test/Tests.kt

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import jetbrains.kotlin.course.card.trainer.util.countries
12
import models.ConstructorGetter
3+
import models.TestMethodInvokeData
24
import org.junit.jupiter.api.Test
5+
import org.junit.jupiter.api.assertThrows
6+
import java.lang.reflect.Field
7+
import kotlin.jvm.internal.DefaultConstructorMarker
38

49
class Test {
5-
// TODO: test randomCardGenerator,getNextCard and startNewGame
610
@Test
711
fun cardServiceTest() {
812
val clazz = cardServiceTestClass.checkBaseDefinition()
9-
cardServiceCompanionTestClass.checkBaseDefinition()
13+
val companion = cardServiceCompanionTestClass.checkBaseDefinition()
14+
cardServiceCompanionTestClass.checkDeclaredMethods(companion)
1015
cardServiceTestClass.checkFieldsDefinition(clazz, false)
1116
cardServiceTestClass.checkConstructors(
1217
clazz,
@@ -16,6 +21,101 @@ class Test {
1621
)
1722
cardServiceTestClass.checkDeclaredMethods(clazz)
1823
}
24+
25+
private fun getCardsField(invokeData: TestMethodInvokeData): Field {
26+
val cardsField = invokeData.clazz.declaredFields.find { it.name == cardsVariable.name }
27+
assert(cardsField != null) { "Can not find field ${cardsVariable.name}" }
28+
cardsField!!.isAccessible = true
29+
return cardsField
30+
}
31+
32+
@Test
33+
fun getNextCardMethodTest() {
34+
val invokeData = TestMethodInvokeData(cardServiceTestClass, getNextCardMethod)
35+
val cardsField = getCardsField(invokeData)
36+
37+
val n = countries.size
38+
val previousCards = mutableListOf<String>()
39+
40+
var cardsRes = cardsField.get(invokeData.instance).toString()
41+
assert(cardsRes.parseCards().size - 1 == countries.size) { "The initial number of cards in ${cardsVariable.name} should be the same with the number of pairs in the countries map" }
42+
repeat(n) { i ->
43+
val card = cardServiceTestClass.invokeMethodWithoutArgs(
44+
clazz = invokeData.clazz,
45+
instance = invokeData.instance,
46+
javaMethod = invokeData.method,
47+
).toString()
48+
assert(card !in previousCards) { "${getNextCardMethod.name} should return new card each call" }
49+
previousCards.add(card)
50+
51+
cardsRes = cardsField.get(invokeData.instance).toString()
52+
assert(cardsRes.parseCards().size == countries.size - i) { "After each call of ${getNextCardMethod.name} you need to drop one card from ${cardsVariable.name}" }
53+
}
54+
cardsRes = cardsField.get(invokeData.instance).toString()
55+
assert(cardsRes == "[]") { "If we run ${getNextCardMethod.name} $n times the list of cards in ${cardsVariable.name} should be empty" }
56+
57+
assertThrows<java.lang.reflect.InvocationTargetException>("You need to throw an error if ${cardsVariable.name} is empty and you try to run ${getNextCardMethod.name} method") {
58+
cardServiceTestClass.invokeMethodWithoutArgs(
59+
clazz = invokeData.clazz,
60+
instance = invokeData.instance,
61+
javaMethod = invokeData.method,
62+
).toString()
63+
}
64+
}
65+
66+
private fun String.parseCards() = drop(1).dropLast(1).split("Card(")
67+
68+
@Test
69+
fun randomCardGeneratorMethodTest() {
70+
val constructor = DefaultConstructorMarker::class.java.declaredConstructors.first()
71+
constructor.isAccessible = true
72+
val defaultConstructorMarkerInstance = constructor.newInstance()
73+
74+
val invokeData = TestMethodInvokeData(
75+
cardServiceCompanionTestClass,
76+
generateNewCardsSequenceMethod,
77+
constructorArgumentsTypes = listOf(DefaultConstructorMarker::class.java),
78+
constructorArguments = listOf(defaultConstructorMarkerInstance)
79+
)
80+
val previousSequences = mutableListOf<String>()
81+
val n = 100
82+
repeat(n) {
83+
val cardsSequence = cardServiceCompanionTestClass.invokeMethodWithoutArgs(
84+
clazz = invokeData.clazz,
85+
instance = invokeData.instance,
86+
javaMethod = invokeData.method,
87+
isPrivate = true
88+
).toString()
89+
assert(cardsSequence !in previousSequences) { "You need to generate different card sequences with ${generateNewCardsSequenceMethod.name} method" }
90+
previousSequences.add(cardsSequence)
91+
val cards = cardsSequence.parseCards()
92+
assert(cards.size - 1 == countries.keys.size) { "The number of generated card sequence should be the same with the number of pairs in the countries maps" }
93+
assert(cards.toSet().size == cards.size) { "All pairs in the generated card sequence should be different" }
94+
}
95+
}
96+
97+
@Test
98+
fun startNewGameMethodTest() {
99+
val invokeData = TestMethodInvokeData(cardServiceTestClass, startNewGameMethod)
100+
val cardsField = getCardsField(invokeData)
101+
102+
val previousSequences = mutableListOf<String>()
103+
val n = 100
104+
repeat(n) {
105+
cardServiceTestClass.invokeMethodWithoutArgs(
106+
clazz = invokeData.clazz,
107+
instance = invokeData.instance,
108+
javaMethod = invokeData.method,
109+
).toString()
110+
111+
val cardsRes = cardsField.get(invokeData.instance).toString()
112+
assert(cardsRes !in previousSequences) { "You need to generate different card sequences with ${generateNewCardsSequenceMethod.name} method when call ${startNewGameMethod.name} method" }
113+
previousSequences.add(cardsRes)
114+
assert(cardsRes.parseCards().size == countries.keys.size) { "When you call ${startNewGameMethod.name} you need to drop one card from ${cardsVariable.name}" }
115+
}
116+
117+
}
118+
19119
@Test
20120
fun cardSequenceGeneratorTestClassTest() {
21121
val clazz = cardSequenceGeneratorTestClass.checkBaseDefinition()
@@ -33,9 +133,9 @@ class Test {
33133
listOf(
34134
ConstructorGetter(
35135
parameterTypes = listOf(String::class.java, String::class.java),
36-
toAddDefaultConstructorMarker=true
136+
toAddDefaultConstructorMarker = true
37137
),
38138
)
39139
)
40140
}
41-
}
141+
}

cardTrainerServer/cardTrainerServerFinishGame/task-info.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,15 @@ files:
1919
visible: false
2020
- name: src/main/kotlin/jetbrains/kotlin/course/card/trainer/util/Countries.kt
2121
visible: false
22+
- name: test/Tests.kt
23+
visible: false
24+
- name: test/CardTestClass.kt
25+
visible: false
26+
- name: test/CardSequenceGeneratorTestClass.kt
27+
visible: false
28+
- name: test/CardServiceTestClass.kt
29+
visible: false
30+
- name: test/StatTestClass.kt
31+
visible: false
32+
- name: test/StatServiceTestClass.kt
33+
visible: false
Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,52 @@
1-
TODO:
2-
- CardService
3-
- Stat + StatService
1+
Wow! You've almost finished the project! This is the last step.
2+
On this step you need to add statistics to the game.
3+
4+
First of all, you need to add a class `Stat` in the `jjetbrains.kotlin.course.card.trainer.stat` package to store statics:
5+
6+
- it must have two properties in the primary constructor:
7+
- `knownBacks` with `List<Back>` type to store known countries;
8+
- `unknownBacks` with `List<Back>` type to store unknown countries.
9+
10+
Next, you need to implement several methods in already defined class `StatService`
11+
in the package `jetbrains.kotlin.course.card.trainer.card`:
12+
- add a `history` property into the companion object with type `MutableList<Stat>` and initialize it with an empty list;
13+
- implement the method `save` that `adds` new `Stat` into `history`;
14+
- implement the method `getHistory` that returns _reversed_ `history`.
15+
16+
If you have any difficulties, **hints will help you solve this task**.
17+
18+
----
19+
20+
### Hints
21+
22+
<div class="hint" title="How to create an empty mutable list">
23+
24+
To create a new _mutable_ list, you can use [`mutableListOf`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/mutable-list-of.html):
25+
26+
```kotlin
27+
val mutableList = mutableListOf<Int>(1, 2, 3)
28+
```
29+
</div>
30+
31+
<div class="hint" title="The `reversed` built-in function">
32+
33+
If you need to get a list in which the elements are in reverse order,
34+
you can loop through the elements of the original list from the end to the beginning and
35+
return a new list, or use the [`reversed`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/reversed.html) built-in function:
36+
37+
```kotlin
38+
val numbers = listOf(1, 2, 3, 4)
39+
val reversedList = mutableListOf<Int>()
40+
for (i in numbers.size - 1 downTo 0) {
41+
reversedList.add(numbers[i])
42+
}
43+
println(reversedList) // [4, 3, 2, 1]
44+
```
45+
46+
is the **same** with
47+
```kotlin
48+
val numbers = listOf(1, 2, 3, 4)
49+
val reversedList = numbers.reversed()
50+
println(reversedList) // [4, 3, 2, 1]
51+
```
52+
</div>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import models.ClassType
2+
import models.KotlinType
3+
import models.TestClass
4+
import models.TestMethod
5+
6+
internal val cardSequenceGeneratorTestClass = TestClass(
7+
"CardSequenceGenerator",
8+
"jetbrains.kotlin.course.card.trainer.card",
9+
classType = ClassType.SAM_INTERFACE,
10+
customMethods = listOf(
11+
TestMethod(
12+
"generateCards",
13+
KotlinType(
14+
"List",
15+
params = listOf("jetbrains.kotlin.course.card.trainer.card.Card")
16+
),
17+
returnTypeJava = "List",
18+
),
19+
),
20+
)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import models.*
2+
3+
internal val getNextCardMethod = TestMethod(
4+
name = "getNextCard",
5+
returnType = KotlinType("jetbrains.kotlin.course.card.trainer.card.Card"),
6+
returnTypeJava = "Card",
7+
)
8+
9+
internal val startNewGameMethod = TestMethod(
10+
name = "startNewGame",
11+
returnType = KotlinType("jetbrains.kotlin.course.card.trainer.card.Card"),
12+
returnTypeJava = "Card",
13+
)
14+
15+
internal val generateNewCardsSequenceMethod = TestMethod(
16+
name = "generateNewCardsSequence",
17+
returnType = KotlinType(
18+
"List",
19+
params = listOf("jetbrains.kotlin.course.card.trainer.card.Card")
20+
),
21+
returnTypeJava = "List",
22+
visibility = Visibility.PRIVATE
23+
)
24+
25+
internal val cardsVariable = Variable(
26+
name = "cards",
27+
javaType = "List",
28+
kotlinType = KotlinType(
29+
"MutableList",
30+
params = listOf("jetbrains.kotlin.course.card.trainer.card.Card")
31+
),
32+
// Because it is inside companion object
33+
visibility = Visibility.PRIVATE,
34+
mutability = VariableMutability.VAR,
35+
isStatic = true,
36+
)
37+
38+
internal val cardServiceTestClass = TestClass(
39+
"CardService",
40+
"jetbrains.kotlin.course.card.trainer.card",
41+
declaredFields = listOf(
42+
Variable(
43+
name = "randomCardGenerator",
44+
javaType = "CardSequenceGenerator",
45+
kotlinType = KotlinType(
46+
"jetbrains.kotlin.course.card.trainer.card.CardSequenceGenerator",
47+
),
48+
// Because it is inside companion object
49+
visibility = Visibility.PRIVATE,
50+
mutability = VariableMutability.VAL,
51+
isStatic = true,
52+
),
53+
cardsVariable
54+
),
55+
customMethods = listOf(
56+
getNextCardMethod,
57+
startNewGameMethod
58+
),
59+
)
60+
61+
internal val cardServiceCompanionTestClass = TestClass(
62+
"CardService\$Companion",
63+
"jetbrains.kotlin.course.card.trainer.card",
64+
customMethods = listOf(
65+
generateNewCardsSequenceMethod
66+
)
67+
)

0 commit comments

Comments
 (0)