27 October 2012

Game modes and MVC, part 2/2

This article follows a high-level architecture overview of two pygame projects. In this article, I show why it is difficult to engineer a game in modes when following MVC, and suggest two ways of solving the problem, detailing their pros and cons.

MVC and modes: cross-cutting concerns
- Menu mode Game mode
Models None World, Inventory
Views MenuWidget WorldRenderer, HUD
Controllers MenuController CameraController, HUDController

MVC vs modes

In the previous article, I showed two veteran pygame developers using two different techniques to implement modes in their game. Shandy Brown uses a main controller (view) that relays events to its sub-controller(s) (sub-view(s)), so that when the mode changes, only the sub-components inside the main controller (view) change. The event manager is still sending events to the same main controller (view). This approach starts with MVC as a basis, but mode switching has to be implemented in both the controller, the view, and possibly even in the model. In short, the logic for switching between modes is scattered in each of the MVC components. This is called a cross-cutting concern.

On the other hand, Joe Wreschnig first assumes a state machine to handle the game modes. Then, in each mode, there may be an MVC. This is just another way of looking at the previous cross-cutting concern: each mode has to implement an MVC. There is an easy object-oriented answer: a Mode abstract class, with a list of models, views, and controllers as attributes, such that when a mode is created, it directly starts with an MVC structure. But now we have coupled the mode logic to the MVC logic, and this tangling is typical of cross-cutting concerns.

And this is how we end up with two ways to look at the same problem: either per MVC component (horizontally in the table nearby), or per mode (vertically in the table).

Any silver bullet?

I'm not completely sure, but I think object-oriented programming can not solve this problem by itself. Aspect-oriented programming aims at answering those problems, and there are some aspect libraries in Python. However, some Python programmers argue that Python is a dynamic language with powerful introspection mechanisms, and this allows for decorators and monkey patching to do the work just fine. I have not tried any of these solutions (although decorators seem quite interesting). Rather, I want to keep my code simple and readable, and I try to mix the previous two approaches in one, aware that there is no silver bullet. Let us look at the pros and cons of each approach.

The first approach involves a central event manager, main model, main view, and main controller. The single event manager makes debugging relatively easy, since all events have to go through a central point. However, since the main view and the main controller forward messages to their sub-components, there is no single point of control in a given mode anymore. There is another advantage of keeping the MVC at a higher level than the mode management: the view implementation can switch at any time in development from pygame to pyglet or panda3d.

There are also disadvantages. The first happens in an event manager with pubsub: let's say the HUD widgets in the view subscribe to the main view for ScoreUpdateEvent. The main view itself must subscribe to ScoreUpdateEvent from the central event manager. When the view changes mode, it should unsubscribe from ScoreUpdateEvent from the event manager, but should still subscribe to tick events. This means unsubscribing happens per event, and not per component. Thus, the view should memorize which events it subscribed to in the previous mode, and unsubscribe from each of them. This is awkward. Another architectural problem shows up when, say, a model component tries to create a controller component. The new controller component will have to subscribe to the model event broker. This is bad: there is a now controller in the model.

In the second approach, the mode state machine is central, and each mode implements its own event manager and MVC. Unlike the first approach, there is a single entity managing mode transitions, which is a good point. Another advantage is that sub-components do not need to unsubscribe from the central event manager: they stay subscribed to their mode's event manager until the mode is killed. But this has the drawback that the mode state machine must subscribe to each mode's event manager to be able to receive events for mode switching. Moreover, each mode now has its own model. For simple menu modes, there is no model, so it's not a big problem. But the config menu may change some values that the game may need to know about. It seems that data can be exchanged between modes only through 1) events, 2) init arguments, or 3) config files. A last disadvantage is the fact that there is no central place where the view is: each mode has a view, so switching from pygame to pyglet will require updating all the modes.

Conclusion

After giving a try to the first approach, I did not like having to unsubscribe to the event manager. So I went with the second approach: each mode has its own event manager and clock. I think that each mode having its own context is actually a good thing: a game instance should not need anything that is not in a config file or too big to fit as an argument or attribute of an event. For example, if the character selection mode returns the character name as a string, it passes it as an attribute of a GameStartEvent, so that the game instance can determine which character to play with. And finally, to fix the problem of changing from pygame to pyglet, my views exist in their own files, not with the mode's controller or model.

Links

26 October 2012

Game modes and MVC, part 1/2

Following a previous article about MVC and event managers, I look at how two veteran pygame developers are using MVC and/or event managers, in particular with respect to switching between modes (e.g. menu vs game vs config).

Example 1: Fool The Bar

In Fool The Bar, an MVC tutorial by Shandy Brown last updated in 2011, a central event manager publishes all the events it receives to the model, the main controller, and the main view. There is a pub/sub in place, but all components subscribing to the event manager receive all the events. The GameStartRequest event is used to switch from the main menu mode to the game mode.

The main controller subscribes to the event manager for all events, and forwards all of them, except the events like GameStartRequest that cause mode changes, to its single current subcontroller. Thus the subcontroller does not subscribe to the event manager, but only publishes to it. And when a GameStartRequest event is fired, the main controller switches its subcontroller from SimpleGUIController for the menu, to MainBarScreenController for the game.

Each subcontroller has its own input mapping. When the SimpleGUIController is active, if the user pushes the DOWN arrow key, the controller fires a GUIFocusNextWidgetEvent. When the MainBarScreenController is active, in game mode, pushing the DOWN arrow key does not fire any event.

Similarly, when the main view receives a GameStartRequest from the event manager, it switches from the menu mode to the game mode. But when the main controller has only one subcontroller active at a time, the main view can have multiple subviews active for one mode. For example, the game mode requires both the MainBarScreen (where the game is displayed) and the MainGUIView (where the HUD with score and possible actions is displayed) to be active at the same time. Thus the main view acts as an event manager for its subviews: subviews subscribe to the main view, and the main view relays events from the event manager to its current subviews. When the mode changes, the main view kills all its current subviews, and instantiates the new subviews.

Example 2: Angry Drunken Dwarves

Angry Drunken Dwarves is a 2004 falling-block puzzle game by Joe Wreschnig. It does not really follow MVC: instead of having distinct models, views, and controllers, modes simply stack up on top of each other. For example, when the player pushes the "Play a game" button, the button's callback stacks the character select mode on top of the main menu mode. When the player has selected a character, the character select mode returns with the selected character, and the callback starts the game mode, passing the character in argument.

Each mode instantiates its own event manager, but unlike Fool The Bar, the event manager is directly polling pygame.events for keyboard or click events. When a component decides that the previous mode should be brought back, such as when the game is over and the main menu should be brought back, that component sends a QuitEvent to pygame.events.

09 October 2012

Balance of Power - Crawford 1986

Notes from Balance of Power, a book by Chris Crawford published in 1986.

Games vs simulations

Games differ from simulations in three ways. First, games carry an artistic message, with unquantifiable concepts and feelings. Second, games simplify reality: they only keep the conflicts inherent in the situation, and unlike in real life, they provide clear and emotionally satisfying resolutions to those conflicts. Yet games maintain a level of realism appropriate to the audience. Finally, games are accessible; there is no need to study the manual.

Balance of power, in short

The player controls one of the two superpowers, the USA or the USSR, during the Cold War. Each turn, both superpowers send money, weapons, troops, or diplomatic pressure onto other countries to trigger insurgencies, coups d'etat, or Finlandization favorable to them. For example, the USSR can send money to Cuba.

If a superpower contest the actions taken by the other superpower, then a crisis testing the player's brinksmanship follows. Either the player stands firm on its ground (whether s/he was contesting or not), and brings the crisis one DEFCON level higher, ie one step closer to World War III and a nuclear holocaust, or s/he pulls back. For example, think about the Cuba missile crisis of 1962.

When a superpower stands firm and wins the crisis, it gains prestige points. A minor country such as Nicaragua is worth 2 points, while a country like East Germany is worth 200 points. The further down in a crisis a superpower backs down, the more it loses prestige. When the highest DEFCON level is reached, the game is lost for both players.

The game ends after eight turns. The superpower with most prestige points win.

Balance of insurgencies

Insurgencies consist of rebels trying to take over the government of a minor country. The strength of any armed faction is measured from its number of soldiers and its number of weapons. If 100 government forces share 1 weapon, then the government's power should be low, and a superpower providing a couple weapons would really increase its strength. Similarly, 1 government force with 100 weapons should be weak. Thus, the strength of an armed faction is the harmonic mean of the number of soldiers and the number of weapons. It is optimal when each soldier has a weapon.

Balance of crises

Nastiness is a game-wide variable; it describes how slippery taking actions in the world has become. When nastiness is high, the AI is more likely to contest the player's actions, start crises, and refuse to back down. Nastiness increases after each crisis or military intervention of a superpower, and decreases slowly every turn.

Pugnacity is a superpower variable; it describes how trustworthy the superpower is considered by minor countries. Pugnacity increases when the superpower is aggressive and wins crises, and decreases when a superpower backs down in crises. If a superpower backs down late in a crisis, it loses a lot of pugnacity.

Combined, nastiness and pugnacity amplify the amplitude of missteps; an error in judgement can cause the end of the game. This is exactly what Crawford was trying to convey about brinksmanship.