This is a simple class in Kotlin that handles three basic Bingo-related functionalities: Specifying the numbers, generating a card, and calling one random element. Additionally, it should support an empty constructor, and support Discord Emoji output, in which there should be no spaces between any elements. I personally think drivers that use this class should do their own error handling.
I'd like suggestions on how to make this more in line with Kotlin standards, e.g. idioms, code style, whether or not I should delegate error handling to drivers, alternate/more readable approaches to certain subtasks...
Edit: I have changed the code to add an additonal constructor that specifies the delimiting char between elements.
Edit on constructor: The constructor splits the input string along the spl
val and puts the first element into delim
. For the rest, if the first character isn't :
(meaning the input isn't Discord Emoji), the elements are sorted by length alone before being padded with spaces so 1. all elements are of the same length 2. all elements have a trailing space. Then, the header is initialized with the first 5 elements of the Column
enum using the same padding conditions. For Discord Emoji input, the header is initialized similarly but with regional indicators.
import kotlin.random.Random
class Bingo() {
var num = (1..75).map { if (it < 10) " $it " else "$it " }
var cur = num.associateBy({ num.indexOf(it) }, { it }).toMutableMap()
var delim = " ∅ "
var max = 2
var header = "B I N G O"
enum class Column {
B,
I,
N,
G,
O,
Empty
}
/**
* It is expected that the number of elements ≥ 25 and is divisible by 5
*/
constructor(nums: String, spl: Char) : this() {
val rem = nums.substringAfter(spl).split(spl).toMutableList()
while (rem.contains("")) rem.remove("")
while (rem.size % 5 != 0) rem.remove(rem.random())
if (rem.size < 25) return
num = rem; delim = nums.substringBefore(spl)
if (num[0][0] != ':') {
num = num.sortedBy { it.length }.toMutableList()
max = num.last().length
num = num.map { " ".repeat(max - it.length) + it + " " }
delim = " ".repeat(max - delim.length) + delim + " "
header = ""
for (i in (0..4)) header += "" + Column.values()[i] + " ".repeat(max)
} else {
header = ":regional_indicator_" + Column.values()[0].toString().lowercase()
//Reg. indicators often turn into flags without spaces
for (i in (1..4)) header += " :regional_indicator_" + Column.values()[i].toString().lowercase()
}
cur = num.associateBy({ num.indexOf(it) }, { it }).toMutableMap()
}
constructor(nums: String): this(nums, '@')
fun card(): Array<Array<String>> {
val ret = Array(5) { Array(5) { "" } }
var index: Int
val done = mutableListOf<Int>()
for (i in (1..5))
for (j in (1..5)) {
if (i == j && j == 3) {
ret[2][2] = delim
continue
}
do {
index = Random.nextInt(num.size / 5 * (j - 1), num.size / 5 * j)
} while (done.contains(index))
ret[i - 1][j - 1] = num[index]
done.add(index)
}
return ret
}
fun call(): Pair<Column, String> {
if (cur.isEmpty()) return Pair(Column.Empty, "")
var index: Int
do index = Random.nextInt(num.size) while (cur[index] == null)
val list = cur[index]!!.trim()
cur.remove(index)
return Pair(Column.values()[index / (num.size / 5)], list)
}
}
```