I've implemented a Minecraft-like game for assignm...
# codereview
e
I've implemented a Minecraft-like game for assignments in a class I am teaching. Classes include
Mob
(abstract),
Zombie
, and
Spider
. I would like to create a class
Spawner<T: Mob>
to create, for example, Spider Spawners (blocks that periodically construct and place new instances of
Spider
). I'm trying to figure out the best way to instantiate a new instance of
T
from within
Spawner<T>
. I tried the following, but it doesn't work because of type erasure:
Copy code
class Spawner<T: Mob> {
    fun spawn() {
        val constructor = T::class.constructors.find { it.parameters.isEmpty() } // error: Cannot use 'T' as reified type parameter
        val newMob = constructor?.call()
    }
}
I'd rather not require the students to create factory classes. (This is an introductory programming class.) Instead I was thinking of faking a factory with a
copy()
method:
Copy code
class Spawner<T: Mob>(val mob: T) {
    fun spawn() {
        val newMob = mob.copy()
        // Place newMob on game board.
    }
}

abstract class Mob {
    abstract fun copy(): Mob
}

class Pigman: Mob() {
    override fun copy() = Pigman()
}

fun main() {
    val pigmanSpawner = Spawner<Pigman>(Pigman())
    pigmanSpawner.spawn()
}
Something I don't like about this is the type parameter isn't used and could be removed (if I changed the type of the
mob
property from
T
to
Mob
).
e
Copy code
class Spawner<out T: Mob>(val newMob: () -> T) {
    fun spawn() {
        val newMob = newMob()

val pigmanSpawner = Spaner(::Pigman)
with some better naming but that should be the idea
e
Any ideas on how I can incorporate non-hacky generic types into the assignment? I could create a `Hunter<T>`but I have the same problem because of type erasure. I can't
filterIsInstance<T>
m
Alternatively you could make the spawner an inline function so you can use the generic type
Copy code
inline fun <reified T : Mob> spawn() : T {
    val constructor = T::class.constructors.first { it.parameters.isEmpty() }
    return constructor.call()
}

fun main() {
    val z = spawn<Zombie>()
}
I replaced ‘find’ with ‘first’ that produces a non-nullable type or throws if the element is not found.
e
why would you do that when you can just
T::class.createInstance()
but I'd avoid reflection for this use case, it doesn't fit that well
m
Wow I wasn’t aware of that function, totally missed it! Thanks. Anyway yes, I didn’t realize your solution didn’t use reflection, cool!
e
FYI, after I applied @ephemient’s advice to create
Spawner<T>
and tested it out with
Spawner<Zombie>
, I realized I could create
Spawner<Spawner<Zombie>>
and did so. 🧟