Kotlin 12
Let’s try something specific with the DSL builder idea. I have in mind a text game like ADVENTure or Zork.
If I have readers for whom “Zork” and “ADVENT” don’t ring a bell, a little searching will tell you that there used to be games where, instead of photo-realistic moving pictures of everything, you were presented with textual descriptions of where you were, what you saw, and what happened.
Inside Building
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is tasty food here.
There is a shiny brass lamp nearby.
There is an empty bottle here.
You typed simple two-word commands:
take keys
:taken
take bottle
:taken
take water
:the bottle is now full of water
Kids today often cannot imagine that these rudimentary games could be fun, but in those days they were amazing and quite fun. Fact is, they can be quite fun today, and I suggest that you might want to try to find an online version and try them.
My today plan is that as part of learning Kotlin (and IDEA), I’ll work a bit on defining a DSL (DungeonSpecificLanguage?) for defining such a game. Obviously there will have to be a game interpreter of some kind that accepts the commands and executes them. The objective, however, will be to build the interpreter with very atomic capabilities, with as much as possible of the game-play defined in the DSL.
For example, in ADVENTURE there was a pirate who would occasionally pop in and steal your stuff. Later in the game, you could discover where he had stashed your things and get them back. I don’t recall whether you had to defeat the pirate, outwit him, or what. Maybe I’ll see if I can find a copy of the FORTRAN77 source and figure it out. But the point here is that we’d like to allow whatever game designer is building a game to specify some kind of elementary thing, a sprite we might call it, say that it’s called a pirate, define where it can roam, what it can do when it encounters the player, and so on.
I do not have a well-formed idea of how that might be done. Fact is, I don’t even have a well-formed idea of how to define the map that we play in. Let’s sketch a few stories:
- Define a map of rooms;
- Specify items initially found in a given room;
- specify connections between rooms by direction or other keywords
We’ll elaborate these as we go. I have more thoughts, but let’s not turn this into a festival of speculation.
Setting Up
- Advice:
- Skip from here to Setting Up Again. I’m keeping this for the record but it’s all about how I failed to figure out how to get IntelliJ to find a testing library, plus various other fumbling.
I want to start this in a new IDEA Kotlin project. I expect trouble, and sincerely invite folx to email or message me with advice and corrections to how I do this. I plan to try to build the simplest structure that I can, because I’m not planning to make this into a multi-platform game, and (I would hope) if one wanted to do that, one could plug in options later. (If that’s a vain hope, do let me know. Anyway I’m not gonna do it.)
Starting at the big window, I select New Project. I envision this thing working in a simple JavaFX window. Should I select the JavaFX “Generator”? I guess I will. I select the IntelliJ Build System, because I’m told it is simpler and I’d like not to get stuck before I’ve even started.
So far so good, it gives me a starting hierarchy. I think I’ll look at the adventure.iml
file, which I assume is the build file for the project. As expected, it’s semi-meaningful stuff:
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>
The first thing I’d like to do is to create and run a test. The Kotlin tutorial on that assumes Gradle. So I guess I’ll just try creating a test and see what’s up. I can always crib the referenced jars or whatever from another project. I’ll include details of what I do, for other beginners, and to help experts debug my process.
Right click the kotlin folder under test, select New / Kotlin Class/File. Get this:
class FirstTest {
}
Inside the class, Command-N for new, select Generate / Test Function. Prompts for framework, select Java5 or whatever that said. Name it HookupTest.
Now it says this:
class FirstTest {
@org.junit.jupiter.api.Test
internal fun HookupTest() {
TODO("Not yet implemented")
}
}
I might have done that org.junit stuff differently, but let’s see what we can do with just that. I note that junit
is red. I assume that’s bad news, and that I should probably specify where to find that.
I have exactly no idea how to do that. Clicking in the area gives me an opportunity to download something from somewhere. I accept the offer. Now it’s not red any more.
Let’s see about doing an assert.
I make it look like this:
class HookupTest {
@org.junit.jupiter.api.Test
internal fun hookup() {
assertThat(3).isEqualTo(2)
}
}
The assertThat
and isEqualTo
are red. That’ll be because I need to bring in another jar, if jars they be.
(I should mention that I haven’t used Java for probably a decade, so I have essentially the knowledge of a beginner. I’m not stupid, as far as I know. Just very ignorant.)
In the other project, the one that sort of works and has all my weird experiments in it, the test files start with
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
So I reckon I need the assertj bit here.
Naturally, the assertj
bit in the line is now red. Will it figure out for me where to go for this one? No. This time it just offers “Rename reference”.
Checking the .iml
file, it is substantially different:
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module-library" scope="TEST">
<library name="JUnit5.8.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.1/junit-jupiter-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.1/junit-platform-commons-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.1/junit-jupiter-params-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.1/junit-jupiter-engine-5.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.1/junit-platform-engine-1.8.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>
Now if only I knew where to go for assertj, and how to tell it to add a library, I’d be golden.
Instructions suggest opening Project Structure and adding a library. You can search for one. I ask it to search for
org.assertj.core.api.Assertions
And it cannot find that, nor any useful subsets of it. In particular it doesn’t know org.assertj
anything.
I give up on this pass.
Setting Up Again
The incantation in my mostly working project is:
dependencies {
testImplementation(kotlin("test"))
testImplementation("org.assertj:assertj-core:3.23.1")
}
OK. That’s in Gradle. I scrap this project and start a new one, specifying Gradle. First thing, I paste in the assertj line in dependencies.
Now let’s try to create a test … again. I create this:
import org.junit.jupiter.api.Test
import org.assertj.core.api.Assertions.assertThat
class HookupTest {
@Test
internal fun hookup() {
assertThat(3).isEqualTo(2)
}
}
And click its green arrow and the test fails as expected. Fix it.
It’s green.
OK, I now know a fairly simple incantation to set up a new project. And I’m already tired and tense. And we’re under way.
A Side Quest Pays Off
I happened to click into GeePaw’s Zoom with some little question, and we got to trying to put TornadoFX into my build, and then we decided that was impossible, so after lots of progress and one starting over we created a new empty project on Gradle, requesting JavaFX and made a window open.
That may become important later. For now, I’ve got a project into which I can build.
A Bit of Design
I figure we’re mostly building a sort of outline structure, which is what these builder things mostly do. I’ll sketch a small game that we can describe. Let me type in an imaginary outline for it:
map
room("well house")
item("keys")
go("s", "ravine")
room("ravine")
go("w", "woods")
go("e", "cave")
room("woods")
go("n", "ravine")
go("*", "woods")
room("cave")
go("n", "forest")
go("w","ravine")
go("s","pit")
room("pit")
item("gold")
go("up","cave")
without("gold")
room("forest")
go("w","well house")
go("*","forest")
I can see that we’re going to get tired of typing all those quote marks, but unless we want a DSL compiler instead of a builder, I think we’ll have to live with them for now.
Let’s start. We’ll guide ourselves with tests as much as possible. The work for today will be about building the structure. I doubt we’ll even get to moving between rooms today, but you never know.
A Couple of Tests
I’ll spare you the step by step, which was tedious and for some reason prone to terrible errors from IDEA. I now have these two tests:
@Test
fun createWorld() {
val world = world {println("creating")}
assertThat(world.name).isEqualTo("world")
}
@Test
fun worldWithRoom() {
val world = world {
room("living room") {}
}
assertThat("bar").isEqualTo("bar")
assertThat(world.contents.size).isEqualTo(1)
assertThat(world.contents[0].name).isEqualTo("living room")
}
The first one drove out the basic world
function and World
class:
fun world(init: World.()->Unit): World{
val world = World()
world.init()
return world
}
class World {
val name = "world"
}
And the second one drove out the current World class and Room:
class World {
val name = "world"
val contents = mutableListOf<Room>()
fun room(name: String, init: Room.()->Unit) : Room {
val room = Room(name)
contents += room
room.init()
return room
}
}
class Room(val name: String) {
}
The tests, plus a couple of hookup tests, are all green. I’ll commit this and push to a github repo.
I also saved a starting repo to use for my next project should I need one.
Now, unlike the HTML example that I was doing, I think these classes will not likely have a lot of inheritance going on. So perhaps a less generic word than contents
would be good for world … but I’m not sure of that. I think I’ll refactor that name to rooms
and see if I like it as time goes on.
The rename refactoring actually offers rooms
as an option. Very clever IntelliJ, very clever. Test again. Green again. Commit and push: rename World.contents to room.
After arguing with IntelliJ about whether I need to capitalize some words in my commit comment, frankly I think that’s none of their business, I think we have a committed and pushed repo again. We’ll sum up.
Summary
Today was another two hours of fiddling with IDEA, its builder, which we couldn’t get working to our liking, then back to Gradle, then later a complete restart with that, all to get two tiny tests and two tiny classes under test.
This is all down to maybe two things.
First, managing configurations in any build system is a dark art. You have to know where to find things on the internet, and how to write just the right incantation into a build “language” that, if it is defined anywhere, I’ve not been able to find the definition and operating manual.
Second, I am new to this language and toolset, so I move slowly and make mistakes that even the powerful hinting, creating, and refactoring tools of IntelliJ cannot always compensate for.
Still, I’ve got a starter in place where you can type in the beginning of the hierarchy of an ADVENTURE game:
world {
room("well house") {
}
}
Tomorrow is another day, and if the frakkin’ thing doesn’t explode on me, I can build a bit more. The frustration of today is sufficient thereto.
See you next time!