Showing posts with label ebook. Show all posts
Showing posts with label ebook. Show all posts

Saturday, August 15, 2020

Making Pent Up Anger Part 5 of 5

 I have finally gotten around to posting the code for this at https://github.com/BillySpelchan/BlazingPorts. This month we are finishing up Pent Up Anger, and next month will hopefully be my attempt at the js13kGame competition which I hope to be able to enter as it is going on for a month. I am hoping to somehow find an hour a day to put into it, so it won't be a great game but I will try to do something good. 

Putting Everything together

Tri-state Buttons

At this point in time we have a complete and playable game but need to alter the code to change which players are humans, which are inactive, and which are computer players. We need a way of selecting which players are which. This means that we are going to need some type of menu system that lets the player choose who controls a particular color.

As every color is going to have one of three choices, and only one of the three choices is valid at a time, it makes sense to create a component that lets the player choose one of three states. As there is no such component built into Animate, this Tri-state button then needs to be created as show in the figure below.


First, we create a movie object the size of the desired object. Then we assign a dynamic text object to the left side of the tri-state button. This is named label\_txt and is going to be used for setting the label. Next we draw the three boxes. We create a fill object to represent the check state of the object. This is converted into a movie named select\_box. Three instances of this movie are used, one for each box. The idea here is that, as we are going to have to control the state anyway, one of the three box movies will be visible at a time.

The symbol for the movie is placed on the screen where it is desired, and a tint is applied to this movie so that each instance is a different color. We then create a managing class which handles the state of the component.

We need a way of actually making this component do it's work. We create an invisible button the size of a box. The over frame will be a visible solid color as will the highlight. Instances of these buttons go over each of the three boxes. Now we are ready to write the code for this component. We start of with the initialization code. When creating the button, the creator provides the movie instance of the button, as well as the label to be assigned to the button, and the type of player to initially assign to the button. Listeners for the three box movies are set up so that we will know when the type of player is being changed. Finally, we call a method designed to update the state of the component called refreshComponent.

spelchan.TriState = function(movie, label, playerType) {
this.movie = movie;
this.movie.label_txt.text = label;
this.current_select = playerType;
this.box1Listener = this.setSelected.bind(this,1);
this.box2Listener = this.setSelected.bind(this,2);
this.box3Listener = this.setSelected.bind(this,3);
this.movie.box1_btn.addEventListener("click", this.box1Listener);
this.movie.box2_btn.addEventListener("click", this.box2Listener);
this.movie.box3_btn.addEventListener("click", this.box3Listener);
this.refreshComponent();
}

The refreshComponent method simply sets the box movies visibility to reflect the currently selected player type. It also has a updateCache() method which is used to force the cache holding the pre-drawn version of the component to be redrawn. This is necessary due to the use of tinting. Because tinting is such a costly operation, the tinted object is cached so that only needs to be drawn once. When an object changes, however, the cache needs to be forced to redraw the image so that it is up to date.

spelchan.TriState.prototype.refreshComponent = function() {
console.log("Should be adjusting box state");
this.movie.box1_movie.visible = (this.current_select == 1);
console.log("box1 = " + this.movie.box1_movie.visible);
this.movie.box2_movie.visible = (this.current_select == 2);
this.movie.box3_movie.visible = (this.current_select == 3);
this.movie.updateCache();
}

Next, we need to be able to select a button (otherwise, how are we going to change the state). Note that this function automatically calls the refreshComponent function to make sure that the changed state is drawn. This is paired with a getSelected function which simply returns the currently selected state (1 for the first button, 2 for the second, and 3 for the third). This is the method that was set up in the initialization and thanks to the flexibility of JavaScript's bind method, we can let the function know exactly which button was clicked. 

spelchan.TriState.prototype.setSelected = function(n) {
console.log("TriState button selection changed to " + n);
this.current_select = n;
this.refreshComponent();
}

Having the ability to select the player type is once thing, but the main program needs a way of finding out which state the tri-state button was in once the game is about to start. We could simply grab the current\_selected parameter, but a more proper way is to have a getter method, which simply returns teh current\_selected parameter.

spelchan.TriState.prototype.getSelected = function() {
return this.current_select;
}

One last bit of housekeeping is to be able to remove the listeners once we are done with the component.

spelchan.TriState.prototype.clearListeners = function() {
this.movie.box1_btn.removeEventListener("click", this.box1Listener);
this.movie.box2_btn.removeEventListener("click", this.box2Listener);
this.movie.box3_btn.removeEventListener("click", this.box3Listener);
}

And we have a completed tri-state button so it is easy to set up which type of player each of the five players are. We still need to put five of these buttons somewhere so the next task is to build a title screen.


Title Menu


Now we can assemble the title screen. First, a simple logo. This is simply a nice cursive font for the “Pent up” text and a harsh, bold font for the “Anger” portion. Certainly, if you have the time you could design a fancy title. I am not an artist, so I will keep things simple.

Next we put together the menu of options and the “Start Game” button. The menu is made up of five of the tri-state buttons that we created in the last section. Each of the tri-state buttons is tinted the appropriate color. They are each named using the following template: label\#\_movie. The number of the player replaces the crosshatch symbol. The start button is given the label “start\_btn.” To make the three states of the tri-state button understandable, the labels “No player,” “Human,” and “Computer” are used. The image below shows the resulting title screen.



While the title screen looks nice, it doesn’t do anything at the moment.  To get the title screen to actually do something, we are going to need to add some code. In the first frame we have the initialization code that sets the default player to human, and the startGame function which is called by the start button when it is selected. The startGame function simply sets the global player\_type variables to the values in the tri-state fuctions. To prevent an infinite loop from occurring, it also makes sure that there is at least one human or computer player.

spelchan.Pent.prototype.initTitle = function(movie) {
this.titleMovie = movie;
this.triStates = [];
this.triStates[0] = new spelchan.TriState(this.titleMovie.label1_movie, "Purple", this.playerTypes[0]);
this.triStates[1] = new spelchan.TriState(this.titleMovie.label2_movie, "Red", this.playerTypes[1]);
this.triStates[2] = new spelchan.TriState(this.titleMovie.label3_movie, "Yellow", this.playerTypes[2]);
this.triStates[3] = new spelchan.TriState(this.titleMovie.label4_movie, "Green", this.playerTypes[3]);
this.triStates[4] = new spelchan.TriState(this.titleMovie.label5_movie, "Blue", this.playerTypes[4]);
this.startListener = this.startGame.bind(this);
this.titleMovie.start_btn.addEventListener("click", this.startListener);
this.titleMovie.stop();
}

spelchan.Pent.prototype.startGame = function(e) {
var numPlayers = 0;
for (var cntr = 0; cntr < 5; ++cntr) {
this.playerTypes[cntr] = this.triStates[cntr].getSelected();
if (this.playerTypes[cntr] > 1)
++numPlayers;
this.triStates[cntr].clearListeners();
}
if (numPlayers > 1) {
this.titleMovie.start_btn.removeEventListener("click", this.startListener);
this.stage.gotoAndStop("Game");
}
}

Final Touches


The Pent class updateGameUI now needs to be updated to handle the different types of players. In particular, we need to know if there really is a player which can be done by taking a look at the player type that was assigned to the player as a result of the tristate buttons. One of the options in the tri-state menu’s is “No player”. This is to allow a small number of players play against each other without having any computer opponents. This code should be added to the UI\_ROLL case:

if (this.playerTypes[player] > 2)
this.startRoll();
else if (this.playerTypes[player] < 2)
this.skip();


While I covered the skip button, originally I did not have this feature. It was after playing through the game I noticed that it would be nice to be able to skip a turn.  This is only a tiny bit of the fine tuning for the game. 

There is one more thing that should have been added to the game, but for some reason I never did implement before releasing. That is sound effects. This would be similar to the way it was done for the Video Poker game.

That is it for this game, next month I hope to have a post mortem of mj js13kGame entry (if I manage to finish one) and if I do finish, will spend a few months going over the various parts of the game. If I am lucky enough to be able to use some of my research in the game, I will go over the related research papers and how they aided me in this game.

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.