The Intellectual Wilderness There is nothing more useless than doing efficiently that which should not be done at all.

2020.12.19 17:16

Erlang: Writing a Tetris clone Part 2 – Gameplay mechanics

Last night I was able to make the second video in my series about implementing a Tetris clone in Erlang. Yay!

In this video I start where I left off in the first video where I had ended with a data abstraction to represent the play field (called the “well” in Tetris lingo), a data abstraction for the game pieces, some colored sprites to draw the game board with, and a GUI that could draw a game board and a single random piece every time it was opened as well as print to stdout any key press events that I made.

Oh but by the way…

I have a small confession to make — I didn’t actually start where I left off. I mentioned the stopping point (the “Draw the board with a random piece on it” commit), then I mentioned the next thing I did which was implement basic movement (the “Basic (unsafe) movement” commit), then I completely blew past it and never explained the way I implemented unsafe movement in the first place. The trouble with having skipped that is that I had intended to discuss how keystroke capture actually works, where it is in the GUI code, and follow the event through the system so that people could get that idea into their head earlier than later because it is so basic to making a game!

So instead I’ll explain how that works and point out where it is in the code here in this post and cover it at the beginning of the third video.

Getting Input

If we look at the “Basic (unsafe) movement” commit there is a file called ertltris/src/et_gui.erl that is the code for the GUI process. In the init/1 function we see the wx server get started, a “frame” is created (the main window in wx parlance), some various widget elements and things are all established and on line 112 we see this:

ok = wxPanel:connect(Frame, char_hook),

This is connecting the Frame to a window manager event called char_hook. I do mention this in the video, but it is important to point this out here. I should also point out that I’m mistakenly calling wxPanel:connect/2 instead of wxFrame:connect/2 — which is technically incorrect, but due to the nature of the underlying inheritance among the C++ classes that make up wx and the way that’s all masked in the generated library wrappers that make up wxErlang, it actually doesn’t cause any errors. wxPanel and wxFrame are all ancestors of wxEventHandler.

Anyway… what connecting to this event does is tells wx that whenever the frame is the focus it should relay keystroke events to the program. Inside Erlang they arrive as messages that carry a #wx{} record that includes an event element that carries another record that provides all the relevant information about the event. You can receive these in the handle_event/2 callback function of wx_object. Pretty nice. This means you can deal with GUI events in very much the same way you can handle network events as well as inter-process messages within the Erlang runtime: everything is a message.

IDIOMS IDIOMS IDIOMS!

As I often say: Develop idioms.

A common idiom I use in wxErlang code is to match on an event type in handle_event/2, assign any relevant event data to variables, and then within a case inside the handle_event clause that matched that event type determine where we want to dispatch the event (if at all). You can see a very clear example of this on lines 191-203 of this commit.

handle_event(#wx{event = #wxKey{keyCode = Code}}, State = #s{frame = Frame}) ->
    ok =
        case Code of
             32 -> et_con:random_piece();
             88 -> et_con:rotate(l);
             90 -> et_con:rotate(r);
            314 -> et_con:move(l);
            315 -> et_con:rotate(r);
            316 -> et_con:move(r);
            317 -> et_con:move(d);
            _  -> tell("KeyCode: ~p", [Code])
        end,
    {noreply, State};

I could have made each one of those dispatch decisions inside the function head instead of using a case statement within a clause, but I find it much easier to read this particular style where we open a very compact dispatch span per event type we’re looking for than have a ton of handle_event clauses. In complex GUI applications you can wind up with a lot of special keystroke events and it becomes super cumbersome to match them all as function heads. Not just writing the function heads, but trying to find a specific event gets kind of messy because the code starts looking so scrambled.

Each of the different key codes matched in the dispatching part of that function correspond to some gameplay event. Note that the GUI doesn’t really care what the status of the game is at all, it just cares that it is relaying the mapped commands to the game control process and then carries on doing GUI stuff.

This is important to point out: Nearly all communication between the controller and the GUI is asynchronous. You almost never want blocking calls to pass between them. There may be a need for a blocking call in some special code, but this is almost always a bad idea.

Buh bye!

That pretty much sums up what I wanted to cover that I forgot to mention in the video. Input handling is really important. If you want to jump ahead, check out the way the handle_event/2 function evolved in the latest commits to see how menu commands are intercepted, or go check out the handle_event/2 function in the Erlang Minesweeper clone!

Don’t forget to give me all the delicious likes and stars and channel subs! BWAHAHA! I’ll catch you magnificent nerds in the next video!

No Comments »

No comments yet.

RSS feed for comments on this post.

Leave a comment

Powered by WordPress