Wednesday, July 15, 2020

Making Pent Up Anger part 4 of 5

Last month was so hectic that I was not able to post. While I will try to block out time for this blog, I really am swamped so expect the occational delay or missed post.

AI in Board Games


AI stands for Artificial Intelligence and is a misused term but is commonly used in computer games to describe a computer controlled opponent. Scientists also use the term AI to describe research into Artificial Intelligence. AI research has resulted in a variety of techniques that can be used to help a computer make complex decisions such as fuzzy logic, neural networks, genetic algorithms, expert systems, and many others. The techniques used in the scientific research of AI, however, are not always appropriate for use in computer games.  What many beginning programmers seem to forget is that the goal of a computer game is to have a computer opponent capable of playing the game intelligently enough to provide a challenge to the player, not to necessarily beat the player.

I am not saying that you shouldn’t look into the various AI research that has been done. What I am saying is to keep your goal (creating an enjoyable opponent for the player to play against) in mind. Most people tend to play games to win and don't like losing. If the computer opponent always wins, they will quickly stop playing the game. At the same time, if the computer opponent doesn't play very well, the games will be too easy to win and the boring to play. This is the real challenge to creating a computer opponent.

While there are numerous techniques that can be used to build an AI for board games, the two techniques that tend to work the best are tree searches and decision trees. Some more complicated board games (such as war games) may require other techniques, but for most classic board games these two techniques should work.

Tree searches tend to use recursive algorithms and are mostly associated with chess games. Even today’s chess games use some variation of the recursive algorithm (with AlphaChess using a varient known as Monte Carlo tree search to help it's neural network understand how good different moves are). This technique simply has the computer take a look at every possible move, with each move being examined by taking a look at the possible moves the opponent could make, with each of these moves then being examined for all the possible moves the computer could make which are then examined for ... well, you get the idea. Each level of examination is a recursion (with recursion being a mathematical term for when functions fall back on themselves) The theoretical ideal of this technique is that you would keep looking at possible moves until there are no more moves. Realistically, you can only look at so many moves, so a limit on the level of recursion is usually placed on the computer. By reducing the number of recursions the computer executes, you reduce the intelligence of the computer, so you can use recursion levels as a way of controlling difficulty.

A Decision Tree is a light weight expert system. The computer makes its decision about what to do by following a set of branching rules. As branching rules simply correlate to if..then blocks, such an algorithm is fairly easy to write once the tree has been worked out. This type of technique is really good for track based games and for games that have random elements to them.

Advanced machine learning techniques such as Deep Learning tend to take advantage of the above techniques. While deep learning approaches can produce very good results, they do require training which can be a very computationally expensive. Having done research in this area, I can say that it definitely could be used but is a bit beyond the scope of this book.

Planning Pent Up AI


For Pent Up Anger we are going to use a decision tree to handle the computer moves. This is largely because of the random nature that the game has. As we don’t know what the player’s will roll, all decisions on the move will have to be based on the current state of the board. One problem with decision tree based AI’s is that they tend to be predictable. In some games, having a predictable opponent gives the player a fairly large advantage. In our case, knowing the way the computer thinks doesn’t help player much.

The best way to design a decision tree is to become familiar with the game you are designing the decision tree for. After playing through the game I came up with a basic strategy for the computer to use. All of this revolves around prioritizing the pieces that are able to move.

First, we assign a value to every piece and move the piece with the highest value. The weight value reflects the priority, so the higher the step is on the list of questions, the higher the weight.

1. Is the piece in the loading zone and the die roll that pieces number? This is our highest priority for two reasons. First, we want to win the game, and this is done by removing all our pieces. Second of all, getting to the loading zone is a time consuming process, so if an opponent takes us out at this point, we are losing a lot of time.

2. Is the piece in the starting location and the resulting move won’t take out one of my own pieces? If it is, then it is blocking new pieces from coming out of the starting gate.
 
3. Is the piece in the starting gate, the roll is that pieces number and the starting location is empty? We want to have as many pieces as possible on the board, as having to wait for the number to be rolled is not very efficient. Therefore, we get the pieces out on the board as soon as possible.

4. Can the piece be moved without taking out one of my pieces? Obviously, knocking yourself back to the start isn’t too bright. 

The above is enough for our AI, and results in a decent computer opponent. There are other things we could add to the AI if we desired. For instance, we could make moves that take out an opponent piece a higher priority than moves that don’t take out a piece. We could also add some type of distributed movement system so that the computer doesn’t focus on just one piece. It is also possible to have different difficulty levels of computer opponents by having the decisions made differently based on the difficulty of the opponent. 

Making a Computer Opponent


As computers can't think, we need to give the computer a set of rules for playing. There are many ways of handling the rules. The most common approach is to look at all possible moves and assign the moves a weight. This is the approach we will be using, and the piece class already has support functions for setting and getting a weight.

To determine the weight, we are going to make changes to the prepareMove function that is located in the board symbol. Quite simply, we are assigning a weight to each move. The weight is assigned using the rules we outlined earlier in the chapter. 

First, in the area where we determine if a piece can be launched or finished we assign a piece the weight of 100 to make sure it will exit if it can. Likewise if a piece can be launched we will give it a 90 value. This means that putting a piece in the end zone is the most important thing to do while launching a piece is second-most in importance. Note that we forgot to check if there is a piece in the launching area, which is a bit of a bug but as that is how the game was released I will leave that bug here and leave it as an exercise to fix this bug.

// see if piece can be launched or finished
if ((cntr+1) == roll)
{
if ( (loc == (start+cntr)) || (zonedist < 5) )
{
console.log("Piece is launchable/exitable");
this.pieces[player][cntr].useHighlight.visible = true;
if (zonedist < 5)
this.pieces[player][cntr].weight = 100;
else if (this.layout_piece[home] != player)
this.pieces[player][cntr].weight = 90;
// pieces[player][cntr].onRelease = choosePiece;
++movecount;
}
}

We then need to add some weights to the pieces that are movable on the board. This simply makes sure that we are not taking out our own piece and if not then giving pieces on the home location a boost so they get out of the way of other pieces ready to be launched. Otherwise, the weight is how far from the loading zone the player is.

if ( (loc >= 50) && (zonedist > roll) )
{
console.log("piece movable on board");
this.pieces[player][cntr].useHighlight.visible = true;
var targetloc = loc + roll;
if (targetloc > 104)
targetloc -= 55;
var targetPiece = this.layout_piece[targetloc];
if ((targetPiece == null) || (targetPiece.playerID != player))
if (loc == home)
this.pieces[player][cntr].weight = 60;
else
this.pieces[player][cntr].weight = 60-zonedist;
++movecount;
}

Finally, at the end of the move preparation routine, we call the ai\_move method to actually get the AI player to make their move.

if (listener.playerTypes[player] == 3)
this.ai_move(player);
else if (movecount == 0)
listener.doneMove();

The actual routine that uses the weight is also placed in the board symbol. It simply goes through the pieces and moves the piece with the highest weight. Ties go to the earlier piece. This allows us to know if none of the pieces are able to move (as all the pieces will have a weight of 0 in this case). 

spelchan.Board.prototype.ai_move = function(player) {
console.log("ai move called!");
var cntr, choice = -1;
var best = 0;
for (cntr = 0; cntr < 5; ++cntr)
{
if (this.pieces[player][cntr].weight > best)
{
choice = cntr;
best = this.pieces[player][cntr].weight;
}
}
if (choice == -1)
this.move_listener.doneMove();
else
this.choosePiece(player,choice);
}

The biggest problem with this AI is the predictability of the computer. Still, I have played against many human opponents who are just as predictable.  To test the AI one can simply manually set the player type to 3, or wait until we add the title screen where players will be able to select which players are playing, and if active players are human or AI.

The next installment will be the final one where we wrap up everyting.

Saturday, May 16, 2020

Making Pent Up Anger Part 3 of 5

Making Pent Up Anger part 3 of 5 - Head to Head version of the Game



This is the third part of a five part series on building Pent Up Anger using AnimateCC. The game can be found at http://spelchan.com/games/y2018/PentUpAnger.php .

Preparing the Pieces

The first thing we are going to need to do to get a head-to-head (hot seat) game going is to have more than a single piece on the board. This means that all the pieces for all five potential players will need to be tracked. An array to hold this information is easy enough to build. The big question is where should this information live? It could be a good idea to keep the board separate from the game state and instead have a separate class that manages the game play. This would have the advantage that you would be able to have different variants of the game and that controller would handle the game logic. This is going a bit too overboard and is something that could be done through refactoring at a later point if such a thing ended up being desired so we will follow the principle of only writing the code that we actually need to accomplish our task and put the game logic into the board class.

This means adding a bit of code to the board initialization class to set up the 25 game pieces that are necessary. While I don't like getting ahead of myself, we know that we are going to need to be able to click on the pieces and that this will require a listener so we will set up the click listener right now even though we won't be implementing it until later. The handler for clicking is going to be called choosePiece and we will need. A dummy version of the choosePiece method should be created with a simple console log warning you that the method has not yet been implemented.

As I stated earlier, to use a method as a callback function, you need to bind the method to the class being used. This has a rather strange behavior of being considered a different function call each time it is created which means that while you can add event listeners, removing those listeners is not possible unless you keep a copy of the originally bound function. This information is stored in the pieceListeners field and is used as the event listener.


this.boardMovie = movie;
this.pieces = [
 [movie.p1, movie.p2, movie.p3, movie.p4, movie.p5],
 [movie.r1, movie.r2, movie.r3, movie.r4, movie.r5],
 [movie.y1, movie.y2, movie.y3, movie.y4, movie.y5],
 [movie.g1, movie.g2, movie.g3, movie.g4, movie.g5],
 [movie.b1, movie.b2, movie.b3, movie.b4, movie.b5]
];
this.pieceListeners = [];
this.player = 23;
for (var cntrPlayer = 0; cntrPlayer < 5; ++cntrPlayer) {
this.pieceListeners[cntrPlayer] = [];
for (var cntr = 0; cntr < 5; ++cntr) {
this.pieces[cntrPlayer][cntr].playerID.text = ""+(cntr+1);
this.pieces[cntrPlayer][cntr].useHighlight.visible = false;
this.pieces[cntrPlayer][cntr].playerID = cntrPlayer;
this.pieces[cntrPlayer][cntr].pieceID = cntr;
this.pieceListeners[cntrPlayer][cntr] = this.choosePiece.bind(this, cntrPlayer, cntr);
this.pieces[cntrPlayer][cntr].addEventListener("click", this.pieceListeners[cntrPlayer][cntr]);
}
this.pieces[cntrPlayer][cntrPlayer].useHighlight.visible = true;
}

At the end of initialization, the pieces used by the player exist in a two dimensional array named pieces. This holds the pieces and relevant information about the pieces but does not set up the pieces. This needs to be done to start the game, and as we want to let the player play the game more than once without having to reload the game it makes sense to have a separate method for setting up the starting board. This we will call layoutPieces. This simply loops through the players and their pieces  and then places those pieces on the board in the appropriate starting location. It then loops through all the locations on the board and makes sure that the piece currently assigned to that location is null. This is important as only one piece is allowed on each board location so tracking the piece on a location is required to determine if a piece is going to be sent home.

spelchan.Board.prototype.layoutPieces = function() {
var temp;
for (var cntrcol = 0; cntrcol < 5; ++cntrcol)
{
for (var cntr = 0; cntr < 5; ++cntr)
{
temp = this.BOARD_LAYOUT[cntrcol][this.BOARD_START_INDEX] + cntr;
this.pieces[cntrcol][cntr].x = this.layout_x[temp];
this.pieces[cntrcol][cntr].y = this.layout_y[temp];
this.pieces[cntrcol][cntr].location = temp;
console.log("placing piece on " + temp);
}
}
for (cntr = 0; cntr < 105; ++cntr)
this.layout_piece[cntr] = null;
}

Now that we have the pieces able to appear on the board, the next step is to actually handle the player's turn.


Turn Handling

With the player pieces laid out on the board, we are ready to get the game under way. But for the players to do anything they need to be able to move. This requires a way for the player to roll the die. This can be combined with our solution for determining which players’ turn it is by simply having a roll button for each player. This means that we need to create a roll button for each of the five players. Instead of creating five buttons, we can ``cheat'' by only create one grayscale version of the play button and tint it. The states for this button can be seen in the image below. In addition to the roll button, we will need a skip button for skipping your turn which is set up the same as the roll button.

To make use of this button, it actually has on the screen. This is done in animate by adding the buttons to the Only the player who currently is playing can use this button, only the roll button for the current player needs to be on the screen. This is done by manually positioning the components on the game screen symbol as shown in the image below. Note that this screen shot is from later in development so there are messages and a button that would not normally be on the screen at this time.



This, in my opinion, is user interface related and is kept in the main Pent class.  This is set up allows us to set up in the startGameScreen which is the method we use for initializing the main game screen when the game is being set up. This simply sets up the pieces of the board and then sets up all of the roll and skip buttons.

spelchan.Pent.prototype.startGameScreen = function(movie) {
this.gameMovie = movie;
this.currentPlayer = 0;
this.board = new spelchan.Board(movie.board_movie);
this.board.layoutPieces();
this.die = new spelchan.Die(movie.die_movie);
movie.stop();
this.rollBtns = [movie.proll_btn, movie.rroll_btn, movie.yroll_btn, movie.groll_btn, movie.broll_btn];
this.skipBtns = [movie.pskip_btn, movie.rskip_btn, movie.yskip_btn, movie.gskip_btn, movie.bskip_btn];
this.rollHandler = this.startRoll.bind(this);
this.skipHandler = this.skip.bind(this);
for (var cntr = 0; cntr < 5; ++cntr) {
this.rollBtns[cntr].addEventListener("click", this.rollHandler);
this.rollBtns[cntr].addEventListener("mousedown", spelchan.tintedButtonEvent);
this.rollBtns[cntr].addEventListener("pressup", spelchan.tintedButtonEvent);
this.rollBtns[cntr].addEventListener("rollover", spelchan.tintedButtonEvent);
this.rollBtns[cntr].addEventListener("rollout", spelchan.tintedButtonEvent);
this.skipBtns[cntr].addEventListener("click", this.skipHandler);
this.skipBtns[cntr].addEventListener("mousedown", spelchan.tintedButtonEvent);
this.skipBtns[cntr].addEventListener("pressup", spelchan.tintedButtonEvent);
this.skipBtns[cntr].addEventListener("rollover", spelchan.tintedButtonEvent);
this.skipBtns[cntr].addEventListener("rollout", spelchan.tintedButtonEvent);
}
this.winText = movie.win_txt;
this.continueHandler = this.endGame.bind(this);
this.continueBtn = movie.continue_btn;
this.continueBtn.addEventListener("click", this.continueHandler);
this.UI_ROLL = 0;
this.UI_SKIP = 1;
this.UI_WIN = 2;
this.UI_HIDE = 3;
this.ROLL_TIME = 50;
this.PLAYER_LABELS = ["Purple", "Red", "Yellow", "Green", "Blue"];
this.PLAYER_COLORS = [0xFF8800AA", 0xFFAA0000", 0xFFAAAA00", 0xFF00AA00", 0xFF0000AA"];
this.updateGameUI(0, this.UI_ROLL);
}

As you can see by looking at the code, when the player's button is visible, clicking on the button calls the rollHandler function which is the startRoll method bound to the class instance. It simply sets up the roll and then updates the user interface using our updateGameUP which is called whenever the system needs to update the UI with the caller providing the current player and the state the game is in. Note that we defined four states as follows:

  • UI_ROLL Waits for player to start rolling the dice.
  • UI_SKIP Waiting for user to make move or skips the rest of their turn.
  • UI_WIN Indicates that the game has been won.
  • UI_HIDE Turns off all the user interface.

spelchan.Pent.prototype.updateGameUI = function(player, state) {
// hide all buttons
for (var cntr = 0; cntr < 5; ++cntr) {
this.rollBtns[cntr].visible = false;
this.skipBtns[cntr].visible = false;
}
// reveal stuff based on state
switch (state) {
case this.UI_ROLL:
this.rollBtns[player].visible = true;
break;
case this.UI_SKIP:
this.skipBtns[player].visible = true;
break;
}
}

spelchan.Pent.prototype.startRoll = function() {
this.die.startRoll(this, this.ROLL_TIME);
this.updateGameUI(this.currentPlayer, this.UI_HIDE);
}

The roll function calls the roll result method once the roll is finished. This method makes use of the board class to determine which pieces that the player can move based on the results of the roll.

spelchan.Pent.prototype.rollResult = function(n) {
this.updateGameUI(this.currentPlayer, this.UI_SKIP);
console.log("the roll was " + n);
this.board.prepareMove(this.currentPlayer, n, this);
}


The prepareMove function is a bit more complicated task so I am going to break the function up into several sections and comment on each section. The first thing this function does is determine where the relevant board locations for that player are. This is a simple lookup in our BOARD\_LAYOUT array.

spelchan.Board.prototype.prepareMove = function(player, roll, listener) {
var moveCount, cntr, loc, zonedist;
this.lastRoll = roll;
this.clearActions();
this.move_listener = listener;
var home = this.BOARD_LAYOUT[player][this.BOARD_HOME_INDEX];
var start = this.BOARD_LAYOUT[player][this.BOARD_START_INDEX];
var end = this.BOARD_LAYOUT[player][this.BOARD_END_INDEX];
this.player = player;
movecount = 0;

Next we loop through the five pieces that belong to the player. We then determine how far from home that particular piece is. The board is circular (sort of) which means that we have the extra complexity of dealing with the possibility that the target home location is actually less than the actual location. As this will result in a negative distance from home, we simply need to add the size of the board (55 locations) to this value to get the correct distance.

Knowing how far the piece has to travel to get to the home location is the way we determine if the piece is located in the loading zone. It is also going to prove to be a very useful bit of information when we get around to writing the AI next chapter.

for (cntr = 0; cntr < 5; ++cntr)
{
this.pieces[player][cntr].weight = 0;
loc = this.pieces[player][cntr].location;
console.log("piece " + cntr + "is at " + loc);
zonedist = home - loc;
if (zonedist <= 0)
zonedist += 55;
console.log ("piece " + cntr + " is " + zonedist + " from reaching home");

As you have probably already realized, not all pieces are going to be on the board.  In fact, some of the pieces will be in the starting gates. It is important that those pieces be able to move, otherwise we won’t have a game at all since there would never be any pieces on the board. To start a piece on the board, the roll must match that piece. Likewise, to remove the piece the roll must match the piece. This is simply enough to check. If the piece matches the roll, we then see if the piece is in the loading zone or in the starting gate. If the piece is in one of these area’s then the piece is movable.

// see if piece can be launched or finished
if ((cntr+1) == roll)
{
if ( (loc == (start+cntr)) || (zonedist < 5) )
{
console.log("Piece is launchable/exitable");
this.pieces[player][cntr].useHighlight.visible = true;
++movecount;
}
}

We then see if the piece is on the board and can move without going past the loading zone. We know the board starts at location 50, so that check is simply a location check.  The distance from home can be used to see if the roll will take you past home, as if the distance is less than the roll of the die, then the move will take you past the starting position!

if ( (loc >= 50) && (zonedist > roll) )
{
console.log("piece movable on board");
this.pieces[player][cntr].useHighlight.visible = true;
var targetloc = loc + roll;
if (targetloc > 104)
targetloc -= 55;
++movecount;
}
}

Finally, we see if there is actually a move available for the player. As all the moves are counted above, we simply see if there are moves. If not, we are done the move!

if (movecount == 0)
listener.doneMove();
}


When the player chooses a piece by clicking on it, the choosePiece function gets called. 
The choosePiece function will start the animation so it will be covered next.

Moving the Piece

The choosePiece function within the board movie is called when a piece is clicked. The first thing the function does is determine if the piece that was selected belongs to the player and if not simply returns. It then figures out where the piece is going to be moving to. If the piece is waiting to be put on the board, the target location is the player's starting spot on the board. If the piece is already on the board, it moves it the die roll's number of slots. This is not as easy as it sounds as the board is a circle but in memory is treated as a line. To make the board act circular, if a piece goes of the end of the line, it wraps to the beginning of the line.  Finally, it sets up the movingPiece, movingTargetX and movingTargetY variables so the updateMove method will know that it has a piece to move and where to move that piece to.

spelchan.Board.prototype.choosePiece = function(player, piece) {
console.log("***choosePiece called***");
if (player != this.player) {
console.log("Piece does not belong to player! " + player + "!=" + this.player);
console.log("speed = " + this.SPEED);
return;
}
console.log ("Piece was selected to be moved: " + player + "," + piece);
var loc = this.pieces[player][piece].location;
console.log ("Piece was on " + loc);
this.clearActions();
if (loc == (this.BOARD_LAYOUT[player][this.BOARD_START_INDEX]+piece) )
this.movingTargetLoc = this.BOARD_LAYOUT[player][this.BOARD_HOME_INDEX];
else if ( (loc >= this.BOARD_LAYOUT[player][this.BOARD_LOADING_START]) &&
(loc <= this.BOARD_LAYOUT[player][this.BOARD_LOADING_END]) &&
(this.lastRoll == (piece+1)) )
this.movingTargetLoc = this.BOARD_LAYOUT[player][this.BOARD_END_INDEX]+piece;
else
{
this.movingTargetLoc = loc + this.lastRoll;
if (this.movingTargetLoc > 104)
this.movingTargetLoc -= 55;
}
this.movingPiece = this.pieces[player][piece];
this.movingTargetX = this.layout_x[this.movingTargetLoc];
this.movingTargetY = this.layout_y[this.movingTargetLoc];
this.layout_piece[loc] = null;
console.log("Moving from " + loc + " to " + this.movingTargetLoc);
// onEnterFrame = updateMove;
}

The updateMove function is called every frame whether there is something to be animated or not. It knows that it has work to do if the movingPiece variable is not null. The first thing the function does is figures out if the current frame of motion will be the last frame of motion. This is simply a check of the distance from the current position to the ending position. If the distance remaining to move is less than the speed then we obviously are on the last frame of animation. Or at least the last frame of that pieces’ animation.

If the piece has reached the target location we need to determine if there is already a piece on the target location. If there is, we determine the original start-block location for that piece and create a new animation moving that piece back to start. If there is not a piece on the target location we call the doneMove handler assigned to the board which is used for managing what happens once the animation is finished. This handler will be written shortly.

If the piece being animated has not reached the end of it's animation, then we simply move towards the target location by applying the time adjusted movement speed to the piece.

spelchan.Board.prototype.updateMove = function() {
if (this.movingPiece == null)
return;
console.log("moving piece...");
var curX = this.movingPiece.x;
var curY = this.movingPiece.y;
var deltaX = this.movingTargetX - curX;
var deltaY = this.movingTargetY - curY;
var distance = Math.abs(deltaX) + Math.abs(deltaY);
if (distance <= this.SPEED)

this.movingPiece.x = this.movingTargetX;
this.movingPiece.y = this.movingTargetY;
// movingPiece.changeState(0);
this.movingPiece.location = this.movingTargetLoc;
if (this.layout_piece[this.movingTargetLoc] != null)
{
var temp = this.layout_piece[this.movingTargetLoc];
this.layout_piece[this.movingTargetLoc] = this.movingPiece;
this.movingTargetLoc = this.BOARD_LAYOUT[temp.playerID][this.BOARD_START_INDEX]+temp.pieceID;
this.movingTargetX = this.layout_x[this.movingTargetLoc];
this.movingTargetY = this.layout_y[this.movingTargetLoc];
this.movingPiece = temp;
}
else
{
this.layout_piece[this.movingTargetLoc] = this.movingPiece;
this.movingPiece = null;
// onEnterFrame = null;
this.move_listener.doneMove();
}
}
else

var moveX = deltaX * this.SPEED / distance;
var moveY = deltaY * this.SPEED / distance;
this.movingPiece.x += moveX;
this.movingPiece.y += moveY;
}
}

We are finished with the board movie for the moment, so the last thing we need to do is in the main movie. This task is simply a function to handle the game once the move is done. This function simply checks to see if the game has been won and if so ends the game. If the game is not over it goes to the next player.

spelchan.Pent.prototype.doneMove = function() {
if (this.board.checkWin(this.currentPlayer) == true) {
this.updateGameUI(this.currentPlayer, this.UI_WIN);
return;
}
this.currentPlayer = (this.currentPlayer + 1) % 5;
this.updateGameUI(this.currentPlayer, this.UI_ROLL);
}

The method for determining if the game has been won will be developed in the next section.

Winning the Game

As shown last section, whenever a player finishes a move, we need to determine if the game has been won. This is a fairly simple function as we simply need to check to see if all the pieces are in their end positions. A simple function in the board movie can handle this.

spelchan.Board.prototype.checkWin = function(player) {
var cntr, result = true;
var end = this.BOARD_LAYOUT[player][this.BOARD_END_INDEX];
for (cntr = 0; cntr < 5; ++cntr)
{
if (this.pieces[player][cntr].location != (end + cntr))
{
result = false;
break;
}
}
return result;
}

If the player has won, however, the state for showing the win message has not been handled. To do this we are going to need to create a continue button to add to the screen as well as a large text message that displays the win message. The continue button is a simple gray button with the states as shown in the image below.


The Pent class updateGameUI now needs to be updated to handle the win message and showing the win button. At the top of this method add the following:

this.winText.visible = false;
this.continueBtn.visible = false;

The actual code within the switch statement add the case for the UI\_WIN state as follows:

case this.UI_WIN:
    this.winText.color = this.PLAYER_COLORS[this.currentPlayer];
    this.winText.text = this.PLAYER_LABELS[this.currentPlayer] + "\nPlayer\nWINS!";
    this.winText.visible = true;
    this.continueBtn.visible = true;
    break;

Now the game is playable in hotseat, but it would be sure nice to be able to control the number of players, and be able to play against the computer. To do that we are going to need to delve into the world of Artificial Intelligence, at least as it applies to games. See you next month!


Saturday, April 18, 2020

Making Pent Up Anger part 2 of 5

Pieces of the Game


This is the second part of a five part series on building Pent Up Anger using AnimateCC. The game can be found at http://spelchan.com/games/y2018/PentUpAnger.php .

Building the Board


A hexagon board. Sounds complicated, but building it really isn't. First, we want 12 squares per side, with the corner square shared so we need to figure out how big to make a square. Whatever length we decide, needs to be divisible by 12. Making squares 25x25 would make a line that was 300 pixels long. If we draw a horizontal line 300 pixels long and then rotate it 72 degrees and rotate another 300 pixel horizontal line 144 degrees, we see that the combined lines go outside our 640x480 boundaries, but barely. If we use 288 pixel lines, the lines fit within the area of the screen. This means that 24x24 boxes will work.

I draw a 24x24 box. I then copy the box a couple of times and join them together. I then copy the three boxes and past the copy together. One final copy and past and we end up with a row of 12 boxes. Copy that row and rotate it 72 degrees. Join at the corner. Next past the copied boxes again and rotate 144 and place at the second corner. Again at 228 and 288 and you end up with a completed pentagon board.

Do a bit of colouring so the starting points and the exit zones are distinct. Then grab a 5 block chunk of boxes and create the inside exit zones by rotating the box as you did above.
Finally create starting zones. Turn the whole group into a movie clip.


Building the Player


Building a piece sounds simple, but there is a bit of a logistics problem. First, we need to know if the piece is movable and if it is the current piece selected. This means that a piece is going to have three states associated with it. Next, we are going to have to have a number on the piece indicating which of the five pieces the piece is. Three states with five pieces is 15. There are also five colors. fifteen times five is 75. Drawing 75 pieces is not a pleasant task so we are going to have to come up with a better solution.

In Animate CC, buttons are a special case of a movie clip which simply contain four frames. There is nothing stopping you from using multiple layers on a movie clip, and you can programmatically control objects. By using multiple layers on a button, we can one highlight silhouette indicating which pieces can be moved, a selected silhouette for each color and a piece for each color reduces this down to just 11 shapes which really are just 2 shapes that are colored differently.

Tinting could be used to get different coloring. Create.js does behave a bit strangely with tinting but there is an easy fix for that. We will use tinting with our buttons later to demonstrate this. I chose to do multiple versions of the pieces as this gives you a bit more control over the colors and I really didn’t like the tinted version in my original Flash version of this game. For this reason I have gone with the button technique.


As you can see by looking at the figure above, the button has 4 layers in it. The ``ID'' layer holds a textfield which will allow the changing of the label assigned to that player. This easily allows us to change the number of the piece meaning that we only need to have one piece per color. This is set when the board is initialized, which we will be covering later.

The ``base'' is the actual image for the player. It is second from the top so that it remains clearly visible even when there is indication that the piece is select-able or the piece is highlighted. Each color has their own image which has color choices made to make the piece look nice for that particular color.

The third layer is the ``use'' layer of the button and is simply a movie clip of a white silhouette of the game piece. This movie clip is used by all five variations of the player button. The purpose of this layer is to give the user an indication as to which pieces they are able to move. By having this as a movie clip, you can simply set the visibility of the movie clip to false if the piece is not usable and set it to true when it is usable. This will be done when we implement the game play portion of the game later.

The final layer is not in the button's up state as it is used as a highlight for when the player moves their mouse over the piece. This is shown in the figure below.


Building the Die


This game uses an eight sided die. For those of you unfamiliar with such a die, the die looks sort of like a diamond. You use it like you would a normal six sided die, but it has eight possible results instead of six. The primary reason for going with an eight sided die instead of the standard die was three-fold. First it adds something unique to the game. Second, it speeds up the game as the average roll will be 4.5 as opposed to 3.5 for a six sided die. Finally, they are cool.

Building the die movie is fairly simple. First we start with an image of the die in a finished position. On a separate layer we have text for the eight different values (labelled r1 to r8). We also have mid-roll die images. Figure \ref{fig:die}  shows the frames that make up the die.

This is the minimal die animation approach. A more ambitious approach would have numbers on the between frames with the values shown. This would require many more frames of animation as you would have the 8 result frames and would need 18 additional between frames for 26 frames.  Granted, the animation would look better so if you have the artist time to do this then it may be a good option. 

As with our other movies, we treat the die movie as a class. To handle the rolling of the die we will need some functions, but first we will initialize the die. This simply sets the value of the die to 8 and resets the listener and roll variables, which we will explain shortly.

spelchan.Die = function(movie) {
this.dieMovie = movie;
this.cutoff = 300;
this.frameListener = this.handleTick.bind(this);
this.clickListener = this.handleClick.bind(this);
this.dieMovie.addEventListener("tick", this.frameListener);
this.dieMovie.addEventListener("click", this.clickListener);
this.rolling = false;
this.value = 0;
this.rollListener = null;
}

Rolling the die presents us with a bit of a problem. We want the roll to be animated but if the roll is animated, then it can’t return a value right away. To solve this problem we have the startRoll function to get the die rolling and then once the animation is finished we will have the die class call the handleClick function to stop the movie. The end of roll handler is called handleClick as it is handling the result from the player clicking on a roll button, though this is a poor name but at the time of development it seemed to make sense. This is why code reviews are a good thing to have.

The actual die roll will consist of 5 to 20 spins (the number determined at random) with a spin consisting of an in between shape (the three shapes at the bottom of figure 4) followed by a number. To start the roll the number of spins is determined followed by a call to the handleTick function.

spelchan.Die.prototype.startRoll = function(listener, cutoff) {
this.rollListener = listener;
this.rolling = true;
this.cutoff = cutoff;
}

The handleTick function gets called every frame. It simply looks to see if the die is being rolled and if it is selects a new frame to be displayed. When the die is being rolled, there is a cutoff variable that determines when we are ready to end the roll at which point we call the clickListener function which handles the results of the click on the roll button.

spelchan.Die.prototype.handleTick = function(e) {
if (this.rolling) {
this.dieMovie.gotoAndPlay(Math.floor(Math.random()*17)+1);
--this.cutoff;
if (this.cutoff <= 0)
this.clickListener(null);
}
}

Once the roll is finished, the handleClick function makes sure that the die is actually in the process of being rolled and if so will pick the final value, setting the movie clip to that particular frame. It then makes a call to the roll listener that is currently attached to the die. The roll listener is set by the game as appropriate using a setRollListener method that simply specifies the function to call when the roll is finished.

spelchan.Die.prototype.handleClick = function(e) {
if (this.rolling) {
this.rolling = false;
this.value = Math.floor(Math.random()*8)+1;
this.dieMovie.gotoAndStop(this.value);
if (this.rollListener != null)
this.rollListener.rollResult(this.value);
}
}
spelchan.Die.prototype.setRollLister = function(listener) {
this.rollListener = listener;
}

Board Layout


We already have a board but the player pieces need to be placed on the board. We need to know the coordinates of every square that the player can land on so we can place the pieces on the proper screen locations.  We could create a table by hand that contains all the coordinates of the locations. This is time consuming and very prone to errors but could be simplified by writing a utility to help do this. Such a utility would only need to display the board and then record all the clicks on the board that the user made and storing them in a table that can be saved. For board games that have complex layouts, this may be required. As our board is laid out in a fairly linear and has a regular pattern to it so we should be able to algorithmically create the table.

We still need some coordinates, which lead to a strange problem. Despite dragging an instance of the piece movie to the desired starting locations and using those coordinates, Animate seems to shift the location. After a little investigation, I found out that the properties panel is actually giving you the top-left location of the object, not the location of the anchor. This is something you will need to keep in mind when using coordinates provided to you by Animate.

All the pieces and code to handle the pieces are going to be placed in our board movie. To handle this, a couple additional layers are going to be needed. The layers are “code,” “pieces,” and our original layer which we will name “back.” On the pieces label we drag a copy of our piece movie which we will name “piece\_movie.”

The board initialization will be done in the constructor which is also where we set up all of our ``constants''.  When you think about it, the board really can be broken into three sections for each of the five colors. Every color has a side of the board starting at their starting gate, they also have an ending block of squares where the pieces go when they have finished. Finally, there is the starting - or home - block. All of these things can have simple coordinates attached to them and then, if we know the angle, we can determine the location of the other locations within that group. For the game, we are going to also need the board id’s of these locations as well as the starting and ending ids for the loading zone.

All of this information results in seventy different pieces of information. To handle all of this information without having too many variables to worry about, I have created an array of five arrays with the five arrays containing the fourteen items we are interested in. Each of the items is assigned an index value within the array, with a constant being created so in the future I don’t need to remember the particular index value.

Once this array is created, two layout arrays are created to hold the x and y positions of all of the locations on the board.  Some simple trigonometry is used to generate all the positions within that array. A lot of work, but all this initialization will make developing the rest of the game a bit easier.

var degree = Math.PI / 180;
// set up constants for array indexes
this.BOARD_START_INDEX = 0;
this.BOARD_START_X_INDEX = 1;
this.BOARD_START_Y_INDEX = 2;
this.BOARD_START_ANGLE_INDEX = 3;
this.BOARD_END_INDEX = 4;
this.BOARD_END_X_INDEX = 5;
this.BOARD_END_Y_INDEX = 6;
this.BOARD_END_ANGLE_INDEX = 7;
this.BOARD_HOME_INDEX = 8;
this.BOARD_HOME_X_INDEX = 9;
this.BOARD_HOME_Y_INDEX = 10;
this.BOARD_HOME_ANGLE_INDEX = 11;
this.BOARD_LOADING_START = 12;
this.BOARD_LOADING_END = 13;
this.BOARD_LAYOUT = [
//si,  sx,  sy, sa, ei,  ex,  ey, ea, hi,  hx,  hy, ha, ls, le
[  0, -39,-236,180,  5, -11,-179,270, 50,  -9,-219,324,101,104],
[ 10, 231, -84, 90, 15, 169, -48,198, 61, 205, -58,252, 57, 60],
[ 20, 151, 212,  0, 25, 101, 162,126, 72, 121, 198,180, 68, 71],
[ 30,-175, 212,180, 35,-123, 161, 54, 83,-145, 192,108, 79, 82],
[ 40,-254, -42,270, 45,-185, -48,342, 94,-227, -64, 36, 90, 93] 
];
this.layout_x = new Array(105);
this.layout_y = new Array(105);
this.layout_piece = new Array(105);
for (var cntrcol = 0; cntrcol < 5; ++cntrcol)
{
for (var cntr = 0; cntr < 5; ++cntr)
{
this.layout_x[this.BOARD_LAYOUT[cntrcol][this.BOARD_START_INDEX]+cntr] =
24 * cntr * Math.cos(this.BOARD_LAYOUT[cntrcol][this.BOARD_START_ANGLE_INDEX] * degree) +
this.BOARD_LAYOUT[cntrcol][this.BOARD_START_X_INDEX];
this.layout_y[this.BOARD_LAYOUT[cntrcol][this.BOARD_START_INDEX]+cntr] =
24 * cntr * -Math.sin(this.BOARD_LAYOUT[cntrcol][this.BOARD_START_ANGLE_INDEX] * degree) +
this.BOARD_LAYOUT[cntrcol][this.BOARD_START_Y_INDEX];
this.layout_x[this.BOARD_LAYOUT[cntrcol][this.BOARD_END_INDEX]+cntr] =
24 * cntr * Math.cos(this.BOARD_LAYOUT[cntrcol][this.BOARD_END_ANGLE_INDEX] * degree) +
this.BOARD_LAYOUT[cntrcol][this.BOARD_END_X_INDEX];
this.layout_y[this.BOARD_LAYOUT[cntrcol][this.BOARD_END_INDEX]+cntr] =
24 * cntr * -Math.sin(this.BOARD_LAYOUT[cntrcol][this.BOARD_END_ANGLE_INDEX] * degree) +
this.BOARD_LAYOUT[cntrcol][this.BOARD_END_Y_INDEX];
}
}
for (cntrcol = 0; cntrcol < 5; ++cntrcol)
{
for (cntr = 0; cntr < 11; ++cntr)
{
this.layout_x[this.BOARD_LAYOUT[cntrcol][this.BOARD_HOME_INDEX]+cntr] =
24 * cntr * Math.cos(this.BOARD_LAYOUT[cntrcol][this.BOARD_HOME_ANGLE_INDEX] * degree) +
this.BOARD_LAYOUT[cntrcol][this.BOARD_HOME_X_INDEX];
this.layout_y[this.BOARD_LAYOUT[cntrcol][this.BOARD_HOME_INDEX]+cntr] =
24 * cntr * -Math.sin(this.BOARD_LAYOUT[cntrcol][this.BOARD_HOME_ANGLE_INDEX] * degree) +
this.BOARD_LAYOUT[cntrcol][this.BOARD_HOME_Y_INDEX];
}
}

For testing, we create a copy of our applet specifically for testing the board positions. It will do this by using a loop where a piece goes through every location on the board. This is done in two frames. In the frame we label “Test” we place the following code:

this.test_movie._x = this.layout_x[test_spot]; 
this.test_movie._y = this.layout_y[test_spot]; 

and ten to fifteen frames after the label (so we have time to see the position) we place this code:

++this.test_spot; 
if (this.test_spot >= 105) 
this.test_spot = 0; 
this.gotoAndPlay("Test");

So now we have a board that the players can place pieces on. The next task is to get a head-to-head version of the game working which is what we will be starting on next.