About me

Hi, I'm Grant. I like some things, but dislike other things.

Projects

These are some project's I've worked on. Most of them are complete but very unpolished. If you nonetheless find any of them useful, I'd enjoy reading about it in an email.

GDie

A feature incomplete dice simulator. It uses a simple syntax to express combinations of dice, but I think some dice rolls like roll 4 drop the minimum are still unrepresentable. Try it at GDie.

Anagrams

A simple tool to find anagrams of a given phrase. It has some optimizations to avoid redundant computation. The source is available at GrantMoyer/anagrams on Codeberg.

I wrote it to get familiar with the Nim programming language. It turns out I don't like Nim very much though.

Box cursor

A simple vector-like cursor theme. I use it on my personal computer. The source is avaiable at GrantMoyer/xcursor-box-cursor on Codeberg.

an array of simple, vector-graphics style mouse cursors. The cursors all share the same fully saturated red theme, and each cursor represents a different operation.
Cursors from the Box Cursor theme

Commit playground

A tool for playing around with git and plotting the results. I use it to visualize git concepts for those less familiar with git. The source is avaiable at GrantMoyer/commit-playground on Codeberg.

Doubletone

A tool to descreen half tone images. The key idea is to treat each ink color as a pulse width modulated signal and use an appropriate low pass filter independently for each color to get a smooth image. The source is avaiable at GrantMoyer/doubletone on GitHub.

I use it to make scanned album art look nicer on a computer screen, like the wolf below from the cover of Nightfall in Middle Earth.

Nightfall in Middle Earth wolf, with visible halftone artifacts
Section of a scanned print image
Nightfall in Middle Earth wolf, with halftone artifacts significantly reduced
Same image, after Doubletone filter

Reproject

A command line tool to transform maps from one projection to another. The algorithm is conceptually simple: for each pixel in the output image, take the inverse of the output project of the pixel's coordinates, then project the resulting spherical coordinates into the input image using the input projection. The source is avaiable at GrantMoyer/reproject on Codeberg.

I plan to use it in the production of consistent fantasy maps at different scales for tabletop RPG campaigns. Using Earth as an example, an equirectangular image of Earth can be reprojected to a stereographic image of Antarctica.

Equirectangular projection of Earth.
Input projection of Earth
Stereographic projection of Antarctica.
Output projection of Antarctica

Power Scry

My first CAD modeling and 3D printing project, Power Scry is a power meter attached to an extension cord. I bought an off-the-shelf inductive power meter and off-the-shelf power strip, and I 3D printed a custom housing for the power meter and internals of the power strip. Since the power meter is itself powered directly from mains power with exposed leads, it is important for it to have an electrically insulated housing.

Image of power strip with integrated display showing electric measurements.
Power Scry measuring power use of an appliance

I had no prior experience with 3D CAD tools, so I chose to model the design in OpenSCAD. I figured the declarative language would be easier to pick up than a complex, UI-driven tool, and I think that ended up being true. Furthermore, it was pretty fun to figure out how to construct complex shapes as the composition of simple primitives. That said, I did run into a handful of limitations of OpenSCAD, in particular related to coincident faces. You may find the OpenSCAD models for the project at my power-scry GitHub repo. I also had some fun rendering the models in Blender with procedural textures.

Simple shaded render of tray-like housing with visible support structures.
OpenSCAD preview of Power Scry top shell
Detailed render of complete model with realistic shading.
Ray-traced Blender render of Power Scry CAD model

I printed the housing at my local public library, which graciously does not charge for 3D printing services. I expected shrinkage as the plastic cooled might cause the housing to become too small, so I built in some extra clearance. Still, out of caution I first only printed one half of the housing, and It's a good thing I did, because despite extra clearance it did shrink too much. After measuring the shrinkage, I was able to adjust the model very easily, thanks to the parametric nature of OpenSCAD, and reprint. The second print ended up sized correctly.

Photo of rough-looking top shell of housing with black arrow markings.
First print of the top shell, marked where adjustments are needed
A section of the top part of the housing being printed

The current sense coil of the power meter encircles one of the power strip's conductors, which lets it measure current by induction. Meanwhile, to measure voltage the meter is connected in series from live to neutral. The product of these two measurements, instantaneous voltage and instantaneous current, equals instantaneous power, which the meter aggregates into RMS voltage, current, and power, as well as total energy over some period.

Annotated image of Power Scry internals.
Power Scry internals

Bubbles

Bubbles is a game like Puzzle Bobble or its many clones. I made it for my mom for Christmas after she told me about some issues with another, seemingly unmaintained, Puzzle Bobble-like game she had been playing. Overall, I think it turned out pretty well, with pleasing physics and sound effects. You can play it online at grantmoyer.com/bubbles/ or view the source and build your own native copy from the Bubbles source repo on Codeberg.

Image of a gameplay screen, with Christmas ornament-like balls arranged in a hexagonal grid. A group of yellow balls have just exploded into confetti.
Screen capture of Bubbles game

Since the project had a deadline, I initially decided to use as many off-the-shelf components and write as little game logic as possible. I opted to use the Bevy game engine, because I find writing Rust pleasant, I had played around with it before, and I knew it had ready-made rendering and physics libraries. It was fairly quick to implement — a few days of after-work programming — but I developed it on Linux, and my mom had trouble running it on Windows despite ostensible platform independence. Luckily she was able to share the logs from a debug version I compiled, and I eventually got it running for her, but the performance was bad on her system. Additionally, I wanted to compile the game to Web Assembly so that it would be easier for me to give her updates, and performance wasn't great in browsers either.

After some profiling, I determined the physics system was the major culprit for performance issues. The physics library I used had been easy to integrate into the game, but it was much more general than I needed for my game. For example, each bubble in the grid only really needs to collide with its direct neighbors, so checking for collisions with every other bubble in the grid is a waste of computation regardless of how optimized those checks are. I ended up re-implementing my own physics from scratch with game-specific shortcuts, which made the browser performance playable even on mobile.

Squirrel Logic

Squirrel Logic is a simple, quick-to-play puzzle game. I wanted to create a game in the same vein as the wonderful, mobile puzzle game Hoplite. Play it at grantmoyer.com/squirrel/ or check out the source at GrantMoyer/squirrel-logic on Codeberg.

Squirrel Logic gameplay

At its core, Hoplite is about capturing pieces using a limited set of movement options, somewhat like chess, but on a hexagonal grid. For my own game, I initially considered chess-like movement mechanics as well, but everything I would think of felt too much like outright copying Hoplite. Eventually, I came to the idea of the player moving through the hexagonal grid in straight lines and capturing pieces where the path self-intersects, which I felt was an actually novel mechanic. I could directly limit the moves available to the player by giving them a hand of cards representing the available moves. I also wanted all the moves to be known from the start so that the game didn't feel random, so I decided to organize the cards into three stack of four cards, which are all always visible, but only the top of each stack may be used.

I first programmed a text-based prototype, where the board state was printed out in ASCII each turn, to check that the game idea actually worked in practice. It didn't take too long to get a playable version of the game, but there were a couple complications I didn't anticipate. For one, I had to relaxed capturing pieces from path self-intersection to moving through the pieces at all, otherwise there were too few options where to place pieces. To balance that adjustment, I made it so that some pieces needed to be captured multiple times, which re-introduced the self intersection mechanic. The other complication was that levels were often very hard to solve. I had initially used a pretty naïve approach to generate levels. I first generated a random deck of cards, then picked a random game from all possible games, and placed the pieces such that the chosen game captures them all. However, it turns out it's pretty hard to find one right order for moves out of up to about 35,000 possible orders. So instead of choosing a random game, I narrowed all the games down a the largest subset of games which all capture the same piece three times, then the further subset which capture the same piece two times, and so on for each piece. I also reduced the number of cards per stack to 3 from 4, which reduced the possible orders to about 1,680. Now the game was a little too easy I think, but I stuck with this level generation method for now.

With a working version of the game, I needed to add graphics. The erratic path the player takes, reminded me of how squirrels sometimes dash around, so I was fairly sure I wanted a squirrel theme from the start. Unlike my previous game, I chose to forgo a game engine, since I already had all the game logic done. Instead, I used the WGPU library directly so I could learn the WebGPU API. Since my game had fairly limited scope, I could make a fairly limited sprite-based rendering framework. I initially designed it so that all sprites had to be the same size, and put them all into one texture array, each sprite as its own layer. Eventually, when adding different sized UI elements became tedious, I switched to a texture atlas system, where sprites of any size are copied into one big 2D texture. I drew all the art assets myself using a drawing tablet and Pixelorama. I started out with very rough placeholder assets, but after I had the rendering for all the gameplay implemented, I started replacing them with more polished sprites. The acorns and squirrels are actually pretty close the original designs, but the dirt tiles and grass background needed to be adjusted from much less pleasant colors, and the buttons used to be pretty ugly too. An experienced artist could definitely still create much better sprites than what I ended up with, but I think what I made is acceptable.

a flat colored, rounded rectangle with a dark-light-dark patterned 3 pixel border
Placeholder button sprite
a pixel art illustration of a rectangular sign face composed of three roughly cut boards of wood
Final button sprite variant

With the game in a more polished state, I revisited the difficulty, and considered how I could make a harder mode that was still feasible. First I tried tweaking the level generation so that instead of picking the largest subset of games for each piece, it picked the smallest. Surprisingly, that ended up not any harder, I think because there were so few good options, it was always obvious which move to pick. Next I tried picking the largest subset for all the piece except one, for which I instead pick the smallest subset. That worked pretty well, because gameplay often leads to dead ends where there's one acorn left to collect. Sometimes the levels turn out pretty easy still, but often they're pretty tricky, and the player needs to backtrack and choose an unintuitive move near the start of the level. I'm still trying to figure out how to make an even harder mode — maybe I can maximize the number of dead-end moves — but I think I'd need to implement save games first, so players can set aside a hard level and resume it later.

Contact

contact@grantmoyer.com