Improvements

try to make it run on Android (still unsuccessful)
This commit is contained in:
2024-08-21 00:37:29 +02:00
parent 9175e54c69
commit 082899152f
34 changed files with 84 additions and 20 deletions

View File

@@ -0,0 +1,27 @@
package ch.dissem.yaep.domain
abstract class ClueTest {
protected val size = 6
protected fun createGrid(
selection: (Item<ItemClass<*>>) -> Item<ItemClass<*>>? = { it }
) = Grid(
ItemClass.randomClasses(size)
.map {
it.randomItems(size).map { item -> Item(item) }
}
.map { row ->
GameRow(
category = row.first().itemType.companion,
options = row,
cells = row.map {
GameCell(
selection = selection(it),
solution = it,
options = mutableListOf()
)
}
)
}
)
}

View File

@@ -0,0 +1,169 @@
package ch.dissem.yaep.domain
import ch.dissem.yaep.domain.Animal.*
import ch.dissem.yaep.domain.Nationality.*
import ch.dissem.yaep.domain.Profession.*
import ch.dissem.yaep.domain.PuzzleSolution.SOLVABLE
import ch.tutteli.atrium.api.fluent.en_GB.feature
import ch.tutteli.atrium.api.fluent.en_GB.toBeGreaterThan
import ch.tutteli.atrium.api.fluent.en_GB.toBeLessThan
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.measureTime
class GameTest {
@Test
fun `ensure generated games are solvable`() {
val tries = 2000
var fastest = 500.milliseconds
var slowest = 0.milliseconds
var total = 0.milliseconds
var most = 0
var least = 1000
var totalClues = 0
for (i in 1..tries) {
val game: Game
val time = measureTime {
game = generateGame()
}
println("Generated game #$i in ${time.inWholeMilliseconds}ms")
val solvable = solve(game.grid, game.clues)
if (solvable != SOLVABLE) {
println("Puzzle:\n$game")
}
expect(solvable).toEqual(SOLVABLE)
expect(time).toBeLessThan(500.milliseconds)
if (time < fastest) {
fastest = time
}
if (time > slowest) {
slowest = time
}
total += time
if (game.clues.size > most){
most = game.clues.size
}
if (game.clues.size < least){
least = game.clues.size
}
totalClues += game.clues.size
}
println("Slowest: $slowest")
println("Fastest: $fastest")
println("Average: ${total / tries}")
println("Clues:")
println("Most: $most")
println("Least: $least")
println("Average: ${totalClues / tries}")
}
@Test
fun `ensure generated game is valid`() {
val game: Game
val time = measureTime {
game = generateGame()
}
expect(game) {
feature(Game::areCategoriesValid).toEqual(true)
feature(Game::isValid).toEqual(true)
feature(Game::clues) {
feature(Collection<Clue>::size).toBeGreaterThan(5)
feature(Collection<Clue>::size).toBeLessThan(30)
}
}
println("Clues: ${game.clues.size}")
println("Time: $time")
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
expect(time).toBeLessThan(500.milliseconds)
}
@Test
fun `ensure game can be solved`() {
val professions = listOf(
Item(ASTRONAUT),
Item(FARMER),
Item(SOFTWARE_DEV)
)
val animals = listOf(
Item(SNAIL),
Item(ANT),
Item(OCTOPUS)
)
val nationalities = listOf(
Item(UKRAINE),
Item(CANADA),
Item(SWITZERLAND)
)
val game = Game(
Grid(listOf(
GameRow(
Profession,
professions,
professions.map { GameCell(null, it, professions.toMutableList()) }
),
GameRow(
Animal,
animals,
animals.map { GameCell(null, it, animals.toMutableList()) }
),
GameRow(
Nationality,
nationalities,
nationalities.map { GameCell(null, it, nationalities.toMutableList()) }
)
)),
listOf(
TripletClue(professions[0], animals[1], nationalities[2]),
OrderClue(professions[0], nationalities[1]),
SameColumnClue(animals[1], nationalities[1]),
NeighbourClue(professions[0], professions[1]),
PositionClue(animals[0], 0)
)
)
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
}
@Test
fun `ensure specific game is solvable`() {
val game = Game.parse("""
👩🏿‍⚕️👨🏽‍🎤👩🏿‍⚕️ 👩🏾‍🚀🧑🏿‍🏫
🐜🐕 🐐 🐐
🍉🥭🍐🍇🍍
🧁🍨🍩🍰🥧
🇨🇭🇬🇧🇯🇵🇺🇦🇬🇧🇨🇦
🍷🧃🍺🧃
* ZEBRA is between the neighbours PIE and PEAR to both sides
* WINE is at position 0
* SLOTH is between the neighbours ZEBRA and COFFEE to both sides
* ICE_CREAM is left of MANGO
* SWITZERLAND is at position 0
* PIE is at position 4
* SCIENTIST is between the neighbours ASTRONAUT and PEAR to both sides
* ROCK_STAR is between the neighbours SNAIL and ANT to both sides
* SNAIL is between the neighbours ROCK_STAR and TEA to both sides
* SOFTWARE_DEV is left of SLOTH
* SOFTWARE_DEV is left of HEALTH_WORKER
* MILK is between the neighbours CUSTARD and ZEBRA to both sides
* SLOTH is between the neighbours CUSTARD and CAKE to both sides
* SPAIN is between the neighbours CUSTARD and GRAPES to both sides
* SCIENTIST is between the neighbours SNAIL and SLOTH to both sides
* DOG is between the neighbours CUPCAKE and BEER to both sides
* SNAIL is between the neighbours BANANA and GRAPES to both sides
* SLOTH is between the neighbours GRAPES and CANADA to both sides
* UKRAINE and SCIENTIST are in the same column
* DOG is between the neighbours JAPAN and SWITZERLAND to both sides
* SLOTH is between the neighbours GOAT and TEA to both sides
* ROCK_STAR and UNITED_KINGDOM are in the same column
* ROCK_STAR is next to DOUGHNUT
* PINEAPPLE is between the neighbours TEACHER and GRAPES to both sides
""".trimIndent())
expect(solve(game.grid, game.clues)).toEqual(SOLVABLE)
}
}

View File

@@ -0,0 +1,109 @@
package ch.dissem.yaep.domain
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
class NeighbourClueTest : ClueTest() {
@Test
fun `ensure actual neighbours are valid`() {
val grid = createGrid()
for (ia in 0 until size) {
for (ib in 0 until size) {
for (j in 1 until size) {
val a = grid[ia][j - 1]
val b = grid[ib][j]
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
}
}
}
}
@Test
fun `ensure non-neighbours are invalid`() {
val grid = createGrid()
for (ia in 0 until size) {
for (ib in 0 until size) {
for (ja in 0 until size) {
for (jb in 0 until size) {
if (ja == jb + 1 || ja == jb - 1) {
continue
}
val a = grid[ia][ja]
val b = grid[ib][jb]
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}
}
}
}
@Test
fun `ensure grid with one neighbour not set is considered valid if a neighbour is in the options`() {
val grid = createGrid()
for (ia in 0 until size) {
for (ib in 0 until size) {
for (j in 1 until size) {
val rowA = grid[ia]
val rowB = grid[ib]
val a = rowA[j - 1]
val b = rowB[j]
rowA.forEach { it.selection = null; it.options.clear() }
rowB.forEach { it.selection = null; it.options.clear() }
a.selection = null
a.options.add(a.solution!!)
b.selection = b.solution
b.options.clear()
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
a.selection = a.solution
a.options.clear()
b.selection = null
b.options.add(b.solution!!)
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
if (j < size - 1) {
val notA = rowA[j + 1]
a.selection = null
a.options.clear()
notA.options.add(a.solution!!)
b.selection = b.solution
b.options.clear()
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
}
}
}
}
}
@Test
fun `ensure grid with a and c more than one cell between is not considered valid`() {
val grid = createGrid { null }
val rowA = grid.random()
val rowB = grid.random()
val a = rowA[1]
val b = rowB[2]
a.selection = a.solution
rowB[3].selection = b.solution
expect(NeighbourClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
expect(NeighbourClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}

View File

@@ -0,0 +1,57 @@
package ch.dissem.yaep.domain
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
class OrderClueTest : ClueTest() {
@Test
fun `ensure items in correct order are valid`() {
val grid = createGrid()
for (ia in 0 until size) {
for (ib in 0 until size) {
for (ic in 0 until size) {
for (ja in 0 until size - 1) {
for (jb in ja + 1 until size) {
val a = grid[ia][ja]
val b = grid[ib][jb]
expect(OrderClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
}
}
}
}
}
}
@Test
fun `ensure items in wrong order are valid`() {
val grid = createGrid()
for (ia in 0 until size) {
for (ib in 0 until size) {
for (ic in 0 until size) {
for (ja in 0 until size - 1) {
for (jb in ja until size) {
val a = grid[ia][ja]
val b = grid[ib][jb]
expect(OrderClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}
}
}
}
}
@Test
fun `ensure items in the same column are not valid`() {
val grid = createGrid()
for (rowA in grid.rows) {
for (rowB in grid.rows) {
for (i in 0 until size) {
expect(OrderClue(rowA[i].solution!!, rowB[i].solution!!).isValid(grid)).toEqual(false)
}
}
}
}
}

View File

@@ -0,0 +1,94 @@
package ch.dissem.yaep.domain
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
class SameColumnClueTest : ClueTest() {
@Test
fun `ensure fields in the same column are considered valid`() {
val grid = createGrid()
for (ia in 0 until size - 1) {
for (ib in ia + 1 until size) {
for (j in 0 until size) {
val a = grid[ia][j]
val b = grid[ib][j]
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(true)
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
}
}
}
}
@Test
fun `ensure fields in different columns are considered invalid`() {
val grid = createGrid()
for (ia in 0 until size - 1) {
for (ib in ia + 1 until size) {
for (ja in 0 until size - 1) {
for (jb in ja + 1 until size) {
val a = grid[ia][ja]
val b = grid[ib][jb]
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}
}
}
}
@Test
fun `if a is set, but b is set to a wrong value, it's considered invalid`() {
val grid = createGrid { null }
for (ia in 0 until size - 1) {
for (ib in ia + 1 until size) {
for (ja in 0 until size - 1) {
for (jb in ja + 1 until size) {
val a = grid[ia][ja]
val b = grid[ib][jb]
a.selection = a.solution
b.selection = b.solution
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}
}
}
}
@Test
fun `if there are no options for a and b in the same column, it's considered invalid`() {
val grid = createGrid { null }
val rowA = grid.random()
val rowB = grid.filter { it != rowA }.random()
for (i in 0 until size) {
val a = rowA[i]
val b = rowB[i]
rowA.forEachIndexed { index, gameCell ->
if (index < i) {
gameCell.selection = rowA.options.filter { it != a.solution }.random()
} else {
gameCell.selection = null
}
}
rowB.forEachIndexed { index, gameCell ->
if (index < i) {
gameCell.selection = null
} else {
gameCell.selection = rowB.options.filter { it != b.solution }.random()
}
}
expect(SameColumnClue(a.solution!!, b.solution!!).isValid(grid)).toEqual(false)
expect(SameColumnClue(b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}
}

View File

@@ -0,0 +1,142 @@
package ch.dissem.yaep.domain
import ch.tutteli.atrium.api.fluent.en_GB.toEqual
import ch.tutteli.atrium.api.verbs.expect
import kotlin.test.Test
class TripletClueTest : ClueTest() {
@Test
fun `ensure actual triplets are valid`() {
val grid = createGrid()
for (ia in 0 until size) {
for (ib in 0 until size) {
for (ic in 0 until size) {
for (j in 2 until size) {
val a = grid[ia][j - 2]
val b = grid[ib][j - 1]
val c = grid[ic][j]
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(true)
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
}
}
}
}
}
@Test
fun `ensure that middle might not be at the corners`() {
val grid = createGrid { null }
val rowA = grid.random()
val rowB = grid.random()
val rowC = grid.random()
val a = rowA[1]
val b = rowB[2]
val c = rowC[3]
rowB[0].selection = b.solution
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
rowB[0].selection = null
rowB[grid.size - 1].selection = b.solution
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
@Test
fun `ensure grid where a or c and b are not neighbours is invalid`() {
val grid = createGrid { null }
val ia = 1
val rowA = grid[2]
val a = rowA[ia]
val ib = 2
val rowB = grid[0]
val b = rowB[ib]
val ic = 3
val rowC = grid[1]
val c = rowC[ic]
val clue = TripletClue(a.solution!!, b.solution!!, c.solution!!)
b.selection = b.solution
c.options.add(c.solution!!)
rowA.forEachIndexed { index, notA ->
notA.selection = a.solution
when {
notA == a -> {
// ignore
}
index == ic -> {
rowC[ia].options.add(c.solution!!)
expect(clue.isValid(grid)).toEqual(true)
}
else -> {
expect(clue.isValid(grid)).toEqual(false)
}
}
notA.selection = null
}
}
@Test
fun `ensure grid with a and c more than one cell between is considered invalid`() {
val grid = createGrid { null }
val a = grid[2][1]
val b = grid[0][2]
val c = grid[1][3]
a.selection = a.solution
grid[1][4].selection = c.solution
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
@Test
fun `grid with a set and b and c as option on the same side is considered valid`() {
val grid = createGrid { null }
val rowA = grid.random()
val rowB = grid.random()
val rowC = grid.random()
val a = rowA[3]
val b = rowB[2]
val c = rowC[1]
a.selection = a.solution
b.options.clear()
c.options.clear()
rowB[4].options.add(b.solution!!)
rowC[5].options.add(c.solution!!)
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(true)
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(true)
}
@Test
fun `grid with a set and no b and c as option on the same side is considered invalid`() {
val grid = createGrid { null }
val rowA = grid[2]
val rowB = grid[0]
val rowC = grid[1]
val a = rowA[3]
val b = rowB[2]
val c = rowC[1]
a.selection = a.solution
rowB[4].selection = rowC[3].solution
c.selection = rowC[3].solution
expect(TripletClue(a.solution!!, b.solution!!, c.solution!!).isValid(grid)).toEqual(false)
expect(TripletClue(c.solution!!, b.solution!!, a.solution!!).isValid(grid)).toEqual(false)
}
}