Side Project Beginnings

Public side projects, a beginning

As I mentioned in my last blog entry, I will be covering my past projects and evaluating them. The only logical step is to start from the beginning, or at least close to it. This brings me back to when I was leaving college and starting my first job as a software engineer. At this time, it became clear to me that college had provided me with core foundational knowledge in computer science, but I was lacking practical application skills. To better understand the field, I decided it was time to start building some applications instead of focusing completely on algorithms and theory.

Since I had the most cliché introduction to computer science, I decided something with games would be a great start. Given this, I decided the easiest place to start would be something with text adventure games. Since I tend to lack creativity and storyline creation, I set out to create a text adventure framework so that others could create games on my system. So began one of the largest projects I have done outside of work. This was also for an audience of zero people. As far as I know, no one has ever attempted to use it.

Hello World

Energized with a fun idea, I set out to build this framework. I had decided to go in with a couple of constraints.

Built on Java

At the time, I was mostly proficient with third-generation programming languages, and the majority of code I had written was in Java and C#. At the time, I was working heavily in C#. I had justified this choice in a few different ways. First, I wanted it to be cross-platform, and I knew that Java was built to be cross-platform. This focus on cross-platform was one of the major reasons I didn't do the project in C#. Also, I knew that the all too popular up-and-coming game Minecraft was written in Java. I figured if a 3D-rendered game could run in Java, so could a text adventure engine.

Only standard Library

I had decided that if I wanted to learn something deeply, I should only use the standard library and import nothing else. In the end, I fell short of this one. While I intend to cover this more later, the notable exceptions are Java Spring, JavaFx, and IzPack.

Evolution of the project

Before we consider the end state, it seems fitting to discuss how this project evolved over time.

Early days

One of the first decisions I made was to use Maven for this project. In the earliest versions of this, I had a single project that I was working on in Eclipse and building with Maven. This started as something that I kept on my local computer and backed up on an external hard drive and flash drive for a few years. Over time, the project grew, and before I knew it, a monorepo with around nine different libraries was created.

Growing up

At some point, I had lost my flash drive. This made me realize that I could lose the project completely. After this little scare, I decided it was time to start backing up my project online. For some reason, I was worried about sharing my code publicly. So, I decided to create a Bitbucket account to back the project up in a private repository.

During this time, the industry had decided it was no longer cool to store all code in a single repository. Instead, a ton of small repositories should be managed and versioned independently. As a good engineer, obviously, I had to follow the rest of the industry. I spent a notable amount of time creating separate projects for each of my libraries. This involved breaking the library out of the monorepo, creating a new Bitbucket repository, publishing an artifact to an S3 bucket on my AWS account, just so I could download it again via Maven for the projects that depended on that code.

Getting ready for "Release"

At some point, I had decided enough is enough! “I am going to put this code on GitHub as a public repository”, and that will be my released version. I was worried that once it was out there, people would see what I was working on, so I decided to work feverishly on the project to make it "release-ready". What I defined as release-ready was the following.

First, the project needed very high test coverage. If there was a class it needed to be unit tested. It did not matter how small or pointless. To put this into context, even the about dialog in the main application had unit tests.

The next major requirement I had decided on being a "blocker" was that every public library needed to have every single method documented with Javadoc. and that all projects needed to have generated documents. This began with spending more time than I care to admit writing questionable Javadoc for absolutely everything. Then I had to fight with Maven to make sure the docs would actually generate correctly.

Lastly, if I were going to send this out into the world, every respectable program that runs on a user's machine needs an installer, right? This began a long journey of creating an install workflow. Which basically just dropped a batch script into Windows to start the game builder. In this effort, I found IzPack and spent a long time learning how to create an install flow.

Post Release

Soon after moving everything to GitHub and making everything public, I realized that no one was watching. If you want people to actually see what you are building, you need to be able to market your work, and that was not something I was good at or wanted to get good at. From that point on, I felt free to just work on the things that I had found interesting or wanted to learn.

One of the first major changes happened when I was building my personal website. I decided I wanted that website to scrape all of my GitHub content and have pages for everything I put in the docs folder of any of my projects, along with any associated Java documentation. During that time, I moved a lot of documentation. Mostly, this involved converting HTML documents that detailed how to use the application to markdown files on GitHub.

Not long after I created my website, I decided to create GitHub workflows for all my projects. During that time, all of these repositories got their own set of actions to build and test the repository on every PR. In doing this, I realized that how I was integrating with Maven and S3 was not going to work in GitHub actions. Due to this, I switched over to having my projects published to GitHub's Java repository.

At some point along the way, Java updated to Jigsaw, and I decided it would be nice to make some of my "internals" actually internal, so I updated to Java 9. I wrote about this in the following blog.

While I never really completed it, the last thing I decided to focus on was creating end-to-end tests for the main application. For this, I was playing around with test-fx. Ultimately, this never got completed because I was having a hard time getting the tests to pass on the GitHub runners. The tests had worked just fine on my local, but, for some reason I never really cared to debug, I couldn't get them to pass in the runner.

Current state of the project

Once the dust had settled, I had been working on and off on the project for close to a decade. To my knowledge, it remains used and unheard of.

Project setup

The final makeup of the project was the following repositories. While I do not think lines of code is a great metric, I do think in a pre-AI world, it highlights the amount of time spent on the project more so than anything.

Name Language Purpose Lines of code
java-core Java Library containing anything I deemed a core utility 3562
logrunner Java Abstraction to log output to csv or tsv file 293
java-persistencelib Java Library to save xml files to disk 1586
persist-lib Javascript Javascript reimplementation of java-persistencelib 270
playerlib Java Library for entity models and abstractions 4100
player-lib Javascript Javascript reimplementation of playerlib 956
playerlib-example Java Example application using playerlib 1459
gsmlib Java Library for game state models and abstractions 935
gamestate-manager Javascript javascript reimplemenation of gsmlib 306
iroshell Java Multipurpose JavaFx application shell abstraction 10940
iroshell-examples Java Example applications build on iroshell 1444
textadventurelib Java Core library required to play the text adventure games 25045
text-adventure-lib Javascript Javascript reimplemenation of textadventurelib 7437
textadventurecreator Java The main application that created text adventure games 55066

In total, that is close to 115k lines of code split between Java and JavaScript.

This breaks out into the following dependency graph.

Dependency graph

Features

In the spirit of a fair analysis, I find it is useful to document all of the features wrapped up in this project. Much of this is documented on my personal site. As such, I will avoid repeating all of it. Instead, I will highlight some core features. If you are interested, the main documentation can be found here.

Feature Category high level details
Export as App Export The ability to export the game as a jar
Export as Web Export The ability to export the game as an html document
Export as Electron Export The ability to export as an electron app
Entity creation Core The ability to define players and NPC's/enemies
Macros Core The ability to replace text with some player details
Game State creation Core The ability to define all actions and triggers in a game state
Text Trigger Core-Triggers The ability to trigger some action on user text
Timed Trigger Core-Triggers The ability to trigger some action on a timer
Player Triger Core-Triggers The ability to trigger some action based on player state
Scripted Trigger Core-Triggers The ability to trigger based on some executed javascript
Multi-part trigger Core-Triggers The ability to union multiple triggers together
Append Action Core-Actions Ability to add text to the game output
Complete Action Core-Actions The ability to complete a game state
Execute Action Core-Actions The ability to trigger a process on the users machine
Modify Player Action Core-Actions The ability to change player state
Save action Core-Actions The ability to save the game
Script action Core-Actions Any action that could be expressed in javascript
Finish action Core-Actions The action that completed the game
Text and Input View Layout The base look and feel
Text and Button View Layout A look and feel that replaces a text input with buttons
Content Only Layout A display only view for things like transition images or videos
Custom layout Layout A customizable grid layout of whatever controls you want for the game state, including custom styling
Libraries Libraries User defined exportable collections of players/actions/triggers or layouts
Debugger DevTools A crude debugger to see how playing the game changes the state in the IDE
Language Packs Localization The ability for users to define their own localization to be used in the IDE
Mods DevTools The ability for a developer to add a runtime loaded mod into the IDE

Lessons learned at the project level

A warning to the reader. Many of these lessons are based on the constraints and end state of this project. If this project had been worked on by a team of engineers, my perspectives would have been different. Remember that the end state of this project is that a solo developer built something that was unused but is now in the public domain.

Just because it's on GitHub doesn't mean anyone will notice

I think this is one lesson that I wish I had learned early on. There was a fair amount of time I spent scrambling to get the code "ready for the public to view". I was concerned that once you put something on GitHub, everyone would see it. In reality, I think a handful of people have glanced at the landing page, then saw a sparse readme and moved on. The largest reader of my code may be some AI model trained on all public GitHub data.

The most important lesson for me around this is that you can waste an immense amount of time expanding code coverage and documentation on a project. If you are not working on a team and do not plan to market your work, the returns for high test coverage and documentation are questionable. The few exceptions to this would be critical or regression-prone code paths. Now, in modern times, this may be less of an investment because you could offload most of this work to an AI agent or otherwise. However, if you are going to do that, you should really scrutinize the output because it might not be documenting things correctly or testing things effectively. It is unclear to me if it is even worth the tokens or time to review to autogenerate these sorts of things for solo side projects. What is clear to me is that incorrect documentation or tests are worse than having no documentation or tests.

As far as visibility goes, what I have come to assume is that if people are going to look at your project, they will have to be led to it via some sort of marketing on social media. If you want to capture your audience, you will want a strong README with flashy images to draw their attention. I have never taken the effort to market my work, so these assumptions may be incorrect.

Industry standards are highly contextual

In this case, I made a classic junior to mid-level engineering mistake. That is adopting the mindset that the industry standard is the best practice for all use cases. Since then, I have learned that industry standards are guidelines contextualized for engineering with many active engineers working on the same project, or for domain-specific reasons.

Take, for instance, this project. I intentionally broke apart a monorepo into a series of small, targeted repositories without considering the tradeoff. In this case, I lost valuable time breaking apart a monorepo and incurred the cost of managing dependencies without getting observable benefits. Here are some cases that could have made the investment valuable, and why it remains a sunken cost.

Community involvement

I could have built a community around something like iroshell. This would have the benefit of many engineers helping me with my framework. This did not happen because I did not take an active effort to build a community. In a single instance, a developer reached out with an interest in a single part of the framework, and I chose not to engage with that engineer. If I had been truly interested in a community, I would have missed a real opportunity there.

Code Reuse

Another possible benefit I could have gotten was reuse. However, this reuse benefit would have depended on doing more Java projects that are built on iroshell, java-core, or logrunner. Instead, I have not started another Java project since this project's completion. This means I missed out on all of the hard work I put into making iroshell reusable for other possible applications.

Observing the problem space has its benefits

At no point during this work did I ever stop to look around at what other people had been doing. Had I done so, I would have found that there was already a pretty big community around tools very similar to the one I created. For instance, Twine is a rather powerful tool that can do something very similar to what I created. I do not know that this would have deterred me from taking on this project. However, taking the time to read about the project could have given me a chance to see what features others like and some of the pain points. If I wanted to create a product that got traction, that was a missed opportunity to build for the community.

Not all features are worth the investment

There are a handful of features that were either premature or questionable. In particular, the “execute action” in the game engine itself is a feature that didn’t need to be made. I am still not sure what I thought the benefit would be. As it stands, its biggest purpose is a security risk. On the premature side, features like modding and libraries had been way too early. Without a community using the tool, there was no reason to create community tools like modding.

The highlights

So far, I have been very critical of all the work I have done. However, I would be remiss if I didn't point out the benefits. On the off chance a junior to mid-level developer finds the blog and reads it, I wouldn't want to give the impression that this was a completely wasted effort. While I may have some regrets about the features I chose to work on or the way I assumed the project would be received, it was a very valuable learning lesson, even from a techinical level.

Learning JavaFX

At the start of this project, my experience with Java UIs was small college projects using Swing. At work, I was learning about C# and WPF, but those lessons had not been fully transferable. In this case, I learned how to build a framework, a UI, and auto-generated games all on top of JavaFX. I really enjoyed learning about JavaFX's use of CSS and using tools like ScenicView. While I have yet to use this knowledge in a professional setting, it was great fun to learn.

Installers

While I do not think I nailed the implementation, I did get to learn the basics of installers and the pain points of creating one. Up until this point, the software I wrote either had a team working on an installer or was just a jar/exe that someone else had to figure out how to run for themselves. In the modern era, with web-first and electron apps, this may be an irrelevant skill, but it helped remind me that different users have different machines, which may or may not have some of your prerequisites for your software to run.

It still works

With fairly minimal effort, I was able to come back to this years later and get the thing running again. The biggest pain points I had were the kinds that I expected. Mostly setting up my machine to build Java again by installing OpenJDK and Maven, as well as configuring Maven to install from GitHub.

Project completion

This was the first project I really got to play every possible role. I had the idea, oversaw all the technical decisions, implemented the entire project, designed the UX, and even did some terrible icon artwork to top it all off. I got to see this thing from a silly drawing in a notebook to a completed project that can still be used today.

In this, I learned how to stick with something even when it gets hard or boring. There were days when I would get stuck on a technical issue that I couldn't find any great documentation online. On other days, I mind-numbingly drudged through just one more Javadoc comment.

I cannot overstate how valuable that was to me. I find that when working at a company with a clear goal in mind, there are trade-offs that must be made. As an engineer, those are not always to the benefit of your technical growth. You may want to spend time optimizing a routine or playing around with a new technology/framework/language. In many cases, doing this on the job is borderline negligent and not in the interest of the company you are working for. However, when you are working on a project that is yours without direct financial incentives, you are free to make whatever choices you want. The cost incurred, in money or in time, is on you, and you do not have to consider the cost to some company.

In the modern era of AI, I am not sure if you could recreate this experience. For example, a new developer who needed an icon in a specific way might not try to draw it themselves. Instead, they might just ask an AI to generate it for them. AI can provide some excellent rubber ducking, which was just not possible when I attempted this project.

Digging deeper

By now, I have covered an exhaustive history of this application, with topics across the board. Now I want to dive deeper into the technical design choices. I will have some follow-up blogs about specific libraries and some of the technical design choices I made. This will focus on how I see those decisions now.