Othello
Want to play the game and see the code, yourself? Click here: |
Disclaimer: This game was made when I was an undergraduate, so the code was written in Visual Studio 17. On top of this, the code was made using SDL which has to be installed to Visual Studio for it to build. The executable still works without SDL. I have also learned a lot since this project, so it shows my early knowledge of C++ and code organization.
Focus of the Project
The main focus of this project was to study and implement a Min-Maxing AI which could play a simple board game with the player. This project was provided to me as an assignment in my AI class in undergrad, and I decided to use the engine I made for Grassbot to provide the visual elements and take it as a chance to get more practice in C++ and SDL.
How does it work?
The game loop:
The gameplay:
The AI:
The Debugger:
The visuals:
- The main loop of the game involves handling events of the game, updating the game's visuals, rendering said visuals, and then loading what state each tile of the board is in.
The gameplay:
- To start off, the entire board is one large button. Where the player clicks is mathematically calculated to be correlated with a tile, and then the code determines if that position of the board is a spot where a token can be placed.
- When a token is placed, the code checks all 8 directions (up, up-left, up-right, right, down-right, down, down-left, and left). The code goes along each entry in the matrix (the board) checking what is on the tile it just hit. If it hit an enemy token, then it continues to traverse the tiles. If it hits an ally token, then it goes back along the distance it traveled and replaces each token on the tiles it traversed with an ally token. If it hits an empty tile, it stops searching that direction and checks the next direction. It also cancels the search if it hits any of the bounds of the board.
- When a round starts, the game checks every tile on the board for an ally token. When it finds an ally token, it performs a similar function to the placement of a token in that it traverse all 8 directions radiating from the ally token for tiles that can have tokens placed on it. In this case however, it only sets a spot to available if it hits an empty tile at the end of its enemy token traversal and cancels if it hits an ally token at the end of its traversal. It also cancels if it hits the bounds of the board.
- Whenever the game determines there were no available tile left to play tokens on, the game is wrapped up as a game over. In this wrap up, the entire board is traversed and each token is counted towards the two player's score. The player with the higher score is determined from this and they are shown to be the victor in the visuals.
- The only difference between "two-player" mode and the "vs AI" mode is that in "two-player" mode, the AI's function isn't called on the second player's turn. This gives the availability for the second player to make the move.
- In Othello, the black tokens player always goes first. In the "vs AI" mode, the AI's alignment is randomized to determine whether they play as Black or White.
The AI:
- The AI takes in how many "layers" deep it can see. These layers are the current and future board layouts based on decisions made by the player and the AI. If the AI can see two layers deep, then it can see the current turn and one turn into the future based on what choice of a move it makes. Four would mean the current turn and three further future turns based on both its own decisions and the player's decisions.
- Easy AI:
- Can see two layers deep. Only worries about the number of tokens it and the player can earn from future moves.
- Hard AI:
- Can see four layers deep. Only worries about the number of tokens it and the player can earn from future moves.
- Can see four layers deep. Only worries about the number of tokens it and the player can earn from future moves.
- Impossible AI:
- Can see five layers deep. Now has knowledge of a "score" that comes with each tile on the board. Will try to maximize the score from choosing the positions on the board with the highest scores along with how many tokens it earns/loses from each choice.
- At the beginning of its turn, the board's visuals are reloaded so as the player can see that the AI is currently "thinking" and the games hasn't frozen. However, the yellow availability indicators aren't drawn so that the player doesn't think they can still click and take a turn.
- The AI then checks if it is Black or White alignment.
- It then proceeds to check what would happen by placing tokens on the board at each possible position while it "thinks". Over the course of placing pieces on the board and checking how many points both it and the player could earn from each possible choice, it prunes branches of the future off based on if that branch can't produce a better score than other future branches using the Min-Maxing algorithm with Alpha-Beta pruning.
The Debugger:
- The debugger is turned on when the player clicks the Debug button. This prompts the player if they wish to Save the next game played (a Hard game against an AI) or Load the last saved game. (Type y/n into the command prompt to do choose when asked to do so.) On the Save mode, the new game starts, and then all moves decisions made by the player and the AI are recorded in the Save.txt. On Load mode, the Save.txt file is read over the course of a new game. Whenever the player clicks on the board, the file's read in data is translated into a move made by the player or the AI and is reenacted using the same code as a normal game would. This gives the effect of replaying an entire game.
- The debugger also indicates what score each possible move the AI has available to it on its current turn. This debugging was useful when trying to discern what the AI figured out and how it decided which move was best for it.
The visuals:
- All tiles on the board are set to a specific texture (an empty tile, a tile with a white token, a tile with a black token, or a tile with a yellow indicator) every time the board's "DrawBoard" function is called. This is called whenever a player's turn starts.
- On a game over, the victory banner on the right side of the board has its texture set to that of the victor's color.
How to play:
Open this folder in the downloaded Othello folder.
The executable for the game is located here.
Controls:
- The game is entirely mouse controls.
- Click on the yellow circles to place your token.
- To start a 2-player game, hit the "Start" button. To start a game against the AI, press one of the difficulty buttons to choose the difficulty of the AI in the game.
- Clicking Debug will start a game against the AI in hard mode, and whenever the AI makes a move, the command line will print out the scores that the AI discerned were possible from the options it had on its turn.
Treasure Generator
Want to play the game and see the code, yourself? Click here: |
Disclaimer: This game was made when I was an undergraduate, so the code was written in Visual Studio 17. On top of this, the code was made using SDL which has to be installed to Visual Studio for it to build. The executable still works without SDL. I have also learned a lot since this project, so it shows my early knowledge of C++ and code organization.
Focus of the Project
This project was a deep dive into some of the base mechanics often seen in Role-Playing Games: equipment, stats, combat, loot, stores, currency, and lastly game saving. This project initially started off a just a text generator which would write out random loot the player had "earned". However, I eventually decided to upgrade it to a full mini-game that used the loot generator that was built initially. This project was a fun chance to learn about equipment management, saving of complex inventory, and randomization of both loot and enemies.
How does it work?
The game loop:
The loot generator:
The player character:
The enemies:
The areas:
The combat:
The store:
Saving the game:
The visuals:
- The main loop of the game involves handling events of the game, updating the game's visuals, and then rendering said visuals.
- The game has a bool for checking if the player is currently in combat. If the player is and it's the enemies turn, then the enemy will perform its attack in the game loop.
The loot generator:
- The loot generator first receives an integer value to indicate if should produce a random amount of gold, a weapon, a helmet, a chestplate, a pair of gloves, a pair of boots, a rune, or a gem.
- Gold is just used as currency in the store, and comes in a random amount between 1 and 100 pieces when earned by the player.
- Gems are a rare drop by the generator, and their main purpose is to just give the player more than 100 pieces of gold. They come in five forms, each with increasing amount of gold worth. Lastly, a gem has a 5% chance of being "Perfect", which doubles its worth.
- Weapons can come in many forms, from a sword to a bow, but this is just flavor text. The material, however, has an actual affect on the value of the weapon. Starting off at wood and going all the way up to diamond, each tier of material is harder to achieve in the random generation of the weapon, and each tier has a higher possible range of attack stat. On top of this, there is an uncommon chance for a weapon to also receive an elemental bonus, which are explained the the "Player Character" section. The player can only have one weapon at a time.
- Each tier of material increases the potential attack stat range by 20.
- Armor comes in four forms and only one of each type can be equipped at any time. Like weapons, each piece of armor has a material type (from leather to diamond), and these material types work similar to how the weapon's materials work except these have a defense stat instead of an attack stat.
- The rune is inscribed with a single elemental bonus which will affect your stats. (Earth elemental bonus only works on equipment, so there is no Earth elemental rune.)
The player character:
- The hero character is a header file which consists of:
- A private struct for holding all the info needed for equipment.
- Private function for adding on the effects of all the elements in the game.
- Public functions for recalculating the hero's stats.
- Public stat variables.
- A public array of equipment. This array consists of the player's weapon, helmet, chestplate, gloves, boots, and rune in that order. The rune gives the player a free elemental bonus, while the rest of the equipment gives the player attack and defense stats.
- The elements elements of the game that can effect the player consist of:
- Water, which gives the player 50 more health.
- Earth, which increases the stat of the equipment that has it.
- Fire, which increases the player's attack stat by x1.2's.
- Wind, which increases the player's defense stat by x1.2's.
- Lightning, which gives the player 10 more speed.
- What do the stats themselves, do?
- Health is the amount of HP the player has before they lose in combat.
- Attack is how much damage the player does to an enemy when they attack (before being subtracted by enemy defense).
- Defense is how much the damage to the player is reduced by.
- Speed is how many times the player can attack before the enemy attacks.
The enemies:
- Similar to the hero character, the enemies consist of the same stats (Health, Attack, Defense, and Speed). However, the enemies also have a level stat. This is to determine between whether they are a level 1 (blue), level 2 (green), or level 3 (red) slime. Once the level has been determined in the enemy initialization, then the enemy's stats are initialized. The level stat is used to determine what range the slime's stats can be between.
- The enemy is initialized every time the player enters the colosseum, and all of its stats are randomized upon its initialization along with its sprite. The level for every fight except for the first one is completely random, so there is no linear power scaling. The player could win or lose any fight.
- The higher level a slime is, the more loot it gives upon being defeated if the player wins.
The areas:
- Every area on the main map is a button to be clicked. When the button is clicked, the change to the new area consists of moving visual elements and buttons off and on the screen. For instance, if you click on the store: All of the buildings' buttons and the forest background are moved offscreen while the store's buttons and background are moved onto the screen.
- There is a function that performs all in the "Game.cpp" file. This function takes in an integer to determine which are the correct elements to move around.
The combat:
- The combat of the game consists of two phases: player's attack and enemy's attack. While the player has speed left, they can continue to click on the enemy (which is a button) to damage the enemy. Once the player is out of speed, the enemy will proceed to retaliate with its own attacks in the game loop automatically.
- Once combat is completed, the loot generator is called (enemy level * 2) times with a passed in random integer value to determine what loot the player will earn. (If it is a weapon, a helmet, a chestplate, gloves, boots, a rune, gold, or a gem.)
The store:
- Clicking any of the buttons in the store first checks if the player has enough gold to afford that item. It then subtracts that amount of money and calls the loot generator by passing in a specified value for which piece of equipment the player should receive. This helps if the player is in need of a specific piece of equipment they wish to get a better version of.
- Every piece of equipment costs 100 gold for simplicity's sake.
Saving the game:
- The saving of the game simply consists of saving off all the details from the equipment (the name, attack stat, defense stat, and elemental bonus) along with the amount of currency the player has to the "Save.txt" file. The values can easily be read and altered in the text file for testing.
- All equipment and money is loaded upon the opening of the game.
The visuals:
- All 2D sprites used in the game are put into the game upon initialization. However, all sprites are loaded off screen except for the sprites used in the main menu, giving the sense that there are only the main menu elements on screen. Visuals are moved on and off screen throughout the game to provide the changing menus.
How to play:
Open this folder in the downloaded Treasure Generator folder.
The executable for the game is located here.
Controls:
- All input in the game is done through clicking with the mouse.
- Click on the buildings in the main screen to enter that area.
- Colosseum - Fight different slimes to try and win loot. Click on the slime to attack it and watch how it affects the combatants' health!
- Store - Decide between pieces of equipment to buy if you have the money. Equipment type is chosen by you, but the values of that equipment are randomized.
- Church - Save all of the equipment you've earned in the game or reset all of your equipment to start the game anew.
- When loot pops up on screen, you can choose to accept or reject pieces of equipment and runes, but accepting an equipment/rune will replace the one you currently have equipped. You can't get an equipment/rune back once you've gotten rid of it, so be careful!
Grassbot
Want to play the game and see the code, yourself? Click here: |
Disclaimer: This game was made when I was an undergraduate, so the code was written in Visual Studio 17. On top of this, the code was made using SDL which has to be installed to Visual Studio for it to build. The executable still works without SDL. I have also learned a lot since this project, so it shows my early knowledge of C++ and code organization.
Focus of the Project
The main focus of this project was to experiment with menus, saving of game data, and the concept of unlocking levels. Like with all other games made for my FIEA application portfolio, this game was also a very good chance to learn about C++, the handling of 2D graphics, and simple game concepts like the main game loop. The small engine I created for this game was eventually used in both my Treasure Generator and Othello projects.
How does it work?
The game loop:
The levels:
Saving the game:
The gameplay:
The visuals:
- The main loop of the game involves handling events of the game, updating the game's visuals, and then rendering said visuals.
The levels:
- The game holds a 10 x 13 matrix which holds integer values as to what type each tile in the game is. 1 means water, 2 means grass, and 3 means dirt.
- The levels are loaded in through the reading of .txt files. These files hold a 10 x 13 matrix of numbers to represent what will be on the map for the initialization of the level. To discern between the different levels and to decide which level should be loaded next, each .txt file is named "level_" with the blank being replaced with a number to represent the level number.
- The game holds an integer which determines what level the player is currently on.
- The locking and unlocking of levels is handled by an integer which indicates what is the latest level the player has unlocked.
- This "Highest Unlocked Level" integer is saved every time the player beats the highest level unlocked.
- Hitting the "Reset" button changes this integer value to 1.
Saving the game:
- Game saving occurs every time the player beats the latest level to be unlocked.
- The only value saved to the "Save.txt." is the "Highest Unlocked Level" integer. This value is checked every time the game is opened.
The gameplay:
- When it comes to checking if a level has been completed, every "level_.txt" comes with a number at the end which indicates as to how many squares that the player has to fill with grass to beat said level.
- Whenever the player presses any of the WASD keys, the game checks the tile next to the player in the respective direction of the key press.
- If the tile is water, the game does nothing which prevents the player from moving.
- If the tile is dirt, the game changes that tile to 2 to indicate it is now grass, and then increments the counter which keeps track of how many grass tiles there are. If the grass tiles equal the total number of walkable tiles indicated in the .txt file, then the game immediately loads the next level of the game. If the next level is higher than the highest unlocked level, then the integer for the highest unlocked level is also incremented.
- If the tile is grass, the level is reloaded entirely and the player is forced to start back at the beginning, indicating they failed when stepping on said tile.
- The game also has a bool in KeyControl.h (the main file for handling key input) which determines whether or not the player is able to move in that moment. This bool is switched to false when the menus are up to prevent the player from moving around in the game when they should not be able to.
The visuals:
- All 2D sprites used in the game are put into the game upon initialization. However, all sprites are loaded off screen except for the sprites used in the main menu, giving the sense that there are only the main menu elements on screen. Visuals are moved on and off screen throughout the game to provide the changing menus.
- The sprites for the tiles on the map are loaded in "Map.cpp". It uses the numbered matrix to determine which tile should be drawn as which ground type.
How to play:
Open this folder in the downloaded Grassbot folder.
The executable for the game is located here.
Controls:
- All buttons in the menus are clickable by the mouse.
- In the game levels, used WASD to move between tiles. Fill all the tiles with grass without stepping over a grass tile!