I have an article in the latest issue of Well Played. It is a special issue with the theme, "For the love of games," and the editors invited articles from games professionals about a particular game that impacted our career paths. My article certainly has the best title I have written: The Thief of Fate and the Devil Whiskey. It was a delight to write, and I hope readers enjoy it.
Paul Gestwicki's Blog
a blog for reflective practice that was cleverly named after its author
Thursday, April 17, 2025
Article in Well Played special issue, "For the love of games"
Monday, April 14, 2025
Making Dart test helper methods show up in the structure view of Android Studio
Why such an awkward title for this post? It is the kind of search I was doing the other day when I found no hits. I'm writing this in hopes that I can save someone else the trouble I faced when the solution is, in fact, quite simple.
My Dart project contains many unit tests, but many of them use helper methods rather than calling test directly. Unfortunately, I was deep into this approach when I realized that these tests were not showing up in Android Studio's Structure view, which is otherwise a good way to navigate files.
Here is an example to illustrate the problem. Notice that the call to test is nested within the helper function, testExample.
import 'package:test/test.dart';
class Example {
bool isEven(int value) => value % 2 == 0;
}
void main() {
final example = Example();
void testExample(
String description, {
required int value,
required Function(bool) verify,
}) {
final result = example.isEven(value);
test(description, verify(result));
}
group('Example.isEven', () {
testExample(
'2 is even',
value: 2,
verify: (result) => expect(result, isTrue),
);
});
}
When this is opened in Android Studio, the Structure view looks like this:
Notice how the group is empty.
The other day, I tried different searches to find an answer. It seemed to me that there had to be some way that unit testing libraries communicated their structure to JetBrains IDEs; it could not be the case that the JetBrains engineers were doing simple string matching in source files. Yet, I had no luck. In my confusion, I even turned to ChatGPT, which confidently told me that the only way to do it would be to refactor all of my tests into (yet more) higher-order functions so that I was calling the standard test function at the top level. I asked it for a reference, hoping to find the documentation I had unsuccessfully been searching for, and it unhelpfully pointed me toward two web sites that don't address this issue. Still, I put a potential refactoring into my project plan, although with over a hundred tests and counting, and with a rather eloquent current solution, this would have been both tedious and disheartening.
One of the reasons I started writing helper methods in this particular style—that is, by sending named functions as parameters—was because this is how the bloc_test package handles testing. A day or two after having tried to search for a solution to my problem, I was in a test file and noticed that all my blocTest calls did show up in the Structure view. How was that possible? Thanks to the MIT license of bloc_test, I checked the implementation and found the @isTest annotation, which comes from the meta library. Quickly reviewing its documentation, this was clearly exactly what I needed. I included this little annotation to my project.
import 'package:meta/meta.dart';
import 'package:test/test.dart';
class Example {
bool isEven(int value) => value % 2 == 0;
}
void main() {
final example = Example();
@isTest
void testExample(
String description, {
required int value,
required Function(bool) verify,
}) {
final result = example.isEven(value);
test(description, verify(result));
}
group('Example.isEven', () {
testExample(
'2 is even',
value: 2,
verify: (result) => expect(result, isTrue),
);
});
}
The result was that my Structure view looked exactly how I wanted it.
There you go: the solution in Dart is to annotate the helper method with @isTest. The documentation of that annotation make it clear that it solves my problem, but I must not have hit the right keywords with my original search. I hope that this post helps anyone else who is caught by this issue.
Monday, April 7, 2025
Ludum Dare 57: A Weekend with Flame
[UPDATE 1 on April 7: Added paragraph about effects and tweens.]
I am still working on the Flutter-powered game that I mentioned before, although I had to put it down for about three weeks while I worked on a departmental report. This past week, I was able to get back into working on other things, and I was somewhat surprised to see Ludum Dare 57 coming up. I am glad the organizer was able to get the support he needed to run the event. On Friday, before the jam started, I spent several hours tinkering with Flame. It is a Flutter-powered game engine that I have known about for some time but, until Friday, had never tinkered with. I pieced together a minimal arcade-style interaction following my usual inspiration: Every Extend. It was enough of an exploration that I figured I could try using Flame for Ludum Dare, even though I knew it would be slow going.
At 9PM Friday, the theme was announced: depths. I sat with a paper and pencil and started doing some doodling. As I drew out some screens for a rather silly concept for a fishing game, into my mind came the Bonzo Dog Band's "Straight From My Heart." The juxtaposition of these two tickled my fancy, and I decided to go with it.
My hope was to spend most of Saturday on the game and then be done with it. Alas, it was a lot slower going than I had anticipated: I ended up spending all Saturday and much of Sunday on the project, and I still didn't get to some of the juice that I had thought of as essential for the experience. Of course, if the goal was to make the game as well as I could, I would have used Godot Engine. My goal, instead, was to learn as much Flame as I could to finish a project within a 48-hour window, and that, I did.
You can check out Fish Face on its Ludum Dare page or go directly to the web-playable version. The source code is on GitHub. The title of the game is a bit on the nose, and I had originally wanted the entire experience to be more surreal. I would have then given it a name like It's a baseball cap on top of an umbrella, but maybe I can save that for my next surrealist project.
Before I got into the technical implementation, let me say a little about the music, which may be my favorite part. I used to do more songwriting, but these days, I only ever eke something out during a game jam. A student pointed me toward some different SoundFont files, and I had downloaded a few last week to tinker with. That tinkering came during Ludum Dare 57, when I adapted the chord progression, transposed, from "Straight From My Heart" into a doo-wop ballad. This arrangement entirely used the Arachno soundfont. Curiously, in my head, this was the actual instrumentation of the Bonzos as well, and it was only after going to the recording that I realized it's primarily guitar, bass, and drums, with just a little sax and then, later, Hammond organ. It was intentional though that my arrangement could be played by the Bonzos. I hope Neil Innes doesn't mind the rhythmic eighth notes the piano got stuck with. In any case, I was really happy to arrange something with a diminished chord, an augmented chord, and modulation.
As for Flame, I will share my experience as some observations about "pros and cons." Keep in mind that this is my first experience with it. Much of the friction undeniably comes from my trying to think about the problem in the way the Flame architects intend it to be used. My intuition at this point is to approach it the way that Godot Engine would handle it, but while there are similarities, it is not the same.
Benefits of Flame
Challenges of Flame
Wrapping Up
Wednesday, February 26, 2025
An Android Studio Live Template to simplify defining loggers in Flutter
This morning, I came across a discussion on the Flutter Forum about logging. I recently added rudimentary logging support to a side project, and I was interested to hear the perspectives of people with a lot more Flutter experience. Using Android Studio's Live Templates to reduce boilerplate sounded like a good idea. In my case, for every class where I want to log something, I want something like this:
final _logger = Logger('$MyWidget');
The dollar sign there is important because it uses string interpolation to insert the name of the class. If I rename the class, the refactoring tools in Android Studio will also replace this reference.
I don't remember the last time I made a Live Template for a JetBrains IDE, or if I ever have, so I decided I should take a not here.
I created my Live Template in the "Dart" category. This is not necessary, but it is sensible. I called it "logger" and it looks like this:
final _logger = Logger('$$$CLASSNAME$');
Under "Edit Variables," I configured CLASSNAME to use the expression dartClassName(). Checking "Skip if defined" means that keyboard focus skips over the variable after expanding the template, which is a nice feature. I set the context to Dart/Other.
Using double-dollar-signs to escape a dollar sign is a little strange, but once I got past that, it was easy to set up. One unexpected pitfall is that if you erase a variable from the template and then retype it, you have to remap it to an expression: the IDE doesn't "remember" that the variable had a previous definition while working on the template body.
Tuesday, February 11, 2025
Notes from "No Estimates"
I recently finished reading Vasco Duarte's No Estimates after hearing about it on Dave Farley's Continuous Delivery YouTube channel and Allen Holub's #NoEstimates talk. I had been curious about the #NoEstimates movement for some time, reading an article here and there, but this was my first real attempt to understand it. The book itself is clear and direct, interleaving traditional content with an ongoing fictional narrative that motivates and reinforces the ideas. I found many connections to my research and teaching interests. In this blog post, I will share a few findings from my notes and reflections.
Estimates
One of the foundational principles of the book is fairly simple but not something I had considered before: an estimate communicates the peak of a probability distribution. For example, if I estimate a task to take two hours, I am saying that the most likely case is two hours, but it could take as little as zero or negligible time, but it could also take a low probability that it takes forever. The cumulative probability is the area under the curve. From this, we can conclude that the probability of being late is much higher than of being early.
Duarte classifies estimates as waste as defined by a Lean perspective, that more of it will not make the product better from a user's point of view. It clarifies that "no estimates" isn't a goal but a vision: estimates won't be axiomatically removed but minimized. The discussion of waste got me thinking about QA testing in games, a point I will return to below.
Managing time, scope, cost, and quality
I regularly talk to my students about how cost, quality, and scope are the three levers we can control in project management. Since our work is constrained by the semester's schedule, I point out that we cannot shift cost; that is, we cannot simply add more time to the end of the semester to be able to get our projects done. I also argue that quality is non-negotiable: the point of undergraduate education is to learn how to work well, so sacrificing that is against the telos of the endeavor. Therefore, the only manipulable lever is scope. This perspective seems to help students understand why we focus on user story analysis, prioritizing based on features that add value to the users.
I wish I could remember where I encountered that heuristic since it is distinct from a similar concept that dominates Web searches: the project management triangle, which is also known as the triple-constraint model or the iron triangle. This model explains how the constraints of scope, cost, and time are connected such that cutting one without changing the others will result in a loss of quality. Duarte uses this model in his book to draw a distinction between value-driven and scope-driven projects. Traditional management approaches are scope-driven, where the scope is fixed and so cost and time are unbounded. Scope-driven projects instead fix time and cost, leaving scope flexible, which leads to the approach of delivering the most value first. This is a standard agile perspective, but I previously didn't have the nomenclature of "value-driven" and "scope-driven," perhaps in part because in my academic environment I rely on that alternative model described above.
Reducing variability in throughput
Planning the details just in time
The medium is the message
Using social complexity to determine tactics
- Predict progress with #NoEstimates
- Break things down by value, not effort
- Agree on meaning with social and technical complexity, reducing risk
- Use RIDICULOUSLY SHORT timeboxes
Closing thoughts
Saturday, February 1, 2025
The Goal of Higher Education: Remarks at the BSU College of Sciences and Humanities Dean's Honor Reception
By virtue of receiving the inaugural Teacher of the Year award from the College of Sciences and Humanities, I was invited to give some remarks at the Dean's Honor Reception. The reception is later this morning, and these are the remarks I intend to give. I will be working from notes, not this written form, so the precise delivery will certainly vary, but these are the main points. Also, the original title for the talk was "The End of Higher Education," which was a pun on the two meanings of "end," but it was softened to "The Goal of Higher Education."
EDIT (Feb 2): The talk was well received. I have edited my original post to contain a few turns of phrase that I used spontaneously at the time.
In preparation for this talk, I read through two of the notebooks I keep whenever I read a book. It was purely delightful: it's like someone wrote a book that contained only things that I find fascinating, inspiring, or challenging. I hope that you also keep notes when you read. It will bring you joy to read them later.
I searched my notebooks for an answer to the question, "What is the goal of higher education?" I am sure you have your own answers, perhaps dealing with careers or impact on the world. In the Republic, Plato asserts that the goal of higher education is to love what is beautiful. I think he's right.
You might ask, "How do we come to recognize beauty?" I put it to you that beauty is already there, that it's a transcendental property of the world around us. The question then becomes, "How do we fail to recognize beauty?"
In The Lord of the Rings, Gollum was not free to recognize beauty. Gollum could only see the One Ring. Because he was distracted by this created thing, he was blind to the beauty around him. He elevated this created thing beyond its station, and this led to his downfall. Tolkien scholar Joseph Pierce talks about how we can all become Gollumized. We can become so distracted by things that we fail to see beauty.
German philosopher Martin Heidegger wrote that we are not free if we believe that technology is morally neutral. Heidegger recognized that humanity has always used technology, but he said that we are not free if we think of this technology as neutral. Take the humble hammer as an example. With a hammer, I can pound nails, but remember the old saying, "When all you have is a hammer, every problem looks like a nail." It's true. The hammer has moral agency. That is to say, the hammer affects the moral decision space that you are in. With a hammer in hand, striking things with it becomes an option. The hammer has moral agency. How much more so the smart phone?
Canvas is a technology. Canvas affects your moral decision space. Canvas color codes your grades so that you "know how well you are doing." Canvas gives you notifications, so that you stop what you are doing and pay attention to it. Canvas gives you confetti when you turn in work on time. Beware. Larry Muller, in his brilliant book The Tyranny of Metrics, writes about how the calculative is opposed to the imaginative.
How is one to recognize beauty while tempering the calculative? I have time to share with you three stories.
My friend Dannie was an undergraduate architecture major here back in the 1990s. He asked one of his professors, "How am I doing in this course?" expecting a quantitative answer. The professor took out a piece of paper and, along a line, drew an egg, a tadpole, and then a frog. He pointed to a spot on the line and said, "You're about here." Now that is a midsemester grade!
Last weekend, I ran Global Game Jam here on campus. This is an event where people get together and, in 48 hours, create original videogames. We had over 40 people attend, mostly Ball State students but also students from other places as well as community members. There was no judging, there were no grades, there were no prizes, there was no competition, yet at the end, we had made eight original videogames, which are now available to the world for free. We made them for the joy of creating them and the pleasure of sharing them. This event is a global event, and all together, this community created over 11,000 games, just for the sake of beauty.
I regularly teach a required sophomore-level programming class. In this class, I use a system called "achievements", inspired by video games, that let students earn course credit by doing things that are outside the normal course expectations. One of the options is called, "Detox," and it requires a student to go 24 hours without looking at a screen. Every semester, a few students try it, and the beauty of their essay responses would make you weep. Students have told me how they walked across campus and really heard the birdsong for the first time. Others write about how they reflect on their life, how they got to where they are, and their hopes and dreams. My favorite story is of a student who, instead of doomscrolling on Instagram, took her grandmother for coffee. There is nothing better than that.
I put it to you that beauty is all around us—well, unless you work in the Robert Bell Building, like I do, but we're doing our best.
Look at the people on stage here. They are beautiful. Look at the people sitting next to you or behind you. Really! Do it! They are beautiful.
Speaking for myself, I don't care what grades you get. I want you to find your One Ring—because we all have them—cast it into the fire, and then join me in life's great adventure of loving beauty.
Wednesday, January 1, 2025
The Games of 2024
It's time again for my annual reflection on the board games I played in 2024. This year, I logged 337 board game plays, which is 71 fewer than last year's 408. I played 56 different games this year, which is also less variety than last year. The year featured several campaign games, which tend to be longer anyway, but we also had fewer nights for games. The boys have gotten involved in more activities as they have gotten older. I am not sure what this year will bring, with my oldest son likely to move out to college in the Fall semester. He's the one I have logged the most plays with, clocking in at 2,870 plays since I began logging. I am eager for what the future holds, but I'm not sure we'll ever see the same number of plays as we had a few years ago.
Here are the games I played the most this year:
- Bang! The Dice Game (19)
- ISS Vanguard (19)
- Ark Nova (18)
- Dungeons & Dragons: Temple of Elemental Evil (18)
- My Island (15)
- Res Arcana (14)
- The 7th Citadel (13)
- Crokinole (13)
- Thunderstone Quest (13)
- Oathsworn: Into the Deepwood (12)
- The Castles of Mad King Ludwig (11)
- Colt Express (11)
- Cat in the Box (10)
- Everdell (9)
- Heat: Pedal to the Metal (9)