Tuesday, August 4, 2020

Quill: Stories and Games

I did some research into single-player tabletop RPGs as part of my planning for my Fall game design class. I would not have taken the time to do so had I not recently been blown away by Tim Hutching's Thousand Year Old Vampire. I backed that project on Kickstarter on the recommendation of some game design guru that I follow on social media, although now I could not tell you who. The experience of playing TYOV was amazing, much richer narrative than I expected, and it made me change my assumptions about single-player RPGs.

I decided against requiring my students to buy TYOV: although I think it is worth $15, I know that my value per dollar is not the same as my students, so I've left TYOV as a recommendation. When I looked around to see what other single-player RPGs were recommended, a few titles came up regularly. One was Ironsworn, but this was clearly too heavy-weight to use in class. Mythic comes up as the premier “oracle” or simulated dungeon master, but this also was too heavy and didn't hit the point I wanted to make about constrained storytelling.

The third that I saw recommended in a few places was Quill: A Letter-Writing Roleplaying Game for a Single Player. Right off the bat, this hit strong notes for me, taking the unique form of letter-writing and, since it is pay-what-you-want, it is essentially free for my students. Indeed, I wrote this game into my course plans before having played it, based on recommendations and my reading the rules.

Yesterday, I tried it out with my wife and two older sons. Yes, it's a single-player game, but there's no reason you cannot play next to each other and share your tales.

Caution: Spoilers about the scenarios below.

Although I had read the rules and saw that there were multiple scenarios, I did not really read the scenarios themselves. I was surprised then when the first one in the book was to write a letter of condolence to the archduke on the death of his sister. I did not expect the letter to be poignant, and this certainly gave some gravitas to the letter-writing experience. My younger son had trouble getting started, struggling with structure and form, but he did fine once we encouraged him to let loose and let it be silly, if he wanted it to be. And, indeed, it was very silly, though the rest of ours were serious.

I was surprised when cleaning up to see that another of the four scenarios is also about death. This actually made me a little angry, like the author was playing too much on our heartstrings rather than relying on solid design—like the strings in a movie soundtrack, I felt that he was toying with our emotions rather than allowing us to grow them ourselves.

It was pretty clear in our post-writing discussion that the “game” is really about getting points for rolling dice. The letter-writing is almost a separate experience from the game of rolling dice. I like the ink pot system, which requires the player to have a particular listed word in each paragraph provided some constraint to the creativity; yet, one could just write a word, roll the dice, get points, and “finish” without engaging in the story at all.

This brings me to my point, which is the reason for taking time to write this second blog post today. I hope that my students enjoy the game, and I hope that they bring up the point I made above, that the dice “game” seems separate from the ”story.” My question, then, is what makes Quill any different from a game like Final Fantasy? In Final Fantasy, you play a game about making numbers go up by maximizing skills, finding items, and exploiting combinations. Meanwhile, there's a movie going on about spiky-haired teenagers saving the world. There is, from a systems design point of view, no connection between them except that you get rewarded with story for doing well in your game.

Hopefully, writing this down here will help me remember to bring this up when we get to Week 4 of the course plan.

Summer Course Revisions 2020: CS439 Introduction to Game Design

I am grateful to once again be able to offer my Introduction to Game Design course as a seminar in my home department. Like last year, it will be offered as CS439, and I've asked the front office to waive prerequisites so that anyone who wants to take the course can do so. I published the course plan earlier today, which lays out details up through mid-semester and sketches the rest.

I wrote earlier about my revisions to Game Programming and Human-Computer Interaction. I saved my game design course for last, knowing it would be the hardest one to transition from face-to-face instruction. The whole course was really built on a model in which students present their work to each other, and I correctly deduced that working out some of the logistics of my other courses would help me focus on the substantial changes required for this course. Additionally, it was between planning the HCI course and this one that I received official word that my courses would all be entirely online: this gave me some freedom on this course to know, ahead of time, how the students would move the course, but it also means I have to go back to the other two courses and remove any synchronous aspects.

Early in Spring, I picked up two books to review as alternate textbooks, but given all the other chaos around us, I fell away from reading them and decided to stick with Ian Schreiber's Game Design Concepts. This allows me to keep the kind of cadence I want in the class despite the format change, and having used this text many times in the past, I have a sense of how students will engage with it. In part to compensate for the lack of face-to-face discussions, I have also added more readings from different authors, trying to round out students' learning experiences.

I will miss the discussions though, since this was really where the best learning happened in conventional classrooms. Most assignments in the past had the requirement to present a summary poster to class, and it was a great joy to see them all posted on the wall. I believe it gave students a great opportunity to get to know each other, as the work was clearly personal rather than abstract or faceless. I have replaced these with required discussion board posts and responses, which I have never used before. I am hopeful that we will be able to make some hay with them, but I am dubious that it will be equivalent.

One of the things I have been writing about for some time is the desire to have my students engage with some of the great games, but asking them to play more always felt like I was asking too much from a one-semester introduction. With the removal of face-to-face class time, I have injected more required playings. I divided each week into two parts: playing with ideas and working with games. The former are building the fundamental skills and knowledge for game design, and the latter is a path through important games and game mechanisms. The two work in slightly different rhythms, because the former requires responses to discussion board posts while the latter does not. I hope that the students will be able to keep these straight, and I think clever deployment on Canvas will help.

By adopting Board Game Arena and Roll20, we should be able to maintain the ability to play some games together. The part that I haven't quite written up yet is the glue between the first half of the semester, in which students build their background knowledge, and the second half, in which they work on a project. I would like to use a week or two in the middle to look more carefully at solo print-and-play games, which seem salient given the COVID pandemic. I am eager to see if students get inspired by this idea. Of course, those who want to make conventional, non-solo games would be welcome to, but only if they can articulate a playtesting plan for them. (I'll be writing up the final project specifications later, maybe before the semester starts, but certainly before the withdraw deadline.)

I started my course plan by cloning an existing one, but knowing that we would be entirely online prompted me to revisit large portions of the course overview. I added several paragraphs that explain my motivation and provide more direct guidance. For example, the introduction section says a lot more about what we will be doing in the class and about what kind of commitment students are signing up for, the decorum section has been changed to focus on online interactions, and the grading section has been rewritten to expose more of the rationale and philosophy that I would otherwise introduce in person.

I am still a bit uncertain how I am going to manage office hours and communication across my classes, but for this one at least, I decided that we should have our own Discord server. If nothing else, this will provide a convenient place for voice chat when students set up an online game together. I will need to do a little legwork before the semester starts regarding server administration, since I want to make sure the space is as safe and inviting as can be expected. Similarly, I am sure I will need to provide some documentation for folks who are unfamiliar with this system.

I will continue to update the course site as we move on, but since I got a good draft together, I figured it was time for a quick blog post. Next on my to-do list is to revisit the other two courses to remove synchronous aspects and patch back in some of the extended prose and advice I've set up for the game design course.

Friday, July 24, 2020

Something like a Summer Devlog #5: Fluttering back to Thunderstone Quest

TL;DR: New Thunderstone Quest Randomizer with combo support. Check it out at https://doctor-g.github.io/ThunderstoneQuestRandomizer.

Background and Inspiration

By all accounts, the plan for this week was to work on a plan for transitioning the third of my three Fall classes to its online mode. I had done some of the prep work required and started conversations with colleagues about it. Then I received my New Horizons expansion for Thunderstone Quest. Normally, this wouldn't derail a whole week, but I was excited to add the new cards to my Polymer-powered randomizer app, TQR, which is online here and which I wrote about here. I was pleasantly surprised how easy it was to add the new cards: just dropping them into the JSON configuration file let the rest of the engine find them and incorporate them into the user experience. I rarely maintain my old code, but it was a good feeling to have something I wrote in May 2019 hold up.

...well, except for one detail. The expansion added a new kind of Market card: Allies. The expansion rulebook suggests adding the ally randomizer cards to all the rest of the market randomizers, and then adding the Ally cards to the "Any" spaces in the market when they come up. This struck me as curious when I read it originally, since I never thought to mix all my market randomizers together: I keep them separated by type (Item, Weapon, Spell). Indeed, this kind of separation is encouraged by the physical dividers provided in the base set. More relevant to this story, the TSQ implementation assumes that you figure out the distribution of Item, Weapon, and Spell first, then fill the market. That is, since there were always three of two types and two of the other, it determined that first, and then drew from the corresponding decks. There was no way for me to add Allies without gutting this part of the randomization, which was also tightly coupled to the whole user experience.

I began to imagine what it would be like to reimplement the randomizer in Flutter. I taught myself enough Flutter this summer to be able to include it in my HCI course plans, and I developed a sample solution to a two-week project for that course. It was, of course, a simple project, and I had in the back of my head that I should do something more significant. Why not a new Thunderstone Quest randomizer? This would give me the opportunity to get a better grip on Flutter, plus I could add one of the key features that didn't make it into the original implementation: combos. I have seen various Thunderstone communities reminisce about a particular randomizer app from Thunderstone Quest's predecessor, Thunderstone Advance, for its support of card combos. My mind idly went through a couple of potential implementations for combos, and I convinced myself (without building any technical demos or writing the actual requirements) that I could get a version of this working. That was enough for me: Monday morning, I was off to the races and working on a new Flutter-powered randomizer.

A point of clarification: My old randomizer is officially named "Thunderstone Quest Randomizer" and abbreviated TSQ. After some failed attempts to come up with a catchier name for my new implementation, I also named it "Thunderstone Quest Randomizer." When it's not clear which one I might mean from context, I will refer to the old one by the abbreviation and the new one by the full name. At least, I will do this until someone comes up with a better name for it, or until I get really excited about "Thunderpants" as a memorable name. Lion-O would dig it.

Data Wrangling

One of the first things I did was transform my TSQ JSON data file into YAML. It wasn't too bad to add the new data to JSON, but there is a lot of cruft. I happened to be recently back into using YAML, which I traditionally like better than JSON, but the prevalence of JSON has me using it more often. More recently, I've been tinkering with GitHub workflows, which are specified with YAML, and it reminded me how much I prefer it for structured data. I used an online translator that was fairly painless and found a convenient API for parsing YAML in Dart (the programming language of Flutter). Adding new libraries to a Flutter project is as simple as it should be in a modern programming environment: drop a line into the awkwardly named pubspec file, which is itself YAML, and you're off to the races.

I wrote most of the parser using TDD, and I found the testing environment to be similar to ones I've used for Javascript: easy grouping and idiomatic matchers that benefit from higher-order functions. I decided from the get-go that I would not make a fundamental architectural mistake that plagued TSQ, which was the very Webby assumption that the JSON data should be used directly by the view. Instead, I added a semantic model layer in between, so that in the new randomizer, the YAML file is parsed into a list of Quest objects, each of which has Heroes, Monsters, Items, and so on. I didn't see a way to automate the population of the domain model as I would do with a library like GSON, but it wasn't much code to grab the data I needed from the YAML parser. Subscript notation for random map access also meant this was very short to write, compared to Java, for example.

This was also a place where I learned to be a bit more idiomatic with Dart. I originally had repeated code of this form:

if (node['Hero'] != null) {
  for (var entry in node['Hero']) {
    // Parse hero

This sort of defensive programming was required because attempting to access a key that doesn't exist, in the YAML library implementation, produces an exception, not null as I might expect. After reading some documentation on best practices for Dart, I came across the curious "??" operator, which I have not seen in any language before. It allowed me to change the code above into something like this:

for (var entry in node['Hero'] ?? List()) {
  // Parse hero

This means that if node['Hero'] is null, then use an empty list as the value instead, which of course terminates the iteration right away. It's much more terse and expressive: less imperative cruft. I will mention, though, that throughout the implementation, I was nagged by a feeling like there were better, idiomatic ways to do things, and that if I had a code review by a Flutter regular, they could point out where I might grow next. I am sure I can help my students in the Fall go from Novice to Advanced Beginners, but the gap between Advanced Beginner and Competent is foggy.

Let me dig into that just a little bit more to show what I mean. Here's an excerpt from the 1.0.0 release:

for (var entry in node['Heroes'] ?? empty) {
  Hero hero = new Hero();
  _parseCard(entry, hero);

for (var entry in node['Items'] ?? empty) {
  Item item = new Item();
  _parseCard(entry, item);

There are eight blocks basically like this for the eight types of cards. The guts of the parsing is the same for each card, so it was easy to extract a function that. However, the actual runtime type of each card is different, and they get added to different lists within the Quest. It feels like a terrible repetition to have almost the same code eight times, but it's not obvious to me how I could extract this to have fewer lines of code, especially knowing that Flutter does not allow for runtime reflection. I could put the parsing logic into the domain model classes, but that pollutes the model with data concerns, and making separate per-class parsers for each would add more code than it removes. If this looks like a fun refactoring challenge to you, I welcome you to open a pull request!


The combo system that I implemented operates on a matching of two kinds of data. Each of the keywords on the cards (like "Humanoid" or "Edged") are encoded in the YAML file and stored in the domain model. Additionally, each card that references these data on other cards has encoded a combo list. For example, Stormhand has a bonus for Edged weapons, so his combo list includes the term "Edged". When choosing a card, then, the combo can arise in two ways: either the current card has a keyword that is in the combo list of a card on the tableau, or the current tableau has a keyword that is in the combo list of the current card. I introduced a combo bias to the application, which is a probability that a card would be filtered out for not being a combo.

It turns out that this is not entirely sufficient, since not all powers trigger off of keywords. Some trigger off of other properties, such as the presence of diseased wounds or certain words in the card title. To handle this, I added a "meta" category to the card database. These are folded into the keywords when searching for combos. For example, Blizzard gets more powerful the more frozen wounds you have, so this card gets "Frozen" in its combo list, while cards that give frozen wounds, like the Tundra Wolf Pack, have "Frozen" in their meta.

In human language, it works a bit like this. Choose a random card to potentially add to the tableau. Roll the dice to determine if we're looking for a combo. If so, check if there is a combo, and if so, keep the card; if we're not looking for a combo, just keep the random card. Then, see if the card can actually fit in the tableau (e.g. the Marketplace is not full).

This is why I called it a combo bias and not a combo chance, because finding a combo does not mean that the card will be used: it's more of a probability of applying a high-pass filter. The app currently allows the user to set the combo bias between 0% and 95%. In casual testing, I've never had a case fail to match or take an observable amount of time except in cases where I specifically chose quests that have no combos, such as using just the Bandits of Black Rock quest, which does not have enough cards to fill a tableau.

There are parts of the implementation that are woefully unoptimized, such as the on-demand querying of which keywords are on the tableau. I expected this to run much more poorly than it did, but the truth is that because the N is so low, and computers are so fast, it made no observable different to performance whether I cached the data or created and merged new Set objects each time.

The sequence of card selection is important for combos, of course, and I tinkered with this a bit throughout development. As of this writing, the sequence is Heroes, Guardian, Dungeon, Marketplace, and Monsters. We did not encode any combo data for the Dungeon tiles, since they generally key off of things that are always in play, like the four classes, light, and gear tokens. This means that the hero selection is arbitrary, subject to the selected strategy, but anything else except Dungeon Rooms down the chain can be part of a combo. I think this is probably a good configuration, but I'd be happy to hear other perspectives on this.

State Management

Naturally, I had some frustration with Flutter as I tried to wrap my head around it. The little sample project I mentioned before existed on one screen with a single stateful widget, but for this project, I knew I had to handle state more carefully. The documentation gives a few pointers and then says, essentially, do whatever you want. It's nice to have that freedom if you have time and mentoring. I decided upon the approach recommended for simple applications, which was to use a ChangeNotifier to propagate changes to the user settings through the rest of the application. Near the end of development yesterday, I got to wondering if I had correctly discerned the difference between using the Consumer and Producer objects, and I may yet go revisit that. In the meantime, though, the basic architecture of the application is that the randomizer screen is its own stateful widget which is the generated tableau, and the options in the Settings page are shared via a ChangeNotifier.

The SettingsModel and its corresponding screen are another area where I see a lot of repeated code, but it's not obvious to me how to use the tools of the language to factor out the duplication. I can handle cases like showing one checkbox per quest to have it selected or not, but it's less clear to me how to handle the suite of checkboxes around the application appearance.

Implementing different hero selection strategies was done with the Strategy design pattern, of course. I was happy to be able to implement this pattern in a very direct way, and it worked as expected. It did result in a strange separation of concerns, though, where the thing called "Randomizer" delegates to a strategy that is, for convenience, defined alongside the SettingsModel. This is another area of code that is a bit gross, could benefit from refactoring, but also works pretty well if you just leave it alone.

Flutter is famous for its support of contemporary user experience design, including ample animations. I knew I wanted to add something along these lines, although going into the app I didn't have a concrete notion of what it would be. As I got into the implementation, I decided to keep it simple: use fade transitions to show when a new tableau is generated. Note that the animation between the main randomizer page and the settings page was essentially "free" since it is built into the default app routing behavior.


It took me many, many efforts to get the animation working properly. The documentation made it seem like FadeTransition should be easy to put into play, but I tried that, several other things, and then came back to that. After hours of hammering at it, I finally had a sense of how the animation worked, and this revealed to me that I had deeper problems with how I was conceiving of the animation. When I stopped to consider what was scratching at the back of my head, I realized that I had to separate and synchronize the notions of fade out, generate tableau, and fade in. It seemed to me that I should be able to do this with a chain of futures, but the animation framework seems to only provide listeners, not futures. The result is a bit of confused code in the main randomize screen: the logic for the sequence of events is not laid out sequentially in code, but spread across a few different functions to handle different cases. I don't like this, and it's not clear to me if it's a property of the framework or a limitation to my understanding.

Another piece that caused me minor trouble, but is worth mentioning, is the asymmetry in how animation events are reported. When you play an animation forward (fade in), it ends with a "completed" event. That makes sense, so I assumed then I could listen for the same thing when playing an animation backward (fade out). It doesn't, however: when an animation is finished playing backward, it fires a "dismissed" event. Dismissed? I had seen this in the documentation assumed it meant, you know, that the animation was dismissed. I would never say that a reversed animation was dismissed, and this nomenclature surely doesn't show up in any of the game engine's I have used, where reversing animations is commonplace. Heck, I can't even find in the Flutter documentation now the definition that this is what they mean by "dismissed." It's a really odd choice to me.

In the end, though, I got the fade animation working. Originally, I had some idle thoughts about card selections flying in from the side, but really, I think the fade transition fills the bill.

Tool Notes

Let me say for the record that flutter doctor is amazing. Indeed, the whole flutter command-line tool is a master class in how to conveniently manage a development environment. It was frustrating, then, that there were a few cases where Visual Studio Code did not behave as I liked. I regularly had to kill and restart debugging sessions. This would not be such a frustration except that I had to do all my development in Windows because of a problem with hot-reload on Linux. I was using Windows because Linux wasn't providing me with hot-reload, but the hot-reload frequently failed on Windows, so what then? Also, I had bad behavior from the debugging sessions when changing my assets, but it wasn't clear if that's an expected behavior or not.

The biggest frustration about Visual Studio Code, though, was that for the life of me I could not figure out how to tell it to run all my unit tests. I could easily choose a suite of tests to run but not the whole directory of tests. Dropping into the shell, flutter test did the trick, and I ended up regularly just doing that and, when failures were reported, going back into the IDE to run the suite with the failure so I could get the more readable error report.

It took me more than halfway through development to realize the power of the Ctrl-. shortcut, but once I found it, it had a dramatic improvement on my development speed. This shortcut brought up a menu of options that I hadn't even thought to hope for, the real beauties being "Wrap with Widget" and "Remove Widget". Flutter code can end up with lots of nested widgets, which yes, really ought to be extracted into more manageable chunks. Be that as it may, manually wrapping or removing widgets is a painful process of braces-matching. These shortcuts were brilliant time-savers.

Also, the GitHub workflow I wrote about the other day to automatically publish on GitHub pages worked like a charm.

Final thoughts

I have dubbed this release 1.0.0 and posted about it on BoardGameGeek, where it's already generated a few thumbs-up and positive comments. I will be adding a link to this blog post there too, for anyone who wants to read the long form commentary. I would be remiss not to include my gratitude to my eldest son, who helped with encoding the combo data and keywords into YAML. He did a great job, learning the notation and the ideas quickly.

Awkwardly, my own set of Thunderstone Quest is not currently in a state to use a randomized tableau! Before receiving the expansion, we have been playing the Epic variant, so I have several cards pulled from their stacks for this purpose. Maybe after we play the authored quests that came in the expansion, I can excite my kids to do a bit of sorting so that we can play some games with different combo biases.

Right now, the fact that a given card was part of a combo is not exposed in the user interface. You can actually see them logged if you open up the console: for example, on Chrome, press F12 to open up the developer tools, and you can see combo card selection reported there. I've thought about making this an option in the UI, but this goes into the "that's enough for this week" category. Maybe I should open an Issue about it on GitHub so I don't forget.

I have not taken a careful look at internationalization nor accessibility of the new randomizer, but these are significant topics that I would like to investigate. I want to include at least some coverage of both in the Fall HCI class, and one of the reasons for making a bigger Flutter app was to motivate the desire for such things. I don't think I will do it right away, though. I worked on Thunderstone Quest Randomizer more-than-fulltime since Monday, and today I need to step back from it and do some family painting.

Thanks for reading. I hope you enjoyed the commentary and that you find some utility in the Thunderstone Quest Randomizer. It's free software, so please feel free to use it, share it, study it, and modify it to your heart's content.

Tuesday, July 21, 2020

Using emacs as a commit editor from GitBash

For some reason, perhaps related to tinkering with Flutter, my commit editor settings went wonky on Windows. Trying to rebase from Git Bash was connecting to Visual Studio Code rather than Emacs. Using Emacs is an important part of my workflow because of the ease with which rectangles of text can be replaced, such as when choosing to squash sequences of commits. (That's M-x replace-rectangle for the curious.)
It took much too long for me to get the proper setting back in place to run emacs as my commit editor, so I'm tracking it here, in case I need it later. Here's the command:

git config --global core.editor "c:/program\ files/emacs/bin/emacsclient.exe -c -a ''"

Yes, all that punctuation at the end is significant.

UPDATE: It turns out this isn't quite right. It works once, but then on subsequent rebases, emacs opens with an empty buffer and a message that the file on disk has changed. I didn't have to do this before, so whatever configuration I had before that worked, this wasn't it.

Monday, July 13, 2020

Answering questions about effective online learning

I was asked, through the grapevine, to provide some insights into online teaching and learning. The answers are being compiled into an article directed at parents of first-time college students. In the spirit of no wasted writing, I decided to write up my answers here.

Question 1: From your experience during the Spring semester, what approaches to online learning taken by students seemed to be most successful for them?

I want to preface my answer my saying that both my Spring courses were team-oriented, project-intensive courses. We had a great advantage in that the teams were formed before the pandemic forced campus to close. I am planning to do less team work than I normally would since it is so hard to mentor distributed teams as they learn fundamental technical and social processes of collaboration.

A common feature of my most successful student teams was that they maintained a regular schedule. They all had this before the pandemic, and many of them carried that schedule forward as classes moved to purely online. They established regular meeting times, generally between three and six hours, during which times all team members were expected to be online and working on the project. Some used Zoom audio or video meetings during the whole time, while others used Discord or Slack, switching to Zoom audio or video calling when text was inadequate. Because the whole team was online at the same time, teams were able to clear up communication quickly, before issues festered and became social and technical problems.

The most successful teams combined synchronous distributed meetings with asynchronous, persistent chatrooms—either Slack or Discord. This gave a place where they could pose questions, provide links, encourage each other, and otherwise socialize outside of their scheduled meeting times. In the cases where I was invited to join and participate in these discussions, I definitely saw the best students doing all four of those things. The impact of peer-encouragement on these forums is not to be underestimated. When students took the time to give each other a little virtual pat on the back, it clearly and directly contributed to a higher morale, better learning, and increased productivity.

Students and teams who reached out to me for help had much better results than those who didn't. This should come as no surprise, as it's the same for in-person classes, but it also should not be discounted. I could answer easy questions over email, and anything more involved could be done through a video chat—ideally with the whole team. In a normal semester, I would set up face-to-face meetings for this purpose, but video chat actually proved much easier, and I plan to keep this tool on my toolbelt moving forward, regardless of instructional mode.

Question 2: Conversely, were there certain approaches to online learning on the part of  students that caused them difficulty or led to unsuccessful outcomes?

After campus shut down, a few of my student teams decided to take this as an opportunity to relax their schedules. Rather than meet together to work on the project at specific times, they divvied out responsibilities and asked people to work whenever they felt like it. This led to the unfortunate but seemingly-inevitable consequences of miscommunication and confusion, which then resulted in mistrust and poor performance. Briefly, every team that relaxed their schedule of meetings regretted it.

Incidentally, if you ask any undergraduate what the worst part of college is, the answer is "group projects." There are many reasons for this, and they include that we don't do well, institutionally, at teaching students how to work in groups. I had an upper-division class in Fall 2019 in which we had a powerful heart-to-heart conversation in which the room of students admitted they had never been on anything than a dysfunctional team in any school experience. That is, working on a team is not just intrinsically hard: the students also have no tacit or mental model for what positive collaboration looks like.

Coming back to the question, another problem I saw was students' using familiar tools rather than effective but novel tools. Discord vs. Zoom is a great example. Many students use Discord for online gaming, and since they are familiar with the tool, it becomes a natural choice for them when deciding on how to work together as a team. Discord even recently added screen-sharing, so that you can see what a teammate is working on. However, this is like streaming on Twitch: it's a one-way stream, in which one person is working and others are watching. Contrast this against Zoom's Screen Sharing feature, in which anyone on the team can actually drive the host's computer. This leads to real collaboration rather than simply passive or voyeuristic participation. In almost every online team meeting in the Spring, a case would come up where someone in the conversation (often me!) would take control of the screen and type in a few characters to explain a concept, or steer the mouse to demonstrate an interaction. This gives a similar affordance to sitting together in the lab or in office hours, temporarily "taking the wheel" to demonstrate something, as compared to the passive-consumptive stream approach, which as no affordance for collaborative work.

An important point about this is that some of my teams continued to use Discord even after I pointed out the problem or, in one case, mandated the use of something else. The team's inertia and pride kept them from learning about a better solution to their problem. I suppose what we see here is that the root cause is not the tool but the pride, but that seems to be always how it is.

Question 3: What are some tips you can offer students about how they can make the most of online learning and have the most successful outcomes?

Students are in many ways at the mercy of their instructors. The frustrating truth is that it can be hard to get faculty to make the most of learning toward the goal of successful outcomes. Because of this, students need to be careful consumers. To the extent possible, see what you can find out about the courses you want to take. See if you can infer from the designs that this is going to be good learning experience, based on the objectives of the course and the interactions designed for them. Personally, I keep all my course plans public and online, but I suspect I'm in the small minority there.

Advice that is easy to give and hard to swallow is be conscientious. Conscientiousness is highly correlated with academic success, and no matter how conscientious you are, you can always take action to improve. I think the most accessible way to do this is to set up a schedule for a week, see how well you can follow it, and then actively reflect on the differences between what you had hoped to do and what you did do. Nobody is going to make you do this reflection, but that is where the real learning happens.

It's also important to keep in mind a few core principles of effective learning, such as I wrote about in my notes about the excellent Make It Stick. These principles are not about success in college so much as how to become an effective lifetime learner. Ideally, these things would align, but in practice, they don't always do so. I'll repeat my quotation here from Chapter 8 of that book, which provides a great overview of the mindset of effective learning.

  • Some kinds of difficulties during learning help to make the learning stronger and better remembered.
  • When learning is easy, it is often superficial and soon forgotten.
  • Note all of our intellectual abilities are hardwired. In fact, when learning is effortful, it changes the brain, making new connections and increasing intellectual ability.
  • You learn better when you wrestle with new problems before being shown the solution, rather than the other way around.
  • To achieve excellence in any sphere, you must strive to surpass your current level of ability.
  • Striving, by its nature, often results in setbacks, and setbacks are often what provide essential information needed to adjust strategies to achieve mastery.
Finally, my advice to all learners is practice intellectual humility. Listen to other people as if they know something you don't know, because they do. 

Sunday, July 12, 2020

An afternoon with Fate

The Fate RPG caught my attention at the start of the pandemic when the publisher, Evil Hat, offered some sourcebooks for free. I can't even remember how I heard about that, to be honest, but it piqued my interest and I started reading some more about it. The different versions of the RPG confused me for a bit. I started by reading some Fate Core, then I found Fate Accelerated (FAE), which is a briefer version that, it turns out, is still in keeping with Core. I read through some of FAE, then learned about Fate Condensed, and this one seemed like it might be the right size for me to try out. However, even when reading Condensed, I found that I had to go back to Core to try to wrap my head around some essential ideas such as invoking aspects.

I gave my family a heads-up during an evening walk that I wanted to try running a one-shot of a fiction-first RPG with whomever was interested. I explained that it could be any setting at all, and I encouraged them to think of one that might be interesting. This afternoon, I invited any who were interested to actually sit down, make a world, create some characters, and try a session. My 13-year-old, 10-year-old, and 5-year-old all expressed interest, so we set down to it.

We batted around a few ideas for the world. The first suggestion was a sword-and-sorcery world, but I said I'd rather not use that since it's too familiar. (Also, I have secret plans to try Unlimited Dungeon with them.) Other ideas that were mentioned included steampunk, Star Wars, and age of exploration. I suggested we could combine the steampunk and exploration idea and make a world of floating cities and airships. We were all pretty excited about this, so we set into making characters. The eldest wanted to make a tinkerer, and he got started making Fredrick Sparksmith the Expert Mechanic without trouble. The second son had some trouble narrowing down what he wanted: some kind of fighter, thief, sneaky character. After some back and forth, he settled on the concept of "sneaky deserter." To establish his premise, we decided that the floating cities were all independent, and there was an ongoing war between the Kingdom of Terrinoth (the city's name taken from Runebound's world, of course) and the Republic of Loramor. His character, Danny Lorens, had been a soldier for Terrinoth who deserted after his team were nearly all wiped out. The team lacked a captain, so I asked the youngest if he would want to play the captain of an airship. We worked up Apple Cider (which was also the name of his Exspelled character), the Adventurous Captain. 

During this discussion, we returned to the world. I remembered having read in Fate Core that it's good to start with two setting aspects, potentially one current issue and one impending issue. The war between Terrinoth and Loramor was an obvious current issue, so I suggested we try to come up with an impending issue. At first, the boys focused on side-effects of the war, such as resource shortages and rebellions, but I suggested that these were sort of implied by the war and maybe we should try something separate. I was thinking a bit about Dungeon World fronts here. (I wrote about my first game of Dungeon World a few weeks ago.) I suggested something like a looming disaster, like an oncoming storm. The conversation turned to whether or not there really was a ground beneath the floating cities or not, and we decided there wasn't. One of the boys then suggested that there could be some kind of evil coming from the depths themselves, and we came up with the idea of the Shadow Creatures: beings that were increasingly coming up from the depths and attacking airships, and now everyone was afraid to fly or even go out at night.

After a brief recess, we got back together. I asked Apple Cider what kind of treasure he might be seeking, to which the answer was donuts. On one hand, that's a good answer, but on the other hand, I think we can see that there's a disconnect between the narrative desires of 5-year-olds and 10–13-year-olds. The little guy was awfully bored during character creation and actually had asked to leave. I worked out his skills for him and then invited him back to the table for the storytelling part. Even here, though, he did not last long. He changed his mind from donut to trophy, and so I set the initial scene as the group flying to a "scatter" of islands (our word for the equivalent of an airborne archipelago) that were currently being fought over by Terrinoth and Loramor. One of the islands held the treasure, or at least a key to it. However, the ship was stopped by a scouting party of Loramor soldiers, and were asked to identify themselves.

We had some fun with this encounter, and I'm of two minds about it now. My intention was to use it to explain the rules of play, since we really hadn't talked about them yet. I had explained only that aspects and skills were useful, and we had just skipped entirely over Stunts, figuring I could help guide them in those definitions as we played. I think the scenario was useful here, but it was also hardly in media res. I wonder if I should have done something more adventurous. 

They got to the floating island in the scatter and found an ancient vault when the scouts they saw previously got into a firefight with some from Terrinoth. As the heroes opened the vault, the battle escalated, and this put them into a spot: should they try to sail away and risk being seen as enemy combatants, or should they stay and risk being on the island at night? They chose the latter. 

Naturally, this is where I had some shadow creatures prowl around the ship. By this point, the five-year-old really had had enough and left, but I had invited by 7-year-old to join us and play Apple Cider. He was happy to do so, and he loved the game. I knew he would, given that he loves storytelling and he reads effortlessly. I'm sure all the text on the character sheets and index cards was frustrating at best for the littlest guy. In any case, Apple Cider had the aspect, "Takes on more than he can handle," so I compelled him to want to take a shot and see if he could capture one of these mysterious creatures. He loved that, and we started our only Conflict of the game. It was highly cinematic, and the boys loved being able to describe their actions, even if they found it challenging to improvise a compelling narrative. Highlights include: a shadow creature leaping onto a lamp and turning it from a "light" (that spreads light) into a "dark" (that spreads dark); Fredrick running in to turn off the "dark", but accepting a compel for his Scatterbrained aspect that made him forget the switches and just wildly turn things on and off; Danny getting a Success with Style to throw one of his daggers through a shadow creature and right into the "dark", shattering it.

This is where we wrapped it up, not exactly a cliffhanger but with more questions than answers. Thinking back, I clearly had "campaign" mindset rather than "one-shot" mindset. All the boys said they would happily play again, the older two especially feeling like we had made a really exciting and interesting world. 

Even with my one-page cheatsheet from the Condensed rules, I still found it hard to think about the conflict resolution rules. In particular, it wasn't always clear to me when I should simply set a difficulty for an Overcome vs. create a Challenge, Contest, or Conflict. I intend to re-read the corresponding portions of the rulebooks. After having taken a quick look at the FAE character sheets, I'm wondering if the reduced number of skills would actually make that a better fit for the table. Balsera-Style initiative, which I think everyone at the table enjoyed, could easily be imported from Condensed to FAE

Another problem I ran into is who makes choices in the face of failure. The one-page sheet leaves it ambiguous who makes the decision between Failure or Success at a Major Cost; the full rules explanation, however, makes it clear that it's a discussion with the GM and, potentially, the defending player. I realize too in cursory re-reading of a few pages of the rulebook that our conflict looked maybe too much like D&D combat rather than fiction-first. I need to remember to turn the narration over to the player rather than feeling like I have to take that all myself. It's not that I didn't do this right at all, but it's a matter of degrees, and the fiction-first approach is the direction I want to lean toward.

One area I can definitely improve is giving sensory descriptions of the scene and using these to define aspects. I really did not do this enough, focusing more on ensuring I had the rules right in my head than providing a compelling scenario description. 

Another problem I should have seen coming is that I could not read the other players' character sheets during the game. I really need to have my own copy of their aspects so that I can more readily scan them for opportunities to compel.

In summary, we had a great time, and we all enjoyed the world that we created. Part of me would like to go back there, but part of me would also like to try something completely different, maybe with FAE, to see what the boys think. The two younger ones definitely were excited by the combat, but it was really fun to see how the older one recognized he didn't have to stand and shoot: he could instead modify the environment.

Thursday, July 9, 2020

Deploying Godot Engine Web Games to GitHub Pages with Continuous Integration

After figuring out how to use continuous integration with Flutter Web apps on GitHub Pages, I turned my attention to Godot Engine. Through my FamJam projects, I have developed a pair of scripts (build.sh and deploy.sh
) for building and deploying games to GitHub Pages. It seemed like a natural extension to see if I could do this automatically. This could be useful both for my Fam Jams and, of course, for my revised game programming course.

In addition to GitHub actions, the other two critical pieces to getting this to work are aBARICHELLO's Godot CI Docker image and, as used in my Flutter approach, peaceiris' GitHub Pages Action. Here is the simple version:

My custom build script usse NatrimCZ's approach of reducing project download size by zipping the project files and extracting them client-side, and it was surprisingly straightforward to incorporate that into an alternative GitHub workflow:

Either approach, just like with Flutter, requires you to have an authoritative push to gh-pages before using GITHUB_TOKEN for authentication will work; once again, you can do something like this to get that done:

git push origin master:gh-pages
Also, as mentioned in the comments, the script assumes that you have already configured the project with HTML5 export and that you have set the export location to build/web.