inkle / ink

inkle's open source scripting language for writing interactive narrative.
http://www.inklestudios.com/ink
MIT License
4.13k stars 492 forks source link

jInk - Java Ink #31

Closed micabytes closed 4 years ago

micabytes commented 8 years ago

As I've mentioned, I've been doodling around an Ink implementation in Java during the weekend. I've put the results up here in case anyone else is interested: https://github.com/micabytes/jink

It essentially implements Part 1 of the Ink documentation, with a (fairly) comprehensive set of tests based on the examples in the docs. Tests are written using Spec2 (Scala), but should be easily readable (minimal Scala syntax involved).

I'm likely to be a bit busy during the coming month, so probably won't do very much more work on it between now and late April. My first impression of working with this has been positive though. I use a similar IF parser in my current (Android - hence the need for Java) game engine, and I am thinking there'd be some advantages to using Ink instead of my home-grown solution. So unless something happens to change my mind, I'll probably pick up work on this again later with a view to making it "ink-complete".

Anyway, it's available if anyone wants to play around with it. Haven't looked into a license yet, though I expect I'll MIT it to be compatible with this. If anyone wants to contribute/help, that would certainly be appreciated.

micabytes commented 8 years ago

Update.

I ended up having some time to spare during easter, and implemented a bunch of additional stuff. So jInk now currently implements most of the Ink script with the major exception of threading and tunnels (part IV of the docs) + some minor ommissions that I either forgot (!) or which only add minor amount of additional functionality (e.g., Consts). Plan is to add it all eventually, but probably after I have battle-tested the rest of the implementation a bit more, because there are certainly also still bugs (I found a handful just in trying out the first scripts).

There are about 70+ tests for the library, built around the examples in the docs and implemented using Specs 2. It's Scala code, but should be easy to read for anyone with any coding experience (I'm not really using any complex Scala syntax and I'm deliberately keeping the code very clear instead of using one-liners). A typical test looks like:

  "Diverts" should {

    val invisibleDivert =
      """=== hurry_home ===
        |We hurried home to Savile Row -> as_fast_as_we_could
        |
        |=== as_fast_as_we_could ===
        |as fast as we could.
      """.stripMargin

    "divert from one line of text to new content invisibly" in {
      val inputStream = IOUtils.toInputStream(invisibleDivert, "UTF-8")
      val story = InkParser.parse(inputStream)
      val text = story.nextAll()
      text.size() must beEqualTo(1)
      text.get(0) must beEqualTo("We hurried home to Savile Row as fast as we could.")
    }

  }

The nice thing about this approach to testing is that the end-result is some 70+ (hopefully legible) statements about how Ink script is supposed to behave. I'll try to give the tests a pass sometime to clean up the statements, which should produce a fairly concise set of specs with associated tests. Also need to go through the existing CS tests and check for any tests that may be missing.

I've also added a few elements that was in my old scripting language which are not included in Ink, so that I could try to port scripts.

One thing I need which is not in the core Ink script is some way to add code for images to show, playing music, etc. For now, my plan is to implement this as a form of "annotations"; e.g., at the moment, when I add an image reference to the script, it's done as follows:

=== some_knot
// @img(some_image)
The text for the knot.

I think this should work fine for the purpose of adding the functionality I need, while still keeping the scripts 100% compatible with existing Ink.

A thing which I couldn't find a good way to handle without changing Ink is the way I use in-game objects extensively in my game scripts (my games have significant non-narrative components and lots of procedurally generated stuff). I would prefer not to define lots of extern functions and maintain parallel game variables, so at the moment I just insert these directly, and implement direct access to the game objects using Reflection. E.g., I insert the core game object with something story.putVariable("world", new GameWorld()) in the game code, and then the script can do stuff like:

=== some_knot
VAR player = world.getPlayer()
VAR friend = player.getBestFriend()
I was very happy to see {friend.getName()} at the party. 
* Give {him(friend.getGender())} a gift.
   ~ friend.giveGift(player)}

Can't really think of a better way to handle that at the moment, though. I wonder did Stoic do anything special to transfer data from their game mode to the narrative mode?

Anyway, going forward, I plan to test the implementation with some real work, and see how it shakes out. Probably discover even more bugs in my implementation. Maybe create a small app that presents Ink scripts in story form. We'll see how that goes.

micabytes commented 8 years ago

Another update.

I've been continuing work on this, and have now incorporated it into the "early access"/open beta release of my next Android game "Pirates and Traders 2" (https://play.google.com/store/apps/details?id=com.micabytes.pirates2). Pretty much everything narrative in the game now runs over ink script. Overall, I'm pleased with the shift.

jInk still lacks some features to be a full Ink implementation, most notable Tunelling and Threads, but also some other (IMO) less important features such as Consts, pass by reference parameters, etc. But it is perfectly serviceable for basic ink scripting now.

premek commented 7 years ago

I'm doing the same in lua :) https://github.com/premek/pink But I'm much slower than you, I don't even think I will implement most of ink.

micabytes commented 7 years ago

jInk is still seeing updates, and is actively used in the Android game Pirates and Traders II, as well as a couple of other as yet unreleased projects.

Currently I'm working on converting the game engine to Kotlin. Mostly because I can - Kotlin is more fun to work with than Java. For JavaScript users, it may be interesting that Kotlin should be fully interoperable with Javascript.

GargamelLeNoir commented 6 years ago

Hello, I've been trying to use jink in a java project, but I can't still to work with the parser. It can't inherited or instantiated and its functions are not static. Trying to do InkParser.INSTANCE just throws a kotlin/TypeCastException without any explanation. Any help to use the parser, or even better a basic java example, would be much appreciated. Thanks in advance.

micabytes commented 6 years ago

@GargamelLeNoir Have you taken a look at https://github.com/micabytes/storybytes-desktop? That's a simple reference implementation that uses mica-ink (formerly jink).

Though if you're using Java, I would rather recommend using blade-ink; it's a native Java implementation and more up to date with the latest Ink features. mica-ink is more a re-implementation of ink because I had some very special requirements for the framework back when I started developing with it. Some of those (Tags, Lists) are now covered pretty well by "standard" Ink - I think the only thing I'm really missing now is object access, and one could probably work fairly acceptably around that problem with some smart use of external functions and tags.

micabytes commented 4 years ago

Closing this. These days I recommend blade-ink for Java/Kotlin, and would contribute to that is needed. Both storybytes for android and desktrop have also been moved to blade-ink.