1. A description of the design of your classes. We know what the public interfaces are, but what about your implementations: What are the major data structures that you use? What private functions did you define for what purpose? I defined no private member functions in my code, and the only external auxiliary function wrote was to aid in my SmartPlayer’s chooseMove() function. For each class, the private data members are as follows: Board: This contains an integer with the number of beans in each hole at the start, an integer with the number of holes per side (not counting the pots), and two vectors each representing a row of m_nHoles and pot at hole 0 (one is labeled north and the other is south). The issue of representing the board with South’s hole next to hole 6 and North’s hole next to zero is handled entirely by my sow() algorithm. Game: Game contains a pointer to the two players that will be playing the game, the board that the game will be played on, and a Side data member that keeps track of who is taking the current turn, so that move() can iterate between the two sides at the end of each turn. Player: Every derived Player class has a string of the name inside their base Player base class. SmartPlayer, BadPlayer, and HumanPlayer: These derived classes contain no data-members, but some different virtual functions that allow HumanPlayer to be interactive, badPlayer to make random moves, and SmartPlayer to choose good moves. 2. A description of your design for SmartPlayer::chooseMove, including what heuristics you used to evaluate board positions. My heuristics for choosing a “good move” weights any good path as the difference between the number of holes in the SmartPlayers pot and the holes in the opponent’s pot. The greater the difference in favor of SmartPlayer, the better the path. If a path results in SmartPlayer winning, it is weighted as basically infinite, and in a loss, as negative infinity. Basically, this just means that if no path results in either player winner, SmartPlayer will choose the path that likely leads to the most beans added to its own pot in comparison to the other player. If someone wins in the path, SmartPlayer will avoid paths at all times that result in opponents winning and prioritize paths that result in SmartPlayer winning. My program keeps track of whose turn it would be at the time of each branching move possibility, and if that branch choice is up to the other player, it chooses the worst branch possible for the SmartPlayer, in order to assume optimal opponent performance. Pseudocode for SmartPlayer::chooseMove() if the game is over, return -1; decide best move on current board through SmartChoose function(pseudocode below) if SmartChoose chose illegally // (hasn’t ever been true but for protection in worst case) choose a random legal hole return hole choice; Pseudocode for void function: (will refer to the player whose turn it is as Smarty) NOTE* (smartChoose-1 denotes calling smartChoose with depthTrack-1) smartChoose(Board& b, Side p, bool myTurn, int& bestHole, int& value, int depthTrack) { if the game is over { bestHole is -1 if Smarty wins value is set as very large if the game is a tie value is set to 0 if the opponent wins value is set to very negative exit this recursive call; } if program has reached the end of its set recursion limit (tracked by depthTrack) { bestHole is set to -1 value is set to (beans in Smarty’s pot) – (beans in opponent’s pot) exit this recursive call; } otherwise { make a copy of the board called by the function for every hole on the side of the player whose turn it is(that isn’t empty) sow the beans from that hole and capture any beans you would capture if the last hole was a pot call smartChoose-1 on this board, as the same player’s turn else call smartChoose-1 on this board as the next player’s turn if its Smarty’s turn and value from the call is biggest yet set that value to the output value set the returned hole to the output hole else, if the value from the smartChoose call is smallest yet set that value to the output value set the returned hole to the output hole return; } } 3. Pseudocode for non-trivial algorithms. void Game::status(bool& over, bool& hasWinner, Side& winner) { if both sides still have beans in play the game is not over else the game is over if north has more beans in the pot north is the winner else if south has more beans in its pot south is the winner else hasWinner is false (tie) } bool Game::move() { if there are no beans in play on either side return false display the board have the player whose turn it is choose their move in their individual manner sow the chosen space if the last hole sown was previously empty and is parallel to a hole with beans capture those beans if the last hole sown was the players pot while the last space sown is not a pot { have the player choose another space if the player cannot choose because the game is over break sow that space if the last space sown was previously empty and beans can be captured capture those beans } if the game is over display the game over message put all the beans on the board into their respective owner’s pot show the final score board else tell the player a turn has been taken, make it the next players turn } void Game::play() { while the game isn’t over { make the use press enter move() //functions as shown above, makes a full move check if the game is over } if the game has a winner display the win message with winning player’s name else display the tie-game message } int HumanPlayer::chooseMove(const Board& b, Side s) const { if the game is over return -1 prompt the user for a desired space while the space isn’t a valid choice prompt the user for a valid choice return the valid input number; } BadPlayer::chooseMove(const Board& b, Side s) const { if the game is over return -1; generate a random number based on the time choose an integer that is within the limits of the board while the integer chosen indicates an empty hole randomly choose another integer within those limits return the valid choice integer; } Board::sow(Side s, int hole, Side& endSide, int& endHole) { if the hole choice is invalid return false //because the board is asymmetrical in regards to the numbering of the holes relative //to the placement of the pot (6 is next to 0 for south, 1 is next to 0 for north), I have //two different algorithm depending on which side is making a move. all beans are //sown counter-clockwise. if the player is the north player “pick up” the beans from the hole while there are beans left to sow { if you reach the end of north’s side (past the pot) { go to the start of south’s side while there are beans left to sow { if you reach the end of south’s side go to the start of north’s side put one bean in the next space (south’s side) if you run out of beans endSide is south endHole is the last hole sown } put a bean in the next space on north’s side if you run out of beans the endSide is north endHole is the last hole sown } else, the player is South { “pick up” the beans from the chosen hole while there are beans left to sow { look at the next space on the board if you reach the end of south’s board { place the next bean in the pot, if any beans left if the bean placed in the pot was the last one the endSide is south the endHole is the pot go to north’s side while there are beans left to sow { look at the next space on the board if you reach the end of north’s side go back to the start of south’s side put down a bean in this hole if you ran out of beans the endSide is north the endHole is the last hole sown } } put the next bean in the next space on south’s side if you run out of beans endSide is South endHole is the last hole sown } } return true; } 4. A note about any known bugs, serious inefficiencies, or notable problems you had. I had quite a time programming this assignment. My most serious problems were getting a sow algorithm that allowed for looping around the board infinitely until you ran out of beans to sow that worked for each side- I tried for a long time to get the same algorithm to work for both sides, but it was much easier code to read and much more efficient to code each slightly differently. My second big problem was, of course, SmartPlayer. It took me a long time to sort out exactly how to track and limit the recursion, and the concept of weighting the other player’s moves differently to the SmartPlayers moves in the possible game-tree was confusing to me. After hours of tinkering I finally figured out a way of making sure the code always knows who is the player calling the function so it can properly weight the choices and assume the other player always makes the best move. I have not noticed any bugs with my project as of yet. My SmartPlayer always wins against my pure random BadPlayer, and I certainly have never beaten it. I’ve run games against my BadPlayer about 50 times with my final build, and since the player makes purely random moves each time was different, but SmartPlayer has yet to try and make an illegal move (I inserted a cerr message that alerts me when SmartPlayer randomly chose a legal move because the smartMove function returned an illegal one) I have noticed that when two SmartPlayers play each other, the one that moves first always wins by a lot. However, when playing against a BadPlayer from either side, SmartPlayer wins by a lot every time. 5. Test Cases! Many of my test cases were provided by the spec, or were variations on the cases provided by the spec. Specifically, void doGameTests(), void doPlayerTests and void doBoardTests() provided some very helpful cases. doGameTests() tests that BadPlayer will make a legal move, that the sow function knows what to do, that the game knows when to capture the other players beans, that the moveToPot() board function works correctly, that move() will put all the beans on the board in the right pot at the end of the game, and that the game function status() works and can tell you which side won. doBoardTests() tests that boards are constructed correctly given the right input, that the setBeans() and moveToPot() functions work correctly, and that sow() works correctly doPlayerTests() tests that HumanPlayers return true for isInteractive(), and that all other types return false, that name() works correctly, that Players can play in a game and make a valid move on a board. The following main contains many other test cases int main() { //test sow Board test10(6, 16); cout << endl << endl << "NOW SOWING NORTH 3" << endl << endl; Side resultSide; int resultHole; test10.sow(SOUTH, 5, resultSide, resultHole); cout << "Resulting side: " << resultSide << endl; cout << "Resulting hole: " << resultHole << endl; //test beansInPlay, just a bunch of bean counting functions Board test11(6, 4); cout << test11.beans(NORTH, 4) << endl << endl; cout << test11.beansInPlay(NORTH) << endl; cout << test11.beansInPlay(SOUTH) << endl; cout << endl << test11.totalBeans(); //Testing player and chooseMove() Side end2 = NORTH; int end3 = 0; Board test12(7, 4); test12.sow(SOUTH, 3, end2, end3); cout << end2 << end3 << endl; cout << test12.holes(); HumanPlayer test2("Rob"); test2.chooseMove(test12, SOUTH); //testing a normal game between two bad players Player* test3 = new BadPlayer("Bart"); Player* test4 = new BadPlayer("Homer"); Board testBoard(6, 4); Game testGame(testBoard, test3, test4); testGame.play(); //testing a normal game between two human players Player* testa = new BadPlayer("Bart"); Player* testb = new BadPlayer("Homer"); Board testBoard(6, 4); Game testGame(testBoard, testa, testb); testGame.play(); //testing a normal game across player types Player* testC = new BadPlayer("Bart"); Player* testD = new HumanPlayer("Homer"); Board testBoard(6, 4); Game testGame(testBoard, testC, testD); testGame.play(); Player* testE = new HumanPlayer("Bart"); Player* testF = new SmartPlayer("Homer"); Board testBoard(6, 4); Game testGame(testBoard, testE, testF); testGame.play(); //testing a small board for tie Player* test5 = new BadPlayer("Bart"); Player* test6 = new BadPlayer("Homer"); Board testBoard2(1, 1); Game testGame2(testBoard2, test5, test6); testGame2.play(); //test cross-Player class copy constructers Player* temp = new BadPlayer("GEORGE"); Player* tation = new SmartPlayer("BADNNSKNDF"); *temp = *tation; SmartPlayer test7("Bart"); BadPlayer test8("Homer"); //test to see if smartPlayer wins (can be looped, since BadPlayer is completely //randomized) Board testBoard3(6, 4); Game testGame1(testBoard3, &test7, &test8); testGame1.play(); }