Page 1 of 1

Programming a JS game - part 1 (design) Rate Topic: -----

#1 Dormilich  Icon User is offline

  • 痛覚残留
  • member icon

Reputation: 3541
  • View blog
  • Posts: 10,250
  • Joined: 08-June 10

Posted 28 November 2012 - 05:09 AM

Hello,

welcome to my JS game tutorial, or rather I should say, programming tutorial (well, it sounds better in the title and I do indeed explain it on a game (though the game itself doesn’t really matter)). Nevertheless, this tutorial is intended for those who want to get better in their Javascript coding (no matter how proficient they are yet) and therefore I will explain a lot of things around coding itself and focus a little less on the code lines.
___________________________________________

instead of a Preface - a word towards the classical programmer

If you already have experience with one of the main programming languages (like C++, Java, C#, VB, etc.) you will note there there is one all-important thing missing. There are no classes. But fear not - this is not a bug, this is a feature!

You are now encountering a programming language that has no needs for classes. Javascript is all about objects (in the plain definition of an object [​i]). In fact, there is hardly anything that is not an object (specifically the data types null and undefined and the boolean/string/number literals taking an inbetween position). Another unusual thing is that Javascript does not create objects (it would need a class to do so), it copies/clones objects. Therefore you have a Master Object with the name of (tadaaa!) Object. Every object in Javascript is somehow derived from this [ii].

Well, back to programming. When you deal with a new language, you will compare it to the languages you already know. The analogies you can draw are a powerful tool to speed up learning significantly. Alas, analogies are only that powerful, if the base of them is still valid. [*] And here lies the crux of it, Javascript with its object cloning and prototypal inheritance, etc. is the complete opposite what you know. So treat it as a completely new thing and you will learn it faster than it looks like in the beginning.

[​i] - quote from http://en.wikipedia....uter_science%29
Three properties characterize objects:
  • Identity: the property of an object that distinguishes it from other objects
  • State: describes the data stored in the object
  • behavior: describes the methods in the object's interface by which the object can be used

[ii] - one of the reason not to modify the Object object.
[iii] - other Design Patterns that represent a Singleton (like the Module Pattern) eventually return an … object literal.
[*] - an example where an analogy is obvious, but doesn’t work out: the molecular structure of ammonia and phosphine. Both have a triangular pyramidal structure (the analogy), but the bonding angles and also the chemical properties differ significantly. (For the interested reader, the structural discrepancy can be sufficiently explained by the 2nd order Jahn-Teller effect)
___________________________________________

1. Making a plan

So I want to talk about how I create a programme in Javascript. Though the programmes are usually different, there is a general approach that I usually take.
As for matters, I will be using the Tic Tac Toe game as example, as it is one of the most simple games I know (there is also a Tutorial about creating TTT via HTML5/<canvas>). For that matter I use a rather simple version with CSS/HTML. You will find that there is much room to make improvements, esp. in the GUI and UX fields, but I want to concentrate on the Javascript part in the tutorial.

Before I get to coding, I need to make a general strategy.
  • what are the rules of the game?
  • what will the HTML implementation be like?
  • what user interaction will I use?


If you look closer to this three questions and you have scratched into programming a little deeper, you might recognise a certain pattern:
  • game rules => Model
  • HTML implementation => View
  • user interaction => Controller


Exactly, I design a JS programme based on the MVC Pattern (which makes absolute sense, since a pattern is a generic solution strategy).

Some more theory
Spoiler


Now that we know that we follow the MVC Pattern, I will discuss each part of it and show, how these thee parts fit building the final programme.

2. Model - the game’s rules

Every game come with its own set of rules. These may be simple (TTT, Go) [1], intermediate (Chess) or complex (D&D). One of the challenges in game creation is rewriting the lexicographical rules (the rules a human can understand) into programmatical rules (a set of instructions a computer can understand).

It may not be obvious, but to accomplish that you need to really, really understand the game rules yourself (i.e. you must be able to answer even the dumbest or most complex questions one could ask about the game) [2].

OK, let’s compile a list of rules that we know.

  • there is a 3x3 grid as a playing field
  • there are 2 players
  • the players draw consecutively
  • each player uses his own symbol. by convention the 1st player uses a cross (X) the 2nd player a circle (O)
  • the current player (on-turn) must choose an empty field and mark it with his symbol
  • a marked field cannot be chosen again (is locked) by either player
  • skipping his turn is not an option
  • the player off-turn must not interfere

and probably the most important:
  • if a player reaches 3 of his symbols in a row or a column or a diagonal, he wins. this will ultimately end the game.

and eventually:
  • if neither player gets 3 successive fields and all fields are marked, the game ends with a draw. (if both players are sufficiently intelligent, this is the usual outcome)


Phew, for a simple game that is quite a lot of description. If you were to explain all that to a friend, you’d probably just demonstrate it, but unfortunately, computers do not understand this (that easily).

Now that we are this far (and have broken the rules down that we can more or less translate them into algorithms (that’s what a computer can understand)) comes the big miracle-turning that into code. But how doing that exactly?

... dramatic pause ...


Spoiler


Before I’m going to answer that, I will need to briefly explain how the interaction in a programme happens. For the most part, programming languages in common and JS in special, use objects. Objects are little chunks of data that possess functionality attached to it. So if we had an object that represents the rules, each rule (= functionality) would be a function [3] of that object.

// an object containing all our rules
var Rules = { /* ... */ };

If a certain rule is to be executed, we would call the specific rule.

// execute the rule named "specialRuleOne"
Rules.specialRuleOne();

// e.g. the castling rule in chess
Chess.castling();
// whether the castling rule really applies,
// needs to be checked in the method.
// the visual result of the castling
// may be created somewhere else
// but Chess.castling() will make sure
// it is done according to the rules


In conclusion, if in a programme an interaction is required, the appropriate method of the appropriate object needs to be called somehow (that somehow depends on the type of action).

Back to converting to computer code. What we need is an object for the rules and this object needs to have a certain amount of methods to act as rules. It is not required though, that each of the rules we listed above must have its own method. Often two or more rules can be combined and others are implied by the programming approach (e.g. on a turn-based game, consecutiveness is almost implied).

Therefore we create a single object for the rules. This will be a Singleton, but we could as well use a Constructor for several games on the same page.

// the object hosting our rules
var Model = {};


Then there are 2 similar, yet not identical players. These players can again be represented by objects. We could create a constructor function for each one, but to keep it simple I just predefine the objects. Now, what properties would such a player have? Certainly the symbol that he uses to mark a field. And a name. So we make a player object:

var player_1 = {
    name:   "Player 1", // using a default name here
    symbol: "X"
};


Player 2 would be created in the same manner. Now is the problem how to combine the players and the rules. We know that we have to switch between both players, hence using the player objects as direct properties of the rule object is unfavourable. I know (from experience) that putting the players in an array would reduce the object management to array index management (it’s easier to code with obj.player[1] than with obj.player_1). Hence:

var Model = {
	players: [
		{
			name:   "Player 1",
			symbol: "X"
		},
		{
			name:   "Player 2",
			symbol: "O"
		}
	]
};


So we have a turn-based game, that means if the action of player 1 is 1) valid and 2) over, player 2 comes into play. Condition 1 means that we have to check, whether the player’s action is valid. This is to be done in a method. Whether that method is in the Model or the Controller depends on the complexity of the test and the kind of action that was invoked. The validation is usually started in the Controller and may call sub-routines in the Model. Detailed analysis of possible game errors reduce validation to checking if a valid field is chosen (there is not much to actually do wrong in the game). That is clearly the responsibility of the Controller. So point 1 (validation) will be covered in the Controller object.
Leaves point 2, ending the turn of the current player. Both players are part of the Model object, therefore the Model object needs to do this. As this shifts the turn to the next player, "next" would be a good description. Unfortunately "next" is often used for iteration-related methods so we make it a bit more descriptive and name it "nextTurn". Now what does the nextTurn() method has to do?
  • switch players
  • save the current state of the game

Sounds easy, but is it? Well, switching players should not be complicated. I already hinted that by using an array, it would come down to handling array indexes. But how does the object know which player is next? Or even which player is current?
This is where the state comes into play. It is somehow the reduction of all the valid actions done since the game started. How we represent the state strongly depends on what information we want to be able to extract from the state. In our case this information is
  • who is current player?
  • which fields are already marked by whom? (we need that for the winning condition)

Question 1 is relatively easy, Player 1 is current player in turn 1, 3, 5, 7 & 9, Player 2 is current player in turn 2, 4, 6, 8. Let your imagination and experience play and you will find a way to determine the current player based on the turn number. I will use a modulus calculation along current_player = players[turn_number % 2]. However you’re free to use any other approach.
Question 2 poses a stronger problem. The state of a field (marked by X, O or not at all) would be the responsibility of the View object. I.e. a field can be owned by Player 1, Player 2, or none. If we look at that from the other side, this is the same as saying that (e.g.) Player 1 owns fields a, b & c and Player 2 owns fields x, y & z. Et voilà, we have a state description based on the players (hence the Model object). This causes a whole new cascade of problems and solutions:
  • the players need a state property that saves the owned fields
  • there must be an abstraction between owned field and state representation
  • one must be transformed to the other


  • arrays are a good storage type. It will go into the player objects.
  • one of the core problems. You will need to use creativity here. I went for simply assigning a number to each field.
  • subject to the Controller object (but passing around a number should not pose a problem)


With that the Model object grows to
var Model = {
	players: [
		{
			name:   "Player 1",
			symbol: "X",
			fields: []
		},
		{
			name:   "Player 2",
			symbol: "O",
			fields: []
		}
	],
	// the state of the game is saved here, but the state of the
	// fields is put over to the player objects, to which I will
	// have access through the current_player property (see below)
	turn_no: 0,
	// since objects in JS are copied by reference, I’ll copy the
	// player on-turn into this auxiliary property so no matter
	// in which state of the game I’m in, I will always act on the
	// correct player object.
	current_player: {}, // just a default
	nextTurn: function() {
		// assign next player
		Model.current_player = Model.players[Model.turn_no % 2];
		// count up turn (i.e. save state)
		Model.turn_no += 1;
	}
};


What is left now is to determin the winner. From a design point-of-view that can be done in the Model or Controller. Which one to choose depends on the circumstances. This is usually the point with poses the most difficulty. We have to translate an optical state (3 matching symbols in a row) to something computer-recogniseable.
Since I went for numbering the fields one possibility is to determine all combinations of numbers that represent a row (column, diagonal) and check if that is contained in the numbers of the current player (only the current player can win) [4]. Again, play with your creativity. What I did stems from the following thought: If I do an intersection of a winning combination with the fields owned and the winning combination is contained therein, the length of the intersection result must be the same as the length of the winning combination (i.e. 3). (mathematically short: Fields ∩ Win = Win)
// example
var win = [1,2,3];
var fields = [1,5,3,4,2];

// test
if (3 === intersect(fields, win).length) {
    // => game over
}


Game over? Another method we have to create (sigh). Alas, not in the Model. For the Model it just suffices if it is not used any more (the state is not changed), i.e. we need a cut-off from the user’s possible interactions ... which is the responsibility of the Controller.

Adding a win test thus yields:
var Model = {
	players: [
		{
			name:   "Player 1",
			symbol: "X",
			fields: []
		},
		{
			name:   "Player 2",
			symbol: "O",
			fields: []
		}
	],
	turn_no: 0,
	current_player: {},
	winLines: [
		[1,2,3], [4,5,6], [7,8,9], // horizontal
		[1,4,7], [2,5,8], [3,6,9], // vertical
		[1,5,9], [3,5,7]           // diagonal
	],
	nextTurn: function() {
		Model.current_player = Model.players[Model.turn_no % 2];
		Model.turn_no += 1;
	},
	isWinner: function() {
		// get the occupied fields of the current player
		var fields = Model.current_player.fields;
		// test the player’s fields for one of the listed winning combinations [5]
		// if successful, throw a notification to the calling function,
		// otherwise do nothing
		for (var l = Model.winLines.length; l--;​) {
			if (3 === Model.winLines[l].intersect(fields).length) {
				throw (Model.current_player.name + " wins!");
			}
		}
	}
};


[1] - just because a rule set is simple does not mean that you can easily master a game. Often it is quite the contrary, because the rules are simple you need complex strategies to win the game.
[2] - another reason to use Tic Tac Toe (TTT) as example.
[3] - a function belonging to an object is called a "method" of that object. And because of JS’s underlying structure, each function is already a method (be that the window object or another one). So in JS function and method are synonymic (btw, a method is also a property, as in JS functions are regular objects).
[4] - coincidentally, that is the same approach as in the TTT tutorial.
[5] - since array intersection should be generic, I decided to build that functionality into the Array object itself. I could have used a helper method as well.


Read about how that is put together in the next part of the tutorial.

This post has been edited by Dormilich: 13 December 2012 - 01:01 PM


Is This A Good Question/Topic? 3
  • +

Page 1 of 1