Back to home

The Tutorial

2025/10/28

The tutorial was actually the reason I decided to start working on this project in the first place. I saw another tutorial which annoyed me so much, I simply had to make my own. To make my own tutorial, I had to make my own Mahjong game, and here we are.

Concept

To be clear, I had already played around with the concept of this game for a while, but the tutorial was definitely what gave me the push to begin development.

I ranked the most important things to know about Riichi Mahjong, and used that as the order for the tutorial.

The tutorial menu in game, showing the order of items

Introduction shows you the tiles, what groups they belong to, and reminds you that you can always use the info button to find out the name if you forgot. I'm not a fan of translated tiles, so teaching people how to learn to read the tiles is important.

The actual idea for an info button is probably derived from Factorio's Alt-mode, where you can opt to see extra information at the cost of visual beauty.

After that, we learn about how to play the game in the strictest sense: By progressing our hand. Then we follow that up by learning about progressing our hand using calls.

As the final part of the basics, I try to teach the player what a Yaku is, and a few basic Yaku to keep in mind. Yaku are probably the hardest thing for a beginner to learn, so instead of pointing to a big fat list, I narrowed it down to a few easy ones you can aim for, where any Yaku earned beyond that are just a points bonus. I did this because having at least 1 Yaku is required for a complete hand. If you can't teach someone how to get one Yaku, you haven't taught them how to play.

I narrowed it down to: Menzen tsumo, Yakuhai, Tanyao, Toitoi, Honitsu and Pinfu. Pinfu might be a controversial choice, given that it's a fairly complicated Yaku, but I think it fills a good gap of beginner Yaku where in theory any hand could fit it. The actual restrictions of Pinfu is listed as a set of easy to grasp rules.

I had to make a sizeable tutorial to describe Riichi, seeing that it's such an important part of Riichi Mahjong. It also lets you naturally flow into teaching about Furiten, and reading other player's discards. While only lightly touched upon, I think it sets a good basis for players to think about in the future.

The last tutorial touches on everything you need to know about the dead wall. After Kans were mentioned already in tutorial #3 about calls, it takes a while to actually teach someone about them.

Dora are another important part of Riichi Mahjong, so their meaning is taught as well. The concept of what the "next tile" is can be somewhat confusing, so I implemented a feature where the info window shows you what tile is actually a Dora when you hold Info on its indicator. This becomes useful in the main game as well, so that's a plus.

Excerpt of the dead wall tutorial, showing the Dora indicator with info

Development

As for how the tutorials were actually developed, I considered my options when I started work. s&box has a movie maker, which would be tempting to use, but when I conferred with the developer responsible, I decided not to use it because it did not support running code at certain points. I could have waited for that feature to be ready, but I didn't want to delay the tutorial any further. Instead, I decided re-use my ideas that I had worked for my recent work on automated tests.

I was experimenting with ways to dynamically render text using Markdown or something similar as a base, but that all fell through. Instead I decided to use a separate Razor panel for each box of text which pops up in the tutorial.

Each tutorial is its own scene, which has all the tiles and stuff I need strewn about the workspace, ready to be pulled in.

A screenshot of a tutorial scene in the editor, showing tiles out of bounds of the camera.

The brains of the scene lies in a single component. This is where the ideas I'd developed earlier for the testing came in. Each tutorial component is derived from a TutorialEpisode Component, an abstract class I made to allow re-use. The individual components only define Run and Cleanup functions, where Run is an async Task, so it can wait for user input.

The TutorialEpisode component is a partial class, so I can define certain "instructions" which I can use in each Run function. As a result, the code in the Run function ends up looking like this:

  await DrawTile( Draw1 );
  await DisplayPanel<Tutorial002P003>();
  var d1t = await DiscardTile( Discard1 );
  await PutInHand( Draw1, d1t );

This is a structure I settled on after I decided that doing things purely in the inspector would not work for me.

I don't have much else to say about the tutorial. It was quite annoying to get right, but I'm not sure what I could have done better given the tools I have. The only thing which comes to mind is that I should get rid of the Cleanup function and use IDisposable instead.

Notably, the tutorial does not cover how to play Quality Riichi Mahjong. Those instructions are currently in a window which is shown to first time players when they join a game.

I've considered adding another tutorial where players can learn how to draw tiles properly, but the amount of work to get that right is out of my reach for now. I think I would need a real practice mode against AI before I could do that.