Saint Louis University |
Computer Science 362
|
Dept. of Math & Computer Science |
For this assignment, you must work alone. Please make sure you adhere to the policies on academic integrity in this regard.
You will be developing a player for the game of Pente, using the Python software framework that we are providing. (If you are unfamiliar with the Python programming language, please see me for assistance).
The rest of this document will provide guidance and details about the project. But at the onset, please note that all of your code must be in a single source file, such as robot.py (we will provide you with a template to get started). You are not to make any changes to the rest of our framework.
Also, in addition to submitting the .py file, you must also submit a text "readme" document that provides a choherent overview of all design and algorithmic decisions that you made, and a summary of the components of your Python code.
Your final grade on this project will be based on an evaluation of your source code, your summary document, and on the performance of your player in a Pente tournament against all of the other players!
The game of Pente is somewhat like a simplified version of go. Players alternate turns, playing one piece of their own color per turn onto grid intersection points. Black will go first and must play in the center of the board (labeled as j10 in our coordinate system). As a special rule (to mitigate black's advantage), black's second move must be at least three rows and three columns away from the center of the board (the area marked by the small circles on the game board).
A player may capture a pair of the opponent's pieces by placing a piece that causes precisely that pair to be surrounded with the opposite color along any direction. That is, if there is a configuration WBB_ then white may capture the two black pieces by placing a white piece in the fourth spot of that sequence. (Note, however, that if there is a configuration WB_W, black may safely place a piece in the third spot of that sequence without being captured.)
A player wins the game either by getting a configuration of 5-in-a-row somewhere on the board, or by capturing a total of 5 pairs of the opponents pieces (hence the name Pente).
A more detailed summary of the rules can be found at http://www.pente.net/instructions.html.
Our software uses a networked client/server model for communication between the game control, two players, and any number of additional observers. If firewalls allow, games could be played across the internet, but we will typically assume that all processes are running on the same machine (that being 'localhost' by default).
Our entire Python code base can be copied from turing:/Public/goldwasser/362/games/.
To begin a game, you must start the game server with syntax
This software uses port 9000 by default. However, you may choose an alternate port by giving an optional commandline argument such aspython gameServer.py pente
Once the server has begun, we need two players in order to start a game. Players are numbered as "player 0" and "player 1". The software is designed so that you can play human-vs-human, human-vs-robot, or robot-vs-robot. A human may take a seat at the game by running the following client program:
By default, it will look for the game server on the localhost at port 9000, but command line option can be used to change to another port (or IP address). The -v flag visualizes the game with a GUI. (The default, without that flag, is a text-based display and entry.) Also, the software will attempt to connect as player 0 by default, but seat #1 can be indicated with the argumentpython human.py -v
A robot client may also take a seat at a game. You will eventually be writing your own robot, but we offer several samples (discussed below). All will inherit a similar startup interface. A command such as
has a robot join the game, based on the logic expressed in the robot5.py sample file. Similar to the human client, the robot will attempt to find the server at port 9000 of the localhost and to join the game as player 0; these options can be controlled by commandline arguments. Robots also have a fixed time limit per move of the game (humans have the luxury of unlimited time). By default, it will give 30 seconds per move to the robot; that value can be changed with the -T commandline argument.python robot5.py
Finally, if you are having a game played between two robots, and you want to be a spectator to see the game played graphically (rather than monitor the text dump of the game board output by the server), you may start the program
python kibitz.py -v all
Information about usage and commandline options for all of the above programs can be found by providing a -h flag as a commandline argument.
Before we go further in describing the project, we want to emphasize that there is great risk of having out-of-control processes tracing game trees and using up both CPU and memory resources on our computing system. We ask that you be respectful and diligent in your use of our system, and do not leave any robots running unless you are personally monitoring their actions.
Note as well that because both our client and our server programs are multithreaded, you may find it a bit more difficult to cleanly kill a program. Options to try (from simplest to more complex) are:
A note about network ports: The operating system manages the network ports on a machine. If servers and clients connect using a particular port (such as 9000), and the program exit (or are forced to exit), the operating system might not immediately return that port to the pool of available ports. So when quitting and restarting, you may find that you are unable to restart the server on the same port. This will also be the case if there is contention because several students are logged into the same machine (such as turing when using this software). In that case, you can use the -p option for all of the programs to pick some other available port.
Coordinate System:
Externally, the client and server use strings to describe locations on
the board, with columns labeled a through s starting
at the left, and rows labeled 1 through 19, from
bottom-to-top; for example the bottom-left corner is a1 and
the middle spot is j10. However, internally all data
structures representing the board and moves using zero-indexed and as
The Player Class:
When using our framework, your robot class will be declared as a
subclass of the Player class. This allows it to inherit all
the functionality for interacting with a game server and managing the
timer. The player client relies on a multithreaded design. As soon as
it becomes the robot's turn, a timer is started and a call is made to
the function takeTurn(state). From within this
function, you may do whatever processing you like, but when the timer
in the other thread expires, a move will be sent back to the
server. By default, it is an arbitrary legal move. You may update
your move preference anytime before the time expires by making the
call
The move must be an instance of the Action class (described below). If the finalize parameter is true, the move will immediately be sent back to the server. If finalize is false, it records the move as your current favorite, but you may change your mind after further computations by calling the setMoveChoice again.self.setMoveChoice(move, finalize)
When the time expires, the other thread will send your most recently selected move back to the server. But this does not automatically cause the takeTurn call to exit. It is your responsibility to monitor the progress of the game and to make sure that you force the function to return (or risk making future moves haphazardly as you will still be analyzing an old state). Your way to know if the game has moved on is by calling the function
This will return True if the server has already received a move on your behalf since the most recent call to takeTurn began. You should design your code so that it polls this method at regular intervals.self.updateReceived()
When it is the opponent's turn, the method waitForOpponent(state) is called. By default, we have implemented this function to immediately return. However, you are welcome to try to make productive use of the clock cycles while you are waiting. However, as was the case with takeTurn, it is your responsibility to monitor the progress of the game via updateReceived() queries, and exit this function once the game has changed (because your turn will already have started).
Finally, we note that by subclassing the Player class, the robot has access to the following inherited methods to get more information about the game.
The Game Class:
The game class does not have any significant state. But it does
provide many important methods that effectively codify the rules
of the game.
Finally, there are some additional methods that are primarily used for generating human-readable output.
The State Class:
The state class supports meaningful equivalence testing via the
== and != operators, allowing for recognition
of two states being the same. States are also hashable, and
therefore can be used as keys in a dictionary, or as elements of
a set.
States also support the following methods:
The Action Class:
The action class is rather simple. If you want to look at where
a piece would be placed if the action is taken, it supports
methods
getRow() and
getColumn().
Our code repository includes five sample robots, with increasing complexity.
Although our robot5 does not embarrass itself, there is enormous opportunity for improving upon its play when developing your own robot. These improvements can come from better evaluation of positions, or more efficient use of computation cycles (thereby getting deeper in the game tree). Some suggestions for potential improvements include the following:
You can develop a more successful heuristic than the one we introduce with robot4. While I like the concept of considering all possible winning locations, the weighting we apply is arbitrary.
Our proposed heuristic completely ignores the issue of captured pieces, other than the indirect effect they have on the board. But there is a more direct advantage to captures as it brings a player closer to the opportunity to win by capturing five pairs.
For example, at an extreme, it is very difficult to play effectively if you have already given up the capture of 4 pairs, because you must avoid any scenario that leaves you at risk of having a pair captured, thereby ending the game.
A lot of time is spent in computing the heuristic that we provide. In particular, it computes it from scratch for each state.
Much greater efficiency is achievable by computing the heuristic incrementally as we progress from one state to a resulting state. That is, if sufficient bookkeeping bout a parent state were maintained, it might be possible to efficiently compute how the heuristic has changed as a result of the new move.
The efficiency of alpha-beta pruning is greatly increased if moves are considered at each level in the order from likely best to likely worst, as viewed by the player making the move.
While the rule of only considering moves that are adjacent to an existing tile is reasonable, there are two pitfalls. First, it may be that a really good move is overlooked because it is not adjacent (for example, it may be a piece that lies on the intersection of two extended attacks).
But more importantly, the rule of adjacency does not seem to be aggressive enough. As we get beyond the opening of the game, the number of adjacent moves grows. The current iterative deepening does not get deep enough in the game to see important tactical combinations, and that is because the effective branching factor is too high.
I expect that much better results will be achieved if the branching factor can be further limited.
As we get further along, I hope to deploy a bunch of robots with varying skill levels (but stronger than robot5), having game servers run around the clock ready to play willing opponents (human or robot).
More details will be provided here...
As part of the evaluation of this project (and for fun), we will run a double round-robin tournament with all submitted robots (as well as robots 1 through 5, and perhaps an entry from the instructor), allowing each robot to play once as black and once as white against each competitor. This tournament will be run with a 30-second time limit per move.
The top eight robots from the tournament will advance to the playoffs. The playoffs will be using a single-elimination format, with seeds based upon the rankings from the first tournament (i.e., with #1 vs. #8, #2 vs. #7). Furthermore, we will give the higher seed "home-field advantage" by deciding whether the robot plays as black or as white. This tournament will be run with a 15-second time limit per move. We will likely have this tournament broadcast live in the Linux Lab at a time to be determined.
You are to submit a revised version of a Pente robot. All sourcecode that you write must be contained within a single .py file that is submitted. Furthermore, you are to submit a README file that summarizes all design decisions that you made when rewriting the robot. Submit your single .py source code file and your text readme file electronically via the course website (details on the submission process).
This project will be worth a total of 50 points. 15 of those points will be directly apportioned based on your robot's winning percentage in the initial round-robin. The other 35 points will be based on my evaluation of your code (as greatly guided by your prose description of your approaches from the readme file).
Extra Credit
The two robots that lose in the semi-finals of the secondary
tournament will each receive an extra 5 points. The robot that loses
in the finals will receive an extra 8 points. The robot that wins the
tournament will receive and extra 10 points.