704256376

advertisement
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();
}
Download