tl;dr: https://github.com/JosephCatrambone/PyTorchTextOverlayDataset

TextOverlayDataset is a pip-installable package which generates text images from text and image datasets. For a simple project, it turned out to be surprisingly involved. I would mark April as largely a success, though the delivery landed after the two-week mark I set for myself. The project also bled over (or, rather, had outside involvement which led me to revisit it) into May, which is much less fortunate. Nonetheless, I’m happy with the project and will keep working on it as time allows.

The Team DogPit 2023 Jam kicked off this month! It remains my favorite annual jam, though this year I had to scale back the planned game because I was out on a trip for the first half. I ended up submitted a game called “Odd Soul” which you can play here: https://xoana.itch.io/odd-soul

The theme was, “The Odds are Good,” and while I had initially planned a game where you have to DM for a bunch of players who keep rolling terribly, the game seemed too far removed and complicated to execute.

I pivoted to something of a visual novel, planning to make a game with multiple endings whose story junctions depended not just on text but also games of Rock, Paper, Scissors. The mechanics for Rock, Paper, Scissors I stole from someone I saw on Twitter:

The gameplay turned out to be too hard to manage. There’s too much visual noise for the player to really figure out what they’re doing and the action can either be too fast-paced or too boring. There wasn’t really an in-between I found.

Nonetheless, I kept with the visual novel system and, after struggling with a story that was ENTIRELY too complicated and serious for a fun game, I stumbled upon the idea of playing Death for your eternal soul. Mild spoiler: Death plays perfectly for most rounds of the game, but every so often there’s a small chance that it will make a mistake. One-shot, your odds of winning a game are in the tens of thousands, but play often enough and you’re basically guaranteed to win.

When I got tired of working on minigames I went back to writing story and bumbling through possible endings. It’s far from the finest bit of writing, but it works well enough for the game and expected playtime.

I am slightly pleased with how robust the visual novel format is. A user can create an arbitrary story as a JSON file and the application will detect and run that, so new stories can be made (with new visual assets) with no code.

The story JSON is fairly simple: { "scenes": { ... all scenes ... }, "assets": { ... all assets ...}

With the average scene looking like this:

"goto_apartment": {
	"background": "apartment",
	"actors": ["dog", "cat", "death"],
	"text": "[death:eyes_closed]One moment.  [death]There we are.\n[dog]Dog: *bark*\n[cat]Cat: *meow*\n[death]Oh!\nOh wow. <truncated>\n",
	"prompt": "Yeah.",
	"options": {
		"They're hungry.": "hungry",
		"They're my reason for living.  Or they were.": "reason_for_living"
	},
	"next": "",
	"on_enter": "fade_in",
	"minigame": "",
	"minigame_outcome": {

	}
},

And the average asset looking something like this:

"assets": {
	"characters": {
		"reaper": {
			"default": "png:<a base64 encoded PNG>",
			"angry": "file:path/to/a/png/alternative.png",
		}, // More characters
	},
	"backgrounds": { ... like above ... }
}

Let’s break down a scene:

The “actors” array determines the order of the portraits to be shown. In the above example, we have a scene like this:

Is it a good idea to show Death your apartment? Maybe!

The ‘text’ area then can/will highlight actors. When an actor is not speaking, they’re faded slightly and made transparent. An actor can have different emotes inline. [death:happy] will, for example, look for an image in the Death asset named “happy” and will display that where Death is. All actors need a “default” image. Convention over configuration. One additional key about the text is that switching expressions will not pause the dialog for user input. User input is only required when a newline appears in the text, so in theory we could chain several emotes together for an animation (this happens once in the story) or several scenes together to trigger actions (this also happens in the story).

The last piece to consider is how to branch. The branching will trigger after all the text is finished displaying. “options” specifies an associative array (dictionary) with the text of the choice mapping to the name of the scene. In the above, the user will see two buttons, “They’re hungry.” and “They’re my reason for living…”. Clicking “They’re hungry” will bring us to the scene named “hungry”. Another possible way to branch is to trigger a minigame by setting the “minigame” field. I have four implemented (“tictactoe”, “pong”, “random”, and “rockpaperscissors”), but only two appear in the story. (I’d meant to make more, but timing is hard.) Each minigame has to emit a signal on completion with the status: GameOutcome.WIN, GameOutcome.LOSE, or GameOutcome.DRAW. And the “minigame_outcome” has to map “win”, “lose”, and “draw” to the scenes.

I am slightly proud of the “random” game, since that starts up a minigame which immediately returns one of the outcomes and is a way to make a story branch randomly. If there’s no “text”, the scene starts up, finishes, and branches in a single frame.

If there’s no “prompt” and no “minigame”, then “next” is the scene to which we jump when the dialog is complete.

Finally, “on_enter”. This is the part I’m least fond of. “on_enter” is the name of a function to be called as soon as the scene is done loading. I’ve used it to fade_in and trigger returns to the menu, but I think these uses could be better served by inlining them. That way we don’t need separate scenes for returning to the menu or jumping to endings, and opens the possibility of setting multiple flags in the same scene.

All things told, I’m not proud of the gameplay or the art or any of it, but it’s done and I find relief in this and satisfaction in having completed another jam. The entire project is open source and available for review here: https://github.com/JosephCatrambone/DogpitJam2023

I started this project a little late in the month. Initial plan was to use the Team Dogpit Jam for the February activity, but that’s been postponed because of weather troubles and power outages in Texas. My substitute project is a small scriptable text tool.

A few times a week I find myself needing to decode something in base64, wanting to pick apart a JWT token, or needing to nicely format some JSON. Sure, I can post each of these into an untrusted website or, in the base64 case, throw pbpaste | base64 --decode | pbcopy into the terminal, but I’d like something that’s fairly low resource, extensible, and serves the singular purpose of running tiny scripts on text inputs.

The proposed text tool has two fields: one for running a plugin (with autocomplete and suggestions) and another area which takes a big block of text and gets replaced with another block of text. Support for multiple scripting languages is great, but ultimately not worth it if it pushes the project past the time limit. There should be a clean, minimal UI with friendly features like autocomplete (suggest) and keyboard shortcuts for copy/paste into specific command areas.

===

Update: Despite the late start, I was able to wrap up this project. It’s not quite where I want it to be, but when is it ever? The full source is available on my GitHub page at https://github.com/JosephCatrambone/TextUtil . It needs a documentation pass and a lot of code cleanup, but it works. Here’s a demo of me invoking the base64 decode plugin, using a keyboard shortcut to jump back to the command entry, and invoking the ‘hello_world’ plugin.