Week 8 – A Couple Little Improvements, And A Pretty Big One
April 2, 2023
Welcome to my eighth Senior Project blog! At this point I’m working on wrapping up the final stages of my project, as well as trying to make improvements to existing parts of it. As a result, I spent some time this week working on slides for my final presentation, in addition to work on my project. In this blog post, I’ll skip over the progress I made on my presentation (it was just working on slides), and instead go over some of the improvements I made to the actual game.
Level One
In an earlier blog post, I had mentioned that I plan on having nine difficulty levels. Level one would have the computer making random moves. I decided against this, as I didn’t really want to have a difficulty where the computer was making random moves without reason. When looking only one move ahead, the computer isn’t making the best moves, and is often very predictable. However, even here, the moves being made aren’t random, and learning to predict what even an unskilled computer is going to do is a skill that shows a player’s improvement. Playing against a computer making random moves doesn’t help the user get better, and I didn’t see a reason to include that difficulty. With random moves not being included, the levels will go from 1 to 8, with the computer (at level x) looking x/2 moves ahead, rounded up. Additionally, at even numbered levels, the computer will use an improved formula for calculating the board score when determining what the best moves to play are.
Board Score
My original board score would be calculated by subtracting the red pieces from the black pieces. However, every other level would use an improved formula to calculate score, which counted a king as two pieces. Playing through a few games, I felt that two pieces was slightly too low, and instead changed the value of a king to be 2.5 pieces. The following example will help show my reasoning for this.
It’s red’s turn to move, and the two green highlighted pieces are the two being considered in this scenario. Currently, there are 12 red pieces, and 11 black pieces, so the board score is +1. If the red piece on the fourth row captures the black piece, the score will become +2, the same as if the piece on the seventh row becomes a king. Since the entire point of giving kings the value of two pieces was to prioritize getting a piece to be a king, I decided to let them be worth 2.5 pieces so that they are actually prioritized. With the new scoring, capturing the black piece results in a score of +2, while turning the seventh row red piece into a king results in a score of +2.5.
However there are some exceptions to this. When there aren’t many pieces left on the board, having two regular pieces might become more important than having a king, as later on in the game having more pieces starts to become more important than having fewer but better pieces. The opposite is also true at the start of the game, when losing one piece isn’t as big of a deal when there are many more still on the board, with kings being more valuable at this point. To account for the stage of the game, I decided to let kings be worth 2 pieces when either player has fewer than four pieces left, and have them be worth 3 pieces when both players have more than nine pieces. All other times, kings are worth 2.5 regular pieces.
As for why kingMultiplier is 1, 1.5, and 2, rather than 2, 2.5, and 3, kings are also counted along with regular pieces, so the kingMultiplier is added in addition to the original score of 1 for a regular piece on the board.
Keeping Track Of Games
Last week, I added a way for me to keep track of wins and losses. Rather than storing the number of total wins and total losses, along with the results of the last 5 games, I decided to instead store every game’s result, and read the string of W and L to determine the total number of wins and losses. After playing a few games, however, I earlier this week that it wasn’t actually saving the results. As long as the game was running, the wins and losses added up correctly, but after closing and reopening the game, there was only one game saved. After looking through my code for a bit, I realized that this was due to me writing to the file after every game. Writing to the file didn’t just write to the file, however, but instead overwrote the existing file. What I should have done, was append to the existing file, so that new results get added to existing ones, rather than replacing them.
There was another issue that I would soon encounter if I wanted to continue storing the results of games this way. I also needed a way to track the difficulty level and store that. My original idea was to store this along with the number of wins and losses, but since I decided to store wins and losses differently, this is no longer an option. I could also continue to store wins and losses the way I currently am, and instead also store the difficulty level at the end of the file, overwriting the file sometimes but not often, or storing the difficulty in a different text file so that the two don’t interact. However, I wanted a way to be able to keep adding to a file, without removing anything (the reason I changed the way I would store wins and losses in the first place), so after talking with my external advisor, I decided to add the difficulty changes to the text file just like wins and losses.
Example Difficulty Tracking
WLWWWIWWWLWILWWLWWILLLLLDWW. That’s what the text file may look like after 23 games played. The user has won 14 times, lost 9 times, and is currently playing at a difficulty level of 3. The W and L in the string should look familiar from the way I’m tracking wins and losses, but there’s also 3 I’s and 1 D in this string. These are how I will be representing increases and decreases in difficulty level. To explain how the difficulty will be increased and decreased, I’ll go through the example string, bolding the parts that I am currently explaining.
WLWWWIWWWLWILWWLWWILLLLLDWW. The user’s first five games are played at difficulty level 1. They won 4 of 5 games played, resulting in the difficulty being increased.
WLWWWIWWWLWILWWLWWILLLLLDWW. The bolded I represents that the difficulty level was increased.
WLWWWIWWWLWILWWLWWILLLLLDWW. The user is now playing at difficulty level 2, where they again won 4 out of 5 games. Once again, the difficulty level was increased, to 3 this time. WLWWWIWWWLWILWWLWWILLLLLDWW.
WLWWWIWWWLWILWWLWWILLLLLDWW. Here, there are six games, rather than five like the previous two examples. After five games, the user has won three and lost two. Since I am not changing the difficulty level if the user wins 2 or 3 of their last five, there was no change here. After the sixth game, however, the last five games played include 4 wins and a loss, since the first loss isn’t included as part of their last five games. As a result, the difficulty is increased to 4. WLWWWIWWWLWILWWLWWILLLLLDWW.
WLWWWIWWWLWILWWLWWILLLLLDWW. At difficulty level 4, the user loses all five games. As a result, the difficulty is decreased back to 3 (WLWWWIWWWLWILWWLWWILLLLLDWW), where they then go on to win their next two games (WLWWWIWWWLWILWWLWWILLLLLDWW). Since they just got back to difficulty level 3, I won’t be counting their previous games at this level, and will instead wait for the user to finish at least 5 more games at this level.
Loading In Saved Data
WLWWWIWWWLWILWWLWWILLLLLDWW isn’t the easiest string for a person to look through and understand, but a computer can look at it nearly instantly. Python has a count() method that count the number of times a character appears in a string. The following section of code is used to load in the total number of wins and losses every time the game is played.
In this section of code, f is the file that is being read, stats.txt. The string record, reads the single-line file in order for the next two lines of code to be able to quickly determine the number of wins and losses. The difficulty is calculated the same way. Python’s count method is used to determine the number of times ‘I’ or ‘D’ appear in the file. With a starting difficulty of 1, this can then be used to quickly determine the difficulty level that the user should be playing at.