Friday, April 29, 2022

What We Learned in CS222, Spring 2022 edition

This post continues a long tradition of sharing students' responses to the open-ended question, "What did you learn in CS222 this semester?" However, before I dive into the two sections' responses, I need to note that I made a last-minute change in my protocol this year. For ten years, with a few changes around the pandemic, I used a three act structure for the final exam, a portion of which asked the students to make a list of what they have learned. This semester, that didn't feel like the right thing to do. Partially, this is because a few things have happened this semester that I have not written about publicly, but they have left me feeling uneasy. Another reason is that I recently added a "content" portion to the final exam to complement the "reflection" portion, but I realized last Fall that the exam had grown a bit too large for its timeslot. Finally, I also know that change is in the air, as I am not on deck to teach CS222 at all next academic year: my time will be devoted to help spin up the new Game Design and Development concentration in my department. Based on these factors, I decided to move the exercise slightly earlier in the semester.

The exercise itself was still basically the same, but instead of doing it during the final exam, I did in the penultimate day of class, as part of my conclusion to the course. I started by having them create mindmaps on the theme of "programming," which they did on the first day of class as well. Then we launched into a 15-minute timeboxed exercise in logging what we learned. 

One of the problems I alluded to above has been the noticeable lack of attendance. A lot of students are simply not coming to class meetings, or coming with such infrequency that they may as well not be there at all. In the meetings today, which I had announced would be my semester wrap-up, we had eleven people in the first section. That is more than I have seen in three weeks. In the second section, we had only six, which is more than the five that usually show up. An output of the exercise is, essentially, a question on the final exam, and I have no problem letting the people who show up determine on what they will be tested.

The first section came up with 79 items, and so I gave them each three votes. These are the items that received any votes.

  • DRY (3)
  • GitHub (3)
  • Red-Green-Refactor (3)
  • Version Control (3)
  • TDD (2)
  • SRP (2)
  • Time Management (2)
  • Parameterized Tests (2)
  • Java (1)
  • Teamwork (1)
  • Clean Code (1)
  • User Stories (1)
  • JavaFX (1)
  • JSON (1)
  • Abstraction (1)
  • Inheritance (1)
  • Discipline (1)
  • Polymorphism (1)
  • Model-View Separation (1)
  • Documentation (1)
  • Wrapper classes (1)
The second section came up with 52 items, and I also gave them three votes apiece. Here is the subset that received any votes.
  • TDD (3)
  • Clean Code (3)
  • DRY (3)
  • SMART (2)
  • Model-View Separation (2)
  • JavaFX (1)
  • Retrospective Meetings (1)
  • Groupwork (1)
  • Good names (1)
  • Showing up to class is important (1)
Oh, that last one is too ironic for me on the last Friday of classes.

Thursday, April 21, 2022

A second round of the Mob Programming RPG in CS222

This follows up on my epic post from earlier this week. I did indeed continue the theme of Mob Programming in Wednesday's class meetings, although I decided to skip the game entirely. Instead, I began each meeting with a recap of the strengths and weaknesses of Monday: they had learned the fundamentals of Mob Programming, but there were ways in which the game got in the way of the task at hand. I also shared with them my observations that they were trying to act like race cars rather than bulldozers and, in so doing, abandoned the values of TDD and Clean Code, which are the actual topics of the course. We kept the context of FizzBuzz, but we started again with a fresh project.

We used mobti.me once again but with only the two default roles, Navigator and Driver. I was unable to acquire a wireless keyboard in time for us to sit in a row and pass it down, so I copied the same series of names to the whiteboard as we used in the app. This way, students knew when their turn was coming without our having to go to the app's browser window first. I also ensured that audio was working properly before we began.

Another notable difference was that, believe it or not, I had fewer students in both sections on Wednesday than I did on Monday. I had expected the inverse, that those who skipped Monday would hear that there was something really interesting and engaging happening and that this would bring them back to class. (Insert laugh track here.) Instead, the Section 1 had only five attending students, three of whom had been there on Monday, and Section 2 had six attendants, five of whom had been there Monday. The new students looked a little intimidated, but it did not take long for them to integrate into the process.

Both groups got much farther into the project than they had before. Three factors seemed to contribute to their success: they had experience now with the problem (as Fred Brooks said, plan on building one to throw away, because you will anyway); they had experience with mobbing; and I had frontloaded to them a strong reminder to use TDD rigorously. We got through two complete rotations in the first class with time to discuss the experience at the end. We got through almost two rotations in the second class, but I cut off the last Driver-Navigator pair so we could share some conclusions. Between rotations, we took a few minutes to talk about how it was going.

In Section 1, they ran into two categories of problems. One was that the Driver sometimes acted as de facto Navigator, pushing their own vision rather than the mob's. Put another way, the Navigators still tended toward passivity rather than active navigation. We got to talk about this at the end of the class meeting, and I think the students got to a better understanding of this role. The other problem was slipping out of the Red-Green-Refactor rhythm, doing things like refactoring while writing a failing test, or going from passing tests to defining new tests. I shared my observation of this at the midpoint, and there was marked improvement in the second rotation. 

Section 2 was more able to hit the ground running. Various members of the mob held the team accountable to TDD in a more consistent manner. A few Navigators also engaged in improvised dancing while waiting for tests to run. Hey, why not? This group was clearly having some fun with it. They ran into a few problems with failing to meet naming conventions for test names, and toward the end, they headed down a path that would be fruitless, as they tried to factor out essential parts of a method: that is, they would have ended up factoring out every piece into a new method that does the same thing. I tried to help them see that the root problem seems to have been that they started working on a solution (adding a new method to handle something) without really agreeing on what problem they were trying to solve.

During the midpoint discussion with Section 2, a student pointed out that the mob "butted heads" over an issue of how to move forward. I smiled and told them that, back in my day, we just called this "having a conversation." Indeed, that's all it was: a group trying to determine which of two ways to go. It was interesting to me that the students saw this as some kind of competition ("butting heads") rather than the kind of formative argument we should be actively engaging with all the time in higher education.

I am very glad that we spend a second class period exploring Mob Programming. Although neither group fully completed the FizzBuzz problem, they got far enough in to see how a mostly-functioning mob should work. That is, I think it modeled the process sufficiently that a team could take this into their own project work, and crucially, I laid the groundwork to be able to use this in teaching my upper-division courses next semester. I definitely want to add this to my usual arsenal of CS222 tools, although I am not currently in the schedule to teach the course next academic year. That is, indeed, one of the reasons that I have written these posts, so that I can refamiliarize myself with these ideas later.

I am left with the question of whether playing the Mob Programming RPG first was truly necessary. It was useful for me, as someone who has never taught this technique before, to have something to deploy out-of-the-box. However, now that I've seen it in action and spent hours thinking and writing about it, I suspect that I could just start with an exercise like today's. It would be an interesting research study to conduct, although I am certain that the more guided experience a student has with Mob Programming, the better they will understand it.

Tuesday, April 19, 2022

Playing the Mob Programming RPG in CS222

I did something new in Monday's meetings of CS222 Advanced Programming: I had my students play Willem Larson's Mob Programming RPG. A colleague in the games industry turned me on to the idea of mob programming by way of the Mob Mentality Show.  More specifically, the RPG came up in this interview with Joe Justice about mobbing at Tesla. It was the first Mob Mentality Show that I listened to, and it was interesting to jump in with a discussion of how mobbing, which is usually associated with software development, was used for hardware development around electric cars.

Willem Larson was inspired to make a free PbtA-style RPG to teach Mob Programming. Now that scratches a lot of my itches! I encountered a few concerns while reading the rules, including the fact that the introductory script is very long: the amount of ideas presented orally to the team far exceeds what they can remember, especially when you consider that they also are handed three different role descriptions to process. There are certain parts of the instructions that appear incomplete or ambiguous, but rather than start by copy-editing and opening a pull request, I figured I should present the game as-is. Shuhari and all that.

I printed off all the playbooks, reviewed the rules, grabbed the handful of other required supplies, and set off to CS222 Section 1. I figured it was not feasible to plan on everyone sitting in a line, which is recommended in the rules, since I never know how many students are going to show up. I ended up using mobti.me, the first online mob timer I found when searching online for a web-based one. The rules recommend the people on the left and right of the Driver and Navigator help those two manage their XP acquisition, and so to adapt this to haphazard seating, I added the roles "Driver-Helper" and "Navigator-Helper" to the rotated roles.

We used FizzBuzz as our example. Before both classes and in accordance with the game recommendations, I set up separate starter projects. In both cases, I gave a failing unit test as follows:

@Test
public void testFizzBuzz() {
  FizzBuzz fizzBuzz = new FizzBuzz();
  String actual = fizzBuzz.valueOf(1);
  Assertions.assertEquals("1", actual);
}

Of course, without any implementation of FizzBuzz, the test does not compile and hence is "Red." The mobs then started with a failing unit test.

Section 1 proved to be a trial run. I presented the rules as written, answered all the questions, and let them go. Unfortunately, three problems quickly showed up. One was that I had entered the roles in an unhelpful order, which meant that players did not move as intended from Driver to Navigator. After seeing this go poorly, I understood why this sequence is preferable. The other problem was that I did not have the sound configured properly for the online timer. I assumed the problem was that the room's speakers were off (they were) or that my laptop volume was too low (it was). After doing this, though, we still didn't get sound, so I ended up running a parallel timer on my phone. The fact is I had forgotten that the audio buzzer is a configurable option in mobti.me, not a default. Finally, I also noticed that people started putting their badges up, but no one had called out the fact that they had earned XP. Yes, I had announced that when someone earns XP, they should say why; no, the students had no memory of this. It actually took me a while to recognize this error mode, and that put me in a sticky situation: should I interrupt and re-explain the rules? And if I interrupted, should I invalidate or keep the existing badges? I ended up taking the laissez faire approach, although in retrospect, I wish I hadn't.

By the end of one rotation, they had about six or eight badges on the wall, but none of them were authentically earned since no one called out their XP gains; crucially, this means that any student didn't know what valuable thing any other student had just done. Also, throughout the exercise, very few people in the mob participated. The Navigators never really did anything except, occasionally, ask for ideas. They mostly watched the Driver type in reaction to one or two voices in the mob as they talked through a proposed solution at the code level. When these people became Drivers, they just implemented their visions without any Navigator intervention, and as Navigators, they never interacted with the mob.

After the rotation, we had about fifteen minutes left in the class period, so we switched into discussion mode. I explained how I had come across the game, and that I had never played it, nor had I any personal experience in mob programming. I also admitted up front that I noticed them missing that important rule about claiming XP out loud, and I had held back on interrupting their flow. They seemed happy with my candor, and one student even thanked me afterward for running the game. When I asked for their comments, a student pointed out that it didn't feel right, that they didn't think there was a single point of failure, but that at the same time, the ideas weren't going smoothly from the mob to the driver. I pointed out that, if this was what was going wrong, doesn't that mean the Navigator was the single point of failure consistently? After a moment's hesitation, I think the class agreed, although the unwillingness to hold anyone accountable was palpable.

This led to good but brief discussion of the role of the Driver and the Navigator. I pointed out how what they learned in CS120 was, very likely, an antipattern: the Driver works and the Navigator watches. (Maybe we should call this the "Napigator Antipattern".) I pointed out how different the form is when the Navigator has to speak their ideas out loud in such a way that the Driver has to listen and translate it into code. Someone suggested that changing Navigators means that there is not a unified direction for the mob; I made the counterpoint that only changing the Navigator can mean that everyone understands the direction of the mob. Again, I got the sense that they understood it was more complex than it seemed at first blush, but it's hard to say if these particular ideas stuck with the students or not. (In writing this blog post, I did a little research, and it seems some people call it The Strong Technique of the Driver/Navigator pattern when the driver can only do what the navigator suggests. This gives me some nomenclature and resources for talking about how I expect my teams, and the teams in prerequisite courses, to work.)

I was more confident to start the game with Section 2. The same number of students showed up—eight—although more of them late for this section. (Tardiness always irritates me, but cases like this are the most egregious, when someone comes in and, while I'm speaking, loudly asks a friend, "What are we doing?") This time, when explaining the game, I made it clear and explicit, repeating the point that you can only mark the XP if you tell the mob that you earned it. During the pre-game Q&A, students twice asked questions about how they know if they have earned the XP, for cases such as "Listening on the edge of your seat." The answer was easy, and I repeated it verbatim from the instructions: it's up to you to decide.

This section launched right into action, and they clearly were enjoying themselves. Occasionally, someone would say with a smile, "I am listening on the edge of my seat!" There was much more clarity about the roles, as the Drivers acknowledged getting XP for ignoring directions from the mob that didn't come through the navigator, typed things they disagreed with, and asked clarifying questions. Indeed, I think it was in large part the diligent driving that held the Navigators to high standards. Of course, the having the right sequence in place helped too, with the Drivers moving into the Navigator roles before returning to the Mob. For example, a Driver who thought they were typing toward a dead end could, as Navigator, direct the next Driver how to fix it.

There was a lot more excitement and energy in the second section, with people moving around, taping up badges, jotting ideas on the whiteboards, and laughing as they called out their XP earnings. One emergent problem though was that, as roles changed, the new people would wait for things to settle down before starting the next round. That is, a round would end, conversation would stop, and folks would be moving around, cutting out badges, looking at higher level roles, and so on. Even though I encouraged them to just get started, stating that the mob was simply mobbing, the students consistently wanted to wait until everyone was settled and facing front before starting the timer. I wonder if this was because they wanted it to look like a class discussion rather than, well, a mob. This had the effect of significantly interrupting the flow,  killing the energy. It also had the practical effect of using up all our time: so much time was wasted during role rotation that we only had about two minutes for discussion at the end. Fortunately, the students stayed about two minutes after to wrap up the discussion. They also were very helpful in cleaning up the room, for which I am grateful, since we had a colloquium getting set up in the same room for the next hour.

Still, Section 2 did quite well, and with a final score of 31.


During our brief discussion, the students commented that one of the biggest problems with the game is that they spent most of their time looking at their role sheets to figure out how to get points rather than solving the problem before them. This is a serious game design problem, by which I mean it is a serious problem and it is a problem with a serious game. The mechanics of the game here are working against, rather than with, the learning objectives. Indeed, it tempts me to mold what Larsen has so generously provided from his intuition into something that is more rooted in theories of teaching and learning. 

Another student made a good observation about a hesitancy to interrupt someone else's flow. They said that in their final project team, they often felt like others knew what they wanted to do, and so the inclination was to not interrupt even if the direction was not understood. "I don't want to interrupt, and I'll figure it out later," is how the reasoning goes. Spoiler alert: later never came. This allowed us, as the previous section has done, to discuss the Navigator and the fascinating wisdom of rotating that role through the whole team. If everyone on the team can take a turn explaining where the team is going, then everyone understands it, and that means everyone is rowing in the same direction.

This leads me to the most startling observation of the exercise: my students did not come near to solving the problem. I've heard reports and studies about FizzBuzz is still an effective filter since most people who apply for a programming job cannot solve it. Surely, I thought in my naïveté, my students would be able to deploy TDD to come up with at least most of a solution. It was not the case.

Again, we must begin with a disclaimer that they were working under the distractions of the game. They were told, specifically, to try to get as many points as possible. In theory, the points should be earned while moving toward a solution to FizzBuzz, but especially in Section 2, many points were earned by doing the corresponding actions in a vacuous or even antiproductive manner. It's possible that their working memories were completely overloaded in thinking about the game rather than the problem. 

That said, I don't think this explains away all the different error modes that I observed, which suggested more fundamental problems. Both sections showed an alarming lack of rigor around TDD, a practice that is supposed to be a focal point of our work this semester. They are all supposed to be doing Beck-style Test-Driven Development throughout their nine-week projects. Judging from their submitted artifacts, many appear to be doing so. During the game, though, nobody wrote out a plan of test cases that would lead to success: they all started in with coding without any real plan. Indeed, despite the instructions specifically saying that Mob Programming should feel like a bulldozer rather than a race car, the game fought against this: the only way to get points fast is to work fast, so my students throw discipline out the window. I only noted one instance in which a student called out for a failing test to be written before production code. Otherwise, it was a kind of erratic hopping between writing tests and writing production code, with refactoring never really touched. Indeed, in one of the sections, a student said, out loud, in front of me, "Let's just skip refactoring and do it later." Spoiler alert: later never came. However, the horror of saying such a thing in front of the professor who has been proclaiming the importance of refactoring for 13 weeks also points to something subtle and important: I believe I was witnessing authentic student practice. That is, even though I was sitting in the back row, saying nothing, I got the sense that they were not performing any show for my benefit. I was seeing how they really work.

Common definitions of the Driver/Navigator pattern specify that the Navigator should be describing an approach, and the driver turns that into code. In the RPG, it is presented more bluntly: "... [T]he Driver’s job is to type what the Navigator instructs them to type. The Navigator’s job is to sift the ideas of the mob and instruct the Driver what to type." My students took this very literally: the Navigators told the Drivers what to type, keystroke by keystroke. Indeed, one student typed in exactly what the Navigator said, even though it was clearly a syntax error, and then claimed an XP for typing something they disagreed with. The clear problem with this literal interpretation is that it keeps all of the discourse at the level of keystrokes rather than ideas. No wonder they could not solve the problem if they were trying to do so at the level of parentheses! In other cases, however, it wasn't clear if students were being overly literal or if they really just couldn't translate an implementation idea to code. For example, one student said, "We need an 'if' statement: if 'i' is 3." The Driver then keyed in "if (i is 3)". It took over ten seconds for the Navigator to explain that what they meant was "equals equals." Notice here that they did not mean "equals equals" at all. They meant "i is 3," but that turns into "i==3" in languages like Java. This was a breakdown caused by the inability of the students to have discourse about the code at a reasonable level. Not knowing the names for punctuation symbols used throughout programming languages didn't help them either. It seems like a case where the masters have been lecturing them about the Parthenon, but they should have been lecturing about the optative.

I had hoped that my initial failing unit test would push the students to see a clear model-view separation in the problem, but that didn't happen either. I thought it would be obvious from our various class discussions that there is a model that computes the answer and a view that prints it out. The first group, after getting the initial test to pass, agreed to change the test case itself so that FizzBuzz's valueOf method should return an integer. I am still uncertain how they thought this would help. Truly, I cannot get my mind into a state where I can see this as useful, neither the specific change nor the idea that the test I would give them is somehow flawed at the level of static typing. Their implementation of valueOf just ended up being to return the passed-in value. They then added a second method, called isDivisible, that took an int and returned the String that should be printed for it. If you have any experience programming, this should boggle your mind, yet my students happily moved forward with this. Suffice it to say that the rest of their code made no real progress toward a solution, not in any deliberate, structured, or sensible way. If they were ever to get to a solution, it would have been essentially by chance.

In the second section, they started more sensibly, getting the first case to pass in the obvious way and then adding a test for the third word being "Fizz." In doing so, they (seemingly unknowingly) removed the code that solved the first test. However, when they went to run the unit tests, they only ran the new one, the case for three, which passed. I saw a clear regression defect, but they did not, as they immediately went on to writing the code for the "Buzz" case—without having run all the tests, without refactoring, and without writing a failing test first. At some point, several minutes later, someone in the mob saw that only one test was being run, and they instructed the Navigator to ensure all the tests were run. This, then, caught the regression. I suspect that the student who noted the oversight would have noticed it earlier had they not been face-down in role sheets. Of course, in a semester where we learned "Always run all the tests" on the second day of class, I hoped that this knowledge would be more widely distributed, but this seems not to be the case.

Perhaps the most important thing I gained from the Mob Programming RPG were the insights into how my students think and work. Techniques that I've shown them and required on their projects for months are clearly not part of their normal operating mode. That suggests that there really are a few anchor people who are ensuring the right things get submitted on a team, but that this is not being done in a way that the ideas are being disseminated. During the game, I saw how some strong voices could derail the whole team, moving in directions that were not just contrary to our principles, but not even syntactically valid. It makes me want to pull Mob Programming earlier into the semester, although I recognize a logistical problem with that: we're at a point now where about half the class does not show up and a few students have withdrawn. What started as 20-some students in the room has turned into about eight who show up regularly. We could not possibly finish a whole mob cycle with 20 in one class session, which means I would have to split it over multiple days and then deal with absences from the rotation. Mobti.me would not support this, but of course, I'm already imagining whipping up a more robust replacement in Flutter. I need to think about how to deal with this since the benefits of the exercise certainly seem worth the team, even if just for the discussion and my observation of real student practice.

Making my own Mob Programming RPG sounds like a great scholarly endeavor. I think using PbtA playbooks is a clever idea, but the mental load on my students was clearly too much. I am sure that professionals would approach the game differently than undergraduates. Anyone with a few years under their belts would be more adept at programming, communication, and coordination. Crucially, they would also be more familiar with holding each other accountable. My students are trained, well before they get to me, that they should never criticize their peers for anything they do or say. That is completely self-destructive for collaborative software development, of course, but my pointing that out does not change a cultural phenomenon. Students are also swimming in a sea of points, and so if they see that a particular action earns points, they will pursue it regardless of prudence. A good example is in Larsen's Rear Admiral role, in which a player can earn XP for speaking quietly in the Navigator's ear. Twice in the second section, students walked up to the Navigator, whispered something, and walked away, claiming XP. Yet, although what was whispered was actually good advice at the time, they did not regard whether or not the Navigator was in a position to hear or understand, and what the Rear Admiral had to say ought to have just been said aloud as part of the mob. My own hypothetical version of a Mob Programming RPG could capture not just particular learning outcomes of my curriculum, but it could also account for the spaces, headcounts, and population that I deal with regularly.

Looking to the immediate future, I would like both sections of the class to have the opportunity to deploy mobbing to actually solve the FizzBuzz problem. I plan to use another class period this week to repeat the exercise but without the game. That is, we can just do "regular mobbing", using the mob timer with a Driver and Navigator, and see if we can make progress. We will have to start again from scratch since the code from the end of each session is not worth saving. I am concerned that Section 1 may not have seen enough of the practical execution of mobbing, but I think I will try to keep the two exercises in sync rather than allow the two sections to diverge. I will need to start by pointing out the difference between the Navigator telling the Driver what to type vs. describing an approach that the Driver turns into code. Also, I will tighten up role transitions by ensuring that everyone knows the sequence of roles, either by finding a wireless keyboard to pass down a literal line or by copying the name sequence to the board so it's not hidden in a browser window. I am tempted to remind them about Red-Green-Refactor and model-view separation; my hope that they simply remember these may be in vain.

I know that we are all creatures of habit, and I do not expect that any team will make radical changes to their practices in the last two weeks of a nine week project. However, I hope that these exercises will get them thinking critically about their team experience, giving them something concrete to compare it to, and give them a vision for what might be possible. The role of this course in the curriculum is to prepare students for team- and project-oriented upper-division courses, and so I am eager to see how I can leverage this exercise myself as I start thinking about Fall.

This story continues in the next post.

Wednesday, April 13, 2022

Songbooks and Makefiles

Back in 2019, I mentioned polishing up my old mup skills to do some music arrangement. Since then, I have idly thought about putting together some kind of family songbook. Mup is great for lead sheets, but after the family was enjoying listening to some Aquabats songs while painting miniatures, I decided I also wanted to easily make lyric and chord sheets. After some searching, I ended up using ChordPro. These were two separate projects, but I always had in the back of my head that I wanted to bring them together into one sort of family songbook.

My third son has really taken to playing trumpet. Every couple of days, especially since my dad visited, he will grab his horn, and I will grab my guitar, and we'll jam on some easy patterns. Occasionally we get other family members to join in on a spare horn, the keyboard, or some kind of percussion. It's been a lot of fun. It also points to one of the advantages of having typed up my songs: it's easy to produce arrangements in other keys. My mup files, for example, contain simple macros to check whether I have called the application with the -DTRUMPET flag, and if so, transpose the song up a step.

ifdef TRUMPET
  transpose=up maj 2
endif

Last Saturday, I devoted myself to actually pulling together these various mup and chordpro arrangements into one easy-to-reproduce songbook. The tool I ended up using is the classic standard in GNU/Linux: GNU Make. I went for years not having need of Make, but I recently went back to it to handle building Gather 'Round, my family's project for Global Game Jam 2021. Global Game Jam requires a very particular format for archive files, containing source code, builds, promotional materials, and licenses. I had previously managed this manually or with bash scripts, but in 2021, I wrote this Makefile to automate the process. I used a nearly identical one in 2022 for Juxtaposition.

If you look at those makefiles, they are pretty tame. They use the standard features of Make that I learned back in the 1990s: define a target, list its dependencies, provide the rules to build the target. As I started working on my family songbook, I did something similar, listing each .mup file as its own target, with its own source, and with its own command line defining the build process. This was very clearly a DRY problem. I knew how to do wildcards in bash scripts, although every time I write such a bash script, I have to re-learn its idiosyncratic syntax. I figured Makefiles must have something like this, but I had no idea what it was, and I had never seen it.

A bit of stumbling through the information superhighway helped me learn about GNU Make's pattern rules. Combining what I learned from a few StackOverflow posts with the official docs helped me come up with a much simpler and more elegant solution. Whereas I was originally going to use separate directories for mup and chordpro files, with a toplevel Makefile to combine them, this proved awkward without providing real value. I brought all the files into one location and wrote the Makefile shown in the following gist.

The wildcard command gives me what I knew how to do in bash, and the patsubst command is an elegant solution to transforming the strings that are matched by the wildcard. We can look at the simplest case here, the chordpro processing, to understand how these pieces fit together. I'll pull in the relevant variable definitions and targets to aid in explanation.

CHORDPRO_IN  = $(wildcard *.pro)
CHORDPRO_OUT = $(patsubst %.pro,%.pdf,$(CHORDPRO_IN))

$(CHORDPRO_OUT): %.pdf : %.pro
    $(CHORDPRO) $<

CHORDPRO_IN will match all the chordpro input files, any file that ends in .pro. CHORDPRO_OUT then is defined as the same set of files, but with the .pro replaced by .pdf. For each of these expected output files, a target is defined, with the target being the .pdf file, and its dependency being the .pro file. The actual build command is a simple invocation of chordpro, its only command-line argument being the first dependency, which, again, is the .pro input file. Pretty nice.

The Makefile contains a few other tricks. The cover page is a simple affair created in LaTeX. The separate PDFs are combined using pdfunite, which I had never heard of before but certainly did the trick. I ran into one problem with combining the files: if I had mup output PDF directly, the combined file contained errors. I worked around this by having mup output postscript instead, then using the classic ghostscript ps2pdf to do the conversion.

Monday, April 11, 2022

Addendum to Introduction to Game Design and Development at Ivy Tech: A post-presentation bugfix

I just wrapped up a remote presentation at Ivy Tech in which I gave an introduction to game design and development. The presentation included a roughly one-and-a-half-hour workshop on Godot Engine. We built the start of a no-frills platformer from scratch. Unfortunately, there was a little, non-obvious bug in that moving to the left caused the animation to flip out. What makes that worthy of blogging? Well, I told them I'd post the fix on my blog, so here we go!

 This was the original implementation of Player.gd:

extends KinematicBody2D

var speed = 200
var gravity = 10

var velocity = Vector2(0,0)

func _physics_process(delta):
    velocity.y = velocity.y + gravity
    
    var direction = Vector2(0,0)
    if Input.is_action_pressed("move_right"):
        direction.x = 1
        $AnimatedSprite.play("walk")
        scale.x = 1
    elif Input.is_action_pressed("move_left"):
        direction.x = -1
        scale.x = -1
        $AnimatedSprite.play("walk")
    else:
        $AnimatedSprite.play("default")
        
    if Input.is_action_just_pressed("jump"):
        velocity.y = -500

    velocity.x = direction.x * speed
    move_and_slide(velocity, Vector2.UP)

The problem is clearly with scale.x because nothing else would make it flip out like that. The solution is to scale not the whole object but just the sprite. Once I realized that, the fix was easy. Here's the revised version.

extends KinematicBody2D

var speed = 200
var gravity = 10

var velocity = Vector2(0,0)

func _physics_process(delta):
    velocity.y = velocity.y + gravity
    
    var direction = Vector2(0,0)
    if Input.is_action_pressed("move_right"):
        direction.x = 1
        $AnimatedSprite.play("walk")
        $AnimatedSprite.scale.x = 1
    elif Input.is_action_pressed("move_left"):
        direction.x = -1
        $AnimatedSprite.scale.x = -1
        $AnimatedSprite.play("walk")
    else:
        $AnimatedSprite.play("default")
        
    if Input.is_action_just_pressed("jump"):
        velocity.y = -500

    velocity.x = direction.x * speed
    move_and_slide(velocity, Vector2.UP)

Fixing bugs like this while livecoding is like doing elementary arithmetic at the front of the room while holding a marker. That part of the brain just shuts down. 

Saturday, April 9, 2022

Painting Massive Darkness 2: Base Set Heroes and Darkbringer Spirits

Regular readers will know that I've enjoyed painting and playing Massive Darkness. Searching this blog for that title should turn up several posts. I know the game gets flak from some corners of the Internet, but I've logged 32 plays of that game, and my family has enjoyed it. When CMON announced the sequel, with the promise of cleaning up some of the gameplay from the original, I was excited to hear more. I was disappointed to see the "Hellscape" theme. The original was built primarily on classic fantasy tropes, and I was hoping for more of that; fighting angels and demons is less interesting to me. However, the idea of asymmetric heroes intrigued me, and when they added in the Upgrade Pack that lets you use all your Massive Darkness content in Massive Darkness 2, that suddenly seemed like a better deal. I made my decision and backed the project.

Now I have a giant cardboard box in the corner of my office, containing within it several more cardboard boxes filled with too many miniatures. Hooray! I decided to start this set as I did the original back in 2017: the base set heroes. After reading the rules and punching out the tokens, I noticed that the Shaman class can summon two spirits, who are represented by tokens in the base game but have nifty miniatures in the Kickstarter-exclusive Darkbringer set. Who wants to play with tokens when you can have cool miniatures? I decided to add those two spirits to my initial push as well.

As usual, I painted them according to the provided card art. Unfortunately, the cards for most of them cut off around the thighs, but clearly there was full-size art commissioned at some point, because it shows up sometimes in the rulebook and Kickstarter campaign page. A friendly discussion on BoardGameGeek pointed me to the website of Edouard Guiton, who did much of the illustration, and whose page contains the full illustrations. Thanks, Edouard, for posting this! The discussion also pointed me to this incredible effort, which collects all the MD2 characters with their corresponding illustrations in one convenient spreadsheet.

Let's get into it! I will post them in the order I painted them.

Irk (front)

Irk (back)

First up is Irk the Forest Sprite, which I started with because I think it's goofy. For some reason, together with undead, skeletons, demons, and fallen angels, Massive Darkness 2 includes these bizarre round-headed fairies. One needs to warm up the painting process with something. The illustration shows the ... carapace? ... as being very shiny, an iridescent green like a beetle's shell. I've never done any real intentional non-metallic metal before, but I've watched enough Sorastro to know the basic idea is to go from dark darks to light lights. I tried to do that on the green parts, and I think it turned out pretty good without spending inordinate time on it.

Maybe what confuses me most about this sprite is that it's wielding that big sword. Imagine swinging that sword while flying: you yourself would go spinning away from the effort. Something like a spear seems more appropriate, but really, if you can summon fire and ice spirits, why not just focus on that and carry a lucky charm or a wand or something? 

Speaking of which...

Fire Spirit (front)

Fire Spirit (back)

The fire spirit was an interesting challenge to paint. The character art is highly stylized, with the hottest part of the flame being around the face. I think I was able to translate this pretty well into three dimensions. 

Ice Spirit (front/left)

Ice Spirit (back/right)

The ice spirit, like the fire spirit, was fun to paint. Each successive coat of paint really added to the whole thing. When painting more conventional miniatures, you can only work to improve one area at a time. Figures like this, that have the same colors throughout, give a better sense of progress as you work on them.

Both the spirits were pretty badly assembled. In both cases, I filled the gaps and tried to smooth the joins with plastic putty. Part of me wishes I had taken the extra time to get out my milliput and done a finer job. It will only make a difference if the board game is great rather than just fun.

Incidentally, whereas I used zenithal priming on the heroes, I primed the spirits in just plain white. The white from the airbrush was not as bright as I wanted, so I also put on, oh, let's say a million coats of white over that to try to get a clean, non-streaky base. I have on progress shot here, which I think I took after an hour of painting white so I could share it with my brother and question my existence.


My next step worked brilliantly, but I don't have a photographic record of it: I went over the fire spirit again with a warm white (mixing just a little yellow into it) and the ice spirit in cold what (mixing just a little cyan). This made all the difference in the world. Setting the two next to each other, one could already see that one was "hot" and the other was "cold".

Irk and her Amazing Friends

That's enough for the Shaman. Let's look at the other heroes.

Sir Ronen (front)

Sir Ronen (back)

Next up is Sir Ronen. I tackled him next because he seemed pretty straightforward, and I wanted to think of something good to do with his rather plain steel armor. I wanted to use some of the techniques that Dr. Faust described in a video about a year ago, but I had no luck with that. He is using inks diluted with a little water over the same kind of Vallejo Metal Air that I am using, but I find that my inks get hydrophobic: I lose a lot of control as the ink-water mixture beads up. I ended up mixing in some glaze medium, which helped get some control back, but it still didn't behave like what I see in Dr. Faust's video. He also is using colored inks to great effect, but the card art here didn't suggest a color beyond brown, and I quickly found that adding brown just made it look dirty. I ended up doing an ad hoc combination of the mixture above, pin washing, my P3 Armor Wash, and a little stippling to add wear. 

The real bugger here was the skirt that he's wearing. It is rather dashing, but it's not clear that it's practical. He's a paladin, so maybe it's part of his order. Anyway, it took some time to get a good khaki color and shade it properly, and after a long time working the skirt, it looked okay but uninteresting. Looking at the card art, there's a slight suggestion of an orange fringe on the skirt, though it's hard to tell if it's intended to be part of it or a lighting effect. Adding the little orange fringe on the figure gave it just what it needed.

Gheta (front)

Gheta (back)

Gheta was fun to paint, with some interesting details, dedication to earth tones, and a big fur cloak. I was probably an hour into painting her when I realized that the sculpt has no left arm at all. My imagination had put an arm under the furs, but then I realized one could also tell a story that this berserker had lost an arm in battle. Either one works, although if I had just one arm, I probably would not choose such an axe.

This was actually the figure that had me looking up the card art since the legs were ambiguous: it was hard to tell what was supposed to be metal armor vs. leather vs. flesh vs. something else. Unfortunately, the card art didn't really help here except in showing that the sculpt seemed to diverge from the card art in the legs. 

For the furs, I used the classic quick approach I learned from Sorastro's Wookie videos: wet-blend the highlights and shades and then bring the tone together with a wash. A few touch-ups can be added as needed, but otherwise, that's usually satisfactory.

Nahias (front/right)



Nahias (front/left)

Hey, a centaur hero! Pretty neat! Sure, it's again mixing sylvan and hellish themes, but centaurs are fun. Imagine the big centaur, twice the size of the heroes, imposing like Li'l Ned from the original, charging into battle with a spear and a ... hey ... wait ... an archer? Um, ok. You just... stay back there and shoot arrows then, and we'll send Irk to the front lines with her big sword. (Don't get me started on triple-arrows.)

Nahias is large but was pretty straightforward to paint. Painting horses is hard, but fortunately, Nahias' armor covers up much of what would be the challenging horse parts. I approached the armor like Sir Ronen's, and it's nothing special here. I tried to bring out more of the contrast, but a lot of it, especially on the hindquarters, is just sort of big plates. Fortunately, the contrast of the white streak in the hair and tail draw attention away from that.

I'm proud of not having ruined the shield when freehanding the serpent on it. The shield itself already looked good, and so it was with some trepidation that I came in with the aqua paint. I used advice I've come across many times about freehanding: start with just simple lines, like a skeleton, and work outward from there. I decided to also add a little highlight and shade to it, which gives it a little extra pop without drawing too much attention.

Feydra (front)

Feydra (back)

I wonder if the narrative designer came up with this character some velvet morning. The illustration has Feydra's cloak spread out under her arms, but the sculpt has it flared out with much more gusto. It's really dynamic, as if she has just leaped down from a ledge or maybe is Naruto-running toward Area 51. In any case, from the front, it's a pretty straightforward purple-brown paint job with gunmetal greaves. From the back, though, it was a real challenge. The illustration clearly shows that the inside of the cloak is a muted pink. I struggled to get a good match for the flared-out cloak. Note that, despite it being flared out, all of it is still in shadow if conceived from above. I ended up toning it way down except for the very fringe. At first, I was not very happy with it, but I was tired of repainting it. The more I let it sit, the more it grew on me. Lemons and lemonade.

In any case, one of my favorite parts of this sculpt is how the right side of the cloak is not uniformly open like the left side: it is rippled, and so seen from the back, you can see both the front and back colors of the cloak in an accordion fold.

Also, wearing a mask in a dungeon is like wearing a skirt as a paladin. Sure, it looks cool, but it's like taking a handicap.

Mathrin (front)

Mathrin (back)

This wizard was probably the most fun to paint. I saved him for last just in case it was more tedious than fun. These colors are unlike anything else I remember painting, and the patterns of color are much tighter than most things I've painted. I started with the red-orange robes, painting it all in a uniform color. Then, I mixed the yellow for the fringe and laid that down, and finally, the cyan just over the edges. The robes are wonderfully sculpted, and I think the paint job turned out great.

Next, I worked on the curious chestpiece. Fortunately, the sculpt is very generous here: the details and edges are sculpted right in, so it was not as bad to paint as I had feared. The red around the "eyes", for example, is actually a raised ridge, which is much easier to paint cleanly than to freehand symmetric rectangles. 

The triangles on his stole were tricky. I put together a mix of yellow and grey, got out one of my finest detail brushes, and started by laying regular horizontal lines across the front. Then, my plan was to lay down diagonal lines across the whole thing for perfect symmetry, but I started with some triangles to get a feel for it, since I still had the base color to cover up any mistakes. I ended up just doing triangles all the way up, occasionally extending guidelines diagonally, but trying to get something visually interesting rather than completely regular. After all, there's no adjusting paint after its laid down, and I was really hoping to get this in one pass. With a few touch-ups in the base color, I think it turned out well.

The back was another story It tried the same technique, but notice the wrinkles on the back. Getting a "straight" line across the back was difficult. In fact, I suggest that the illusion may have been impossible because I don't think cloth can do what is sculpted into the back of the stole. I think if you flattened out the shape shown here, it would not have straight edges. In any case, I tried, and it looked terrible, and I painted over it. Rather than fight with triangles over those folds, I decided to take a trick from Sir Ronen that matched the robe on Mathrin here: use some edging for easy visual interest without risking having to repaint that whole section a third time. The results are much less flashy than the front, but I think it looks good and am happy with my choice.

Base Set Heroes and Summoned Spirits

That's all for today. I'm excited to get the game to the table with painted heroes, but it looks like that may not be this weekend—at least not a six-player game with the whole family. Now, I'll need to go back to my giant cardboard box and figure out who gets painted next.

Monday, April 4, 2022

Ludum Dare 50: Bomp

This past weekend was Ludum Dare 50, a milestone event for my favorite game-creation event and its 20th anniversary. The theme was "Delay the inevitable." I sketched out a few ideas on Friday night based on the theme. Many of these were tossed quickly due to their art requirements, and I did not feel like investing a lot of time in drawing or trying to remember anything I've learned about 3D modeling. 

The idea that I kept coming back to was to explore one of my favorite inspirations, Every Extend. I have never played any of the commercial releases, but I've been intrigued by the original since shortly after its 2004 release. I wish I could remember how I even came across it. The game was the subject of a case study that I ran in 2006 in my first graduate class at Ball State. That led to my first SIGCSE paper and my first journal article. I have come back to it regularly in my game programming classes. I use the core systems of Every Extend as a sort of kata when learning new game engines: I've rebuilt it in plain Java, Slick, PlayN, Unreal Engine, and Godot Engine at a minimum. Those were all just technical experiments, though. I had never actually built the core systems into a complete game, even a small one for jam.

Until now.

For Ludum Dare 50, I made Bomp, an homage to Omega's original Every Extend. Just like the original, the core tension is that you start with a small stock of bombs, and you blow them up to earn points. Earning more points means you earn more bombs, but the number of points you need increases for each extension. This is where "delay the inevitable" comes in. The game is so short that even though running out of bombs is inevitable, you always feel like you can try one more time to beat your high score. (This kind of game really screams out for a local high score table, but that was out of scope for the 48 hours of Ludum Dare.)

I was able to explore a few specific game development ideas in the building of Bomp, and I'll share a few word about them here.

I have one explosion sound, a simple retro sound created with the SFXR plugin to LMMS. In order to add some variation, I pitch-shifted each instance of it. I did a little research to figure out how to get a normal distribution rather than a "flat" random number generator. By using randfn, I was able to shift the pitch of each one within a 0.15 standard deviation. The result is that they sound almost the same, but just different enough to be aurally interesting.

Most of my jam projects are 2D games, for various reasons, and many of my old Every Extend experiments have been implemented in 2D. For Bomp, I decided to build it in 3D, using CSG primitives for all of the asset needs so that I would not have to drop into Blender or some other modeling tool. This was easy and quick, and the result matches the gameplay aesthetic I wanted. The player's bomb is simply two tori that rotate independently, the obstacles are squares that spin, and (spoiler!) the "shooter" that comes in at the 30-second point is a combination of a box and cylinders. I had imagined exploring 3D shaders to get some more interesting visual effects, but I ended up putting my limited energies elsewhere. One thing I did explore was Godot's WorldEnvironment and its distance blur effect. This got me a great return on investment, but unfortunately, it does not work in the Web build. 

Windows version: Cool blurry background

Web version: Blurless

Two things I noticed about this blur effect are worth noting for future reference. First, if the model's material has transparency enabled, then it will blur regardless of distance from the camera. My best guess is that the alpha data is overloaded: these same data must be used for distance blur as are used for transparency, and the former supersedes the latter. Second, I had to switch to the GLES2 renderer in order for the Web build to work at all. When using distance blur and GLES3, the Web build was unusable, rife with visual artifacting. When using GLES2, the blur didn't work, but at least it showed the game as expected without it.

Over in LMMS, I wrote my first sketches that use effects and the automation track. My first pass at a theme involved an overdriven guitar playing over a bass, and I used a tremolo effect together with automation. This sketch ended up being too dark for the gameplay, and I abandoned it, although the project file is in the repository. For the song that ended being the main theme of the game, I used the automation system to pan the channel of the pads over time. I never did try that with headphones on, and maybe it's too subtle to hear. Or maybe I did it wrong. In any case, I know at least have a sense of how the effects system and the automation tracks work together with the main mixer.

Related to the music, I have been working on trying to get diminished chords more into my playing. If you can live long enough in my game (or click the link above to just hear the soundtrack), you get to a part of the song that uses some simple inversions followed by a cool diminished chord, right before the end of the loop. Now if I could only find the guitar chord fingering for diminished chords as quickly as I can arrange them in LMMS...

Those were the novel areas for me this time around. I am pleased with the result: everybody seemed to enjoy it at my family's play party last night, and most of the folks who have checked it out have given good feedback. Once again, I owe a lot to Omega (Kanta Matsuhisa), whose gameplay ideas I explored here. The tension in the core system is all due to him, but still, I am proud of my work as a quality execution of one of my favorite ideas. I am pleased with the software design as well, for those who are interested in such things.

You can find all the links to the source code and various builds from the project's Ludum Dare page. While you are there, be sure to check out the games made by my two oldest sons as well, The Poisoner and Grim Reaper vs. You! Featuring the Narrator!

Sunday, April 3, 2022

I think.

I used to teach so that students could do what is right and live well. Now I teach so that students can do what is good and die well.