Tuesday, December 15, 2020

Marvel's Avengers

 I was going to start a series on Making Queen's Challenge followed by a series of articles on the techniques for solving that problem but that will wait until next year. It has been a long time since I wrote a game review so decided after a long invigilation session to relax by writing a review for Marvel's Avengers. I also posted this on Steam.

I received Marvel Avenger’s for “free” with the purchase of my laptop so I can easily say that I got my money’s worth out of the game. While I occasionally will play the occasional shooter, I am not really a fan of action oriented games which is why I quickly gave up on  Final Fantasy XV and will likely never bother with the remake of Final Fantasy VII. Perhaps it is my infamiliarity with looter shooters that kept me playing this game despite mediocre reviews. There is a lot wrong with this game, but even though the cons outweigh the pros, I am giving this game a very marginal thumbs up but only because despite all the flaws I am still playing this game and somewhat enjoying it. Perhaps if I played the better loot shooters first or if I didn’t prefer solo gaming my opinion would differ. 

Pros

Marvel. While not as good as the Lego Marvel games, it still lets you be some of your favorite heroes.

Thor. My least favorite Avenger’s character is one of my favorite characters to play so they must be doing something right! 

Good mix of ranged and melee combat. The variety of attacks and booster powers makes combat, even when overwhelmed, fun to play.

Great graphics. 

Great campaign. While it is a bit too PC, the story was not what I was expecting so rather enjoyed the story line. 

Cons

RPG Elements are pointless as the game scales with your level. This also means that poor players can’t over-level in order to reduce the difficult of the game. That said, as the power level is based on your equipment, you may be able to cheat this flaw by putting on low-level equipment then switching in your good equipment once you have started the mission.

Easy isn’t. This could be my lack of reflexes, but more likely it is because the designers made this game for existing loot shooter players so expect a familiarity with that type of game. Hint to designers, if the player is already playing on the easiest level, DON’T PROMPT THEM TO TELL THEM YOU CAN CHANGE THE DIFFICULTY IF THE GAME IS TOO HARD! It would seem to me that most game companies don’t bother testing their games on people who are new to the genre. In the long term this is why genres die out because new players can’t play the games due to entry difficulty and older players get bored of them.

Timing puzzles. If it were up to me, all timing puzzles would be optional! This wasn’t overly bad until you get to the Kate DLC which requires you break a lock then shoot targets on either side of a cell within an exceedingly short time limit. It took me over half an hour to get by the 3 cells locked this way.

Poor navigation. Having a marker tell you that to get to your next objective requiring you to travel through a wall or mountain isn’t very helpful. Waypoints have existed in games for decades now so there is no reason you couldn’t  put a waypoint in the hard to see tunnel so you can find it and not waist time backtracking to find a path around the mountain.

Chimera sequences require you wander around the airship (or anthill) to complete some vaguely specified objective which wouldn’t be bad if the layout wasn’t so awful. Finding the harm room and interrogation room took way too long and were really pointless tasks as the harm room missions are accessed in the war room once you have found it. No clear directions also was frequent during missions. 

Chest nearby? This message came up frequently but most the time I couldn’t find them.

Ms Marvel as main character. Perhaps because I am not familiar with this character. Perhaps because I am old and male so can’t relate to being a fangirl. Escaping the park was by far the worst part of the campaign, which is sad because it is right near the beginning. Once you get other characters the game gets way better, even when you have to use her.

Multiplayer seems repetitive.  Thankfully you can play multiplayer solo by using bots. That said, the missions I went on were very similar to each other and to the campaign sequences. Yay I get to smash more reactors which spawn hoards of enemies!

Bots not good at multiplayer tasks. It might be more fun with other people rather than bots, but as I am poor at these games don’t want to inflict my bad gaming on other players. That said, when you have sequences that require human players spread out, the bots don’t quite seem to understand the objectives meaning when you have to control 3 points on the map things become annoying.

Mobs attacking are harder than boss fights! I get that combat is the point of this game, but this tends to be far too many enemies attacking you at the same time making most combat a test of reflexes rather than on strategy.

Black Widow Boss Fight was exception to above requiring me to exit the campaign and level up outside of the campaign before giving it another go and even then lost another dozen times before I was able to get through this awful fight. Granted, I was also unable to complete the Harm training so could be that I simply can’t use this character properly.

I am sure I missed a few points. 

TLDR: If you like Marvel heroes, this is fun but has a lot of flaws so wait until on sale.


Sunday, November 15, 2020

Porting a Game in Zero Hours

 


Way back in 2014, I participated in a game jam called the Zero Hour Game Jam, with the idea being that you had to write the game between 2AM and 2AM when the times switched for daylight savings time. This “missing” hour would we the time you wrote your game in. The rules were “there are no rules.” Wanting to keep the spirit of the Jam, I decided to do my coding during the jam, but allow myself to prepare artwork beforehand. While I did not have to do the same thing with the port, as the time switch was happening on November 1st and because there is talk of getting rid of daylight savings time (had something called Covid-19 happened, it is possible that there wouldn’t be daylight savings time in BC as voters overwhelmingly want to get rid of it as it serves no practical purpose but does pose medical problems to some people) so this may be the last chance I have to do a game in 0 hours. While there are Zero Hour jams running this year, I opted to do my own Zero Hour Port where I take my original Zero Hour game and re-write it in JavaScript so it will be playable since Flash is no more.

My rules for this port were simple. Pre-Jam preparations were allowed which simply consisted of using my old copy of Flash to create a sprite sheet of the assets in the original game and to convert the resulting JSON file from this sprite sheet that was generated into my own condensed format. A sample of my JSON format is shown below. I am also allowing myself to use my Vampire Attack game from last month as skeleton code to quickly hack out a working game.

ZHAtlas = {

    //"ContinueBtn": {"x":0,"y":0,"w":350,"h":82, "over_x":358,"over_y":0,"down_x":0,"down_y":90},
    "KeyABtn" : {"x":0,"y":0,"w":94,"h":84, "over_x":102,"over_y":0,"down_x":102,"down_y":0},
    "KeyDBtn" : {"x":204,"y":0,"w":94,"h":84, "over_x":306,"over_y":0,"down_x":306,"down_y":0},
    "KeyQBtn": {"x":510,"y":0,"w":94,"h":84, "over_x":612,"over_y":0,"down_x":612,"down_y":0},
    "KeySBtn": {"x":714,"y":0,"w":94,"h":84,"over_x":816,"over_y":0,"down_x":0,"down_y":0},
    "KeyWBtn": {"x":918,"y":0,"w":99,"h":84,"over_x":0,"over_y":92,"down_x":0,"down_y":92},
    "KeyEBtn": {"x":214,"y":92,"w":94,"h":84,"over_x":316,"over_y":92,"down_x":316,"down_y":92},
    "continueBtn": {"x":418,"y":92,"w":216,"h":50,"over_x":642,"over_y":92,"down_x":0,"down_y":184},
    "Zombie": {"x":448,"y":184,"w":546,"h":656},
    "backdrop": {"x":0,"y":848,"w":512,"h":256}
}

As you can see by looking at the above JSON code, objects in the games are named and given information about coordinates and size. Buttons also have additional coordinate information for their over and up states. The sprite sheet is too big to put here but is available in the git repository for those of you who wish to view it.

Once the missing hour started, I copy and paste code into a new project. The HTML boilerplate is pretty much unchanged just with some redundant code removed and the name of the game and script changed. The js file linked to from the html file really doesn’t need too much to be copied from Vampire Attack. First I paste in my JSON file and turn it into an object, that is the one thing I do love about JSON. Then I copy the button class and turn my ScreenHandler class into the game class. I am figuring that I can simply have the title screen and win/lose screens built into this one screen, which in hindsight was a bit more work than I expected.

Drawing the backdrop is trivial so for my rendering routine I simply draw the backdrop image. Then, which is not shown in my final code, I want to make sure the buttons are rendering properly so I render all of them using a for loop. The button class uses a callback mechanism so implementing the handler is trivial. For quick testing the handler just displays a console message but game logic will be going here shortly.

With the user interface implemented, it is time for the gameplay. This involves the zombie moving toward the player. Here is where I wish that more time was spent examining the original code for doing this. While writing scaling code is trivial, having the zombie at the center of the screen didn’t look very good as the zombie was floating in the air. The obvious quick fix for this problem is to shift down the y coordinate. This solves the footing problem, but as we are scaling at a constant rate the zombie appears to slow down as it starts reaching the player. Playing a few times it is a bit surreal but sort of like what you would experience as your adrenaline started kicking into overdrive so figured I would leave this alone unless there was time at the end to fix it.

Now clicking on a button adjusts the zombie distance, and when the zombie is far enough away we log a win. If the zombie gets too close we log a loss. The problem is that all the keys will let the player run, making the game too easy. This is easily fixed by picking one of the keys at random and only rendering/accepting that key. This meant that keyboard support had to be added which isn’t hard. The keyboard handler for key down looks at the key being pressed and checks to see the character is the same as the expected character and if so calls the player step method for moving the player. The button handler does likewise so we have both keyboard and mouse/touch suppprt.

    playerStep() {
        this.zscale -= .06;
        this.cur_key = Math.floor(Math.random()* 6);
        if (this.zscale < .1)
            this.nextScreen(); // note this was a console log earlier
    }

At this point I am thinking that I am almost done and will be able to add sound, music, and maybe even fix the zombie motion. First, however, we need title, win, and lose screens. Choices here are to cut an paste the screen handling code from Vampire Attack and have different screens. Or as there are just messages to display I could just do everything within the existing screen. Feeling the later would be faster, I opted to go for that approach. I don’t think it ended up being faster and added some ugly code to my project so was ultimately a poor decision.

To add a title screen, a showTitle flag was added. When set the title text gets shown. A playing flag determines if the game is in progress. If the game is not being played, a separate message (for winning, losing, and extra title text) will be shown. To handle the transition between the different states, a next screen method was written as shown below. This code gets called whenever a screen transition should occur which includes winning, losing, clicking to return to title, and starting the game from the title.

    nextScreen() {
        if (this.playing === true) { // game ended
            this.cur_key = 6;
            this.playing = false;
            if (this.zscale >= 1) {
                this.message = "You have died!";
            } else
                this.message = "You Escaped!!!";
        } else if (this.showTitle === true) {// game starting
            this.cur_key = Math.floor(Math.random()* 6);
            this.zscale = .5;
            this.showTitle = false;
            this.playing = true;
        } else { // return to title
            this.cur_key = 6;
            this.message = "Billy Spelchan's Zero Hour Game port";
            this.showTitle = true;
        }
    }

Once I finally finished the screen transition logic there was few minutes left but I was too tired to continue so the sound effects, which probably could have been added in only a couple of minutes, was dropped and I figured I would go to bed and put up the game when I woke up.  The code for this game is available in the git repository for those who are interested. Not sure what will be next month, but I do have an interesting multi-part series where I create a game then demonstrate different approaches to solving that game. If I have time to do a Christmas game (slim but non-zero) then I will do a postmortem on that game otherwise I will start the new series.

Thursday, October 15, 2020

Making Vampire Attack 2020

 Vampire Attack was a simple Halloween game I wrote in Flash a long time ago. This revision of the game is just like the original Flash version except that instead of having 13 waves of attacks, I opted for an infinite attack with the spawn rate of the bats slowly growing until it becomes overwhelming. The big concern I had with porting the game is that the current versions of my SLL library do not support rotation. This meant I had to decide between using a library like Create.js that does support rotating sprites, adding that feature to SLL, or writing the game from scratch. Obviously the third option would be insane, so that is what I opted to do. In my defense, that was not my original plan it is just what ended up happening.

My thoughts when starting on this game was to play around with rotation to see how it worked and if it was not going to be overly complicated then I would incorporate it into SLL but in the unlikely even if it was going to be overly complicated then use Create.js as a last resort as this Is a personal project so my NIH Syndrome is welcome here. I had never played with rotation but recalled reading about it in the canvas specifications. The problem with the WHATWG documentation (https://html.spec.whatwg.org/multipage/canvas.html ) is that trivial things, such as rotation as it turns out, tends to be made to sound far more complicated than they are and finding out trivial things, such as how to set the origin that the rotation revolves around, tend not to be trivial to find. Perhaps it is just me, but translate(x,y) is not the obvious way to change the origin of rotation. I suppose it does make sense in a mathematical sense, but I am a programmer because I am not great at mathematics (and all of the problems I am having in Graduate School are due to my lack of math skills). Needless to say I started playing around with trying to rotate a rectangle as shown in the code below. Really, not very difficult. You just set the translation to your origin point then set the rotation (in radians) and then draw the object relative to the new origin.

        ctx.save();
        ctx.fillStyle="#F00";
        ctx.fillRect(50,50,50,50);
        ctx.translate(75,75)
        ctx.rotate(Math.PI/4);
        ctx.fillStyle='#00F';
        ctx.fillRect(-25,-25,50,50);
        ctx.restore();

Once I figured out how to rotate a rectangle properly, I wanted to get the code working with  images. As I already knew that for the game’s graphics I was just going to use the Flash game’s assets that were exported as an image map so I just needed to modify the json code to use my own vastly condensed format. My older Halloween Liche port had the code to handle the format already so to speed up testing I just used the code from this. I quickly had bats on the screen and a rotating player. Implementing shooting and moving bats was not hard and the next thing I knew, there was a nearly finished game.

A bit of fine tuning and we have our Halloween game for this year completed. The source code is being added to my repository at https://github.com/BillySpelchan/BlazingPorts . Enjoy.

Wednesday, September 16, 2020

JS13K Postmortem

 Due to family matters, I simply did not have time to finish my entry for the JS13K competition. I am still, however, going to do a postmortem. 

What Went Right – Good Idea

The theme for the competition was 404. This is the Page Not Found error code for HTML pages. The idea I had was a stealth game where you need to find your way out of a procedurally generated multi-level dungeon while avoiding being detected by the 404 inhabitants. Ideally, assuming I could squeeze it into 13k, I would have used my ray caster to  have the game in 3D using a procedurally generated rock texture. The sprite for the enemy would be generated using simple drawing commands then made into a sprite sheet.

To make sure the game was completable I was going to write a utility to play through randomly generated levels keeping the seed to the levels that could be completed then use the seeds for level data. This is related to my thesis work as part of my work involves creating solvers for playing through procedurally generated platformer levels.

Mixed Blessing – Components Completed

Parts of the game had been completed before hearing the bad news about my father but because I never actually got to finish the game, I consider this to be mixed. Perhaps I will be able to use the code in a future project. Three things were completed before hearing the bad news. I had a random number generator (based off of PCG) so reseeding would be possible. This is necessary for JavaScript as there is no was of seeding the build-in random method. I had my map generator for generating the levels. And I had the texture generator for generating the rock texture and with parameters and color changes this would have even allowed for variety between the map levels.

What Went Wrong – Family Matters

Things have been totally nuts over the last few weeks. With bad news regarding my father, relatives have been visiting in case they don’t have another chance (a real possibility but as this is a private matter I will not go into details). Combined with having to drive to different cities to take my father to appointments and dealing with lawyers and accountants my spare time was next to none. Then the fridge died and needed replacing. So needless to say, the real world was not kind to me so the only choice I had was to cancel the game development which is sad as I think if I could have pulled off what I wanted to accomplish it would have been a great entry.

Not sure what I am going to do next month. Probably a post-mortem on porting my Vampire Attack game from Flash to JavaScript, but going over my texture generator is also a possibility.


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.

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!