Tuple Markup Language
tl:dr I rewrote Google’s Gson JSON library for use with TML. If you use Java and want clean, human-readable/editable data, use it.
https://github.com/codetaylor/Juple
Initially I decided to expose game data via scripts, or rather through a Java api. The scripts were just java classes that got loaded dynamically at run-time. Now keep in mind I’m just talking about data, not logic. This seemed like an awesome idea at first. Then I started using it.
It quickly became apparent that there was too much overhead, boiler-plate, cruft-shifting type coding going on. If I wanted to add a property to something, first I had to define it in the API, then go write the code to handle populating the value in-game, and finally, go modify the script. On top of that, there was the Java-doc for the API that would have to be kept up to date, not only for the data mapping methods, but also for the logic.
If I have a game entity that has four values, I don’t want to have to define a package, define imports, think of a clever class name (or concoct a new naming convention), write an interface in the API, write the class loading code, write the definition handling code, update the java-doc… ugh. Monolithic Sisyphus syndrome.
After a little thought, I decided to split the data definition from the scripting altogether. Scripts will now simply exist for logic and data will be defined without logic. Not to say that data will be generated illogically. A sniper? Let’s give her a hand-to-hand combat bonus. What? I didn’t see an ‘if-then’! No no… I digress.
The first tech that came to mind was XML. I played about with XML for a bit and then moved on to JSON. XML felt too cumbersome for what I imagined and JSON was a move in the right direction. Google has a pretty robust, open-source Java JSON lib called Gson, and I really liked how easy it was to use and extend. Then I ran across TML…
TML, or Tuple Markup Language, is a minimalist, all-purpose markup language created by John Judnich. I thought to myself, “That’s it! That’s what I’m going to use!” I delved into the GitHub repo.. ok C, C++, Javascript, Python: no lib for Java. So sad. Back to JSON I went, sighing all the way.
I was writing stuff like this:
{
"first name": "John",
"last name": "Smith",
"age": 25,
"address": {
"street address": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": 10021
}
}
…while dreaming about stuff like this:
[
[first name | John]
[last name | Smith]
[age | 25]
[address |
[street address | 21 2nd Street]
[city | New York]
[state | NY]
[postalCode | 10021]
]
]
I felt like I was painting with rocks. I just had to have the power and flexibility of the Gson lib, but for TML.
That’s why I rewrote Gson for TML… FTW.
It’s free, open-source, and has some decent docs, check it out.
![I’ve been working on a cave generation system and trying to achieve an organic feel. The starting point for my method was inspired by this post.
Parameters:The cave system generator has several parameters.
Seed - integer used to seed the pseudo-random number sequences
SystemSize - how many caves deep and wide the system is
CaveSize - width and height of each cave
MaxWorkers - count at which to deactivate all workers and stop looping
SpawnChance - percent chance to spawn new worker
Threshold - distance from center at which worker movement becomes harder
Layers - how many data arrays to layer into one cave
Create an Array: First I create a two-dimensional integer array to hold the data. For my purposes a 256 x 256 array is large enough. The array is filled with integers that represent the tile type at an (x,y) position. Currently I have a type index for wall, floor, transition up and transition down. I actually use a one-dimensional array and access it like so:
int get(int x, int y) {
return data[x + width * y];
}
void set(int x, int y, int val) {
data[x + width * y] = val;
}
Create Workers: Next, I create a worker object, activate it, drop it in the center of the data array and set the value at its location to the floor value. Now I start looping through all active workers. During the loop, each active worker is given a procedurally shuffled set of four directions: N, E, S and W. Each direction is checked in order from first to last; if a wall is encountered in the checked direction, the worker is moved to that location and the value of that location is set to the floor value. If no walls are encountered, the worker is deactivated and removed unless the worker is the last active worker; the last active worker will move, without spawning new workers, until it finds a wall. Each time a worker moves, it has a chance [SpawnChance] to spawn a new worker. Again the parent worker is given a procedurally shuffled set of four directions. Each direction is checked in order; if an unoccupied floor is found in the checked direction, a new worker is spawned in that location and the worker count is incremented. If no unoccupied floor is found, the new worker is not spawned. If the distance from the center to the worker is farther than the [Threshold] parameter, the worker’s movement in the direction away from the center becomes exponentially harder the farther it moves past the threshold. If a worker reaches the edge of the data, it is re-positioned at the center. After the worker count reaches the [MaxWorkers] parameter, all workers are deactivated and the loop exits.
Layer Data: The previous steps are performed [Layers] times and each set of data is layered on top of the last. Wall values are changed to floor values only. At this point, we have something that looks like this:Smoothing: The next step is taking the combined layers and smoothing the result. I split my smoothing algorithms into two functions simpleCleanup() and complexCleanup(). The simple function loops through each wall and checks the surrounding values in opposing pairs; if the N and S values are floor or the E and W values are floor, the wall is set to floor. The complex function is a little more, well, complex. It loops through each wall and if at least three of the NW, NE, SW, and SE values are floor, the wall is set to floor. To achieve the smoothed data you see below, first the simple and complex functions were performed each four times, starting with the simple one and alternating between the two; simple -> complex -> simple -> complex -> etc. Finally two simple passes are performed. This results in the smoothed, organic looking cave below. Resize: After each map has been created and smoothed, the minimum and maximum x and y values are calculated and snapped to a grid based on my game’s x and z chunk size, which is 16 x 16. Then, if the cave’s floor is too close to the edge, that edge is padded with a strip of chunks full of wall values. The map is re-mapped to a new map with the new size.
Transitions: To connect the caves in the system, I first generate a maximum number of transitions for the first cave. This number is proportional to the number of floor tiles in the map. Next, a random number of transitions are generated, not to exceed the maximum number of transitions. A map is then generated and linked for each transition, provided the number of maps at the current depth does not exceed the maximum cave system width parameter. If the map depth equals the maximum cave system depth parameter, no transitions are generated and the cave goes no deeper.
Extra: I perform some extra, game-specific steps. I generate a list of indexed chunks for each map and each chunk object contains data about the 16 x 16 region; data such as how many floor tiles are in the chunk. The image of the caves at the top demonstrates this by coloring each chunk a brighter shade of green the more floor tiles it contains.](http://25.media.tumblr.com/3497b3184007fff09bcd05fa33f7a87d/tumblr_mncdvfOvuE1r30ciyo1_500.png)
Smoothing:
Resize: 



