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.