CPSC 415 - Artificial Intelligence - Fall 2023
Possible experience: up to +50XP (or possibly even +60XP or, incredibly, +70XP)
Due: Tuesday, Oct. 31st, midnight
This program concerns one of the most time-honored applications of AI: playing fully-observable, deterministic, multi-agent, zero-sum games (specifically, chess). You will be writing an automated chess 'bot that uses the minimax algorithm and/or other logic to compete against yourself, my test scripts, and other players.
The files chess_model.py, chess_config.py, chess_player.py, chess_view.py, main_chess.py, and chess_piece.py in the class's github repo have a few Python classes you'll need to know about.
A Board object represents the state of a chess board. In true Pythonic style, it is itself a dictionary that maps locations (in chess notation) to Piece objects; i.e., if you have an object "my_board", you can say "my_board['c5']" to get the Piece object that is currently on space c5. (Third column from left, fifth row from bottom.) You can also use ".items()" in the usual way to iterate through it.
Here are the legal operations you may use on a Board object:
[('a1','a2'), ('a1','b1'), ('e4','e2'), ('d4','d7')]which would indicate four possible moves for the player in question, the first of which is to move the piece in the lower-right corner up one square. All of these moves are guaranteed to be legal to play.
Anything else is considered cheating, and will result in public flogging.
Piece objects represent individual pieces on the board. The only thing you should ever have to do to a Piece object is call .get_notation() to get the chess notation for its piece type (e.g., "R" for a white rook, or "n" for a black knight.)
If you wanna be a playa, you'll need to create a subclass of the ChessPlayer class (see below).
Your job is to create a Python class called yourUmwId_ChessPlayer, which inherits from ChessPlayer, an abstract base class. It should be in a file called yourUmwId_ChessPlayer.py. Its first few lines should look like this:
from chess_player import ChessPlayer class jsmith3_ChessPlayer(ChessPlayer): def __init__(self, board, color): super().__init__(board, color) def get_move(self, your_remaining_time, opp_remaining_time, prog_stuff): # YOUR MIND-BOGGLING CODE GOES HERE
Notice that a Board object is given to your ChessPlayer class as an argument to the constructor, is passed up to the superclass's constructor for safekeeping, and is stored in an instance variable ("self.board") that is accessible whenever your player feels the need to examine it. You may also access self.color to find out which color your player is (the string 'black' or 'white' will be returned).
The only meaningful method of your class (besides any helper methods you may want to create) is .get_move(), described here in some detail. The purpose of the method is to return a tuple describing your chosen move. The tuples are the same as described in the return value for .get_all_available_legal_moves(color), above. In fact, you could write a completely random chess player with this line of code:
def get_move(self, your_remaining_time, opp_remaining_time, prog_stuff): return random.choice(self.board.get_all_available_legal_moves(self.color))
and this has in fact been provided for you in the file Random_ChessPlayer.py, which you can pit your agent (or yourself) against.
The other arguments to .get_move(), all completely optional, are as follows:
Again, all three of these extra arguments are completely optional. The most fundamental piece of information your .get_move() method will need is the Board object itself, accessible via self.board.
Get the fresh version of the class repo by doing a git pull. Make sure you can run the simulator in player-vs-player mode by typing:
$ python3 main_chess.py
and drag/dropping pieces. Also make sure you can quit and restart the simulator and run it in player-vs-computer mode with the Random player provided, and in computer-vs-computer mode.
When you get bored of this, create your player class, in a file named exactly as above, and give it the random player implementation suggested. Make sure it shows up in the menu for the computer players and that you can run it. Then check your fledgling player class into your repo via git commit.
You are now ready to begin thinking. No matter what other bells and whistles you eventually toss into your pot of soup, the core of your algorithm is going to be minimax, so take a deep breath and think about how to implement that.
In addition to the standard 8×8 board, the simulation supports other configurations (via JSON files in the chess_configs directory). For tournament play (see below), I will be using the mini (6×6) board so things run faster. Any of them can be chosen from the left-hand menu on startup.
The simulation can operate in two modes: regular mode (in which case the positions of the pieces are specified in the JSON file in chess_configs) or "crazy mode." In crazy mode, all the pieces behind the pawns are chosen randomly from the possible pieces, with two exceptions: (1) there will always be exactly one King per side, and (2) the piece configurations of the two sides will be mirror images of each other across the board, so that neither side has an advantage. For tournament play, I will in fact be using crazy mode. It can be enabled via the checkbox at startup, or with "crazy=True" or "crazy=False" as the final command-line argument to main_chess.py.
All the standard chess pieces move in their standard ways. There are also three others my kids and I made up:
|
|
|
All the other rules of the game are as in standard chess, except the following:
While your agent is thinking about its next move, you can put status messages on the screen and control the progress bar. Here's how:
The prog_stuff argument given to your .get_move() method has two components: prog_stuff.bar and prog_stuff.text.
Any time you want to put text in the status bar, write:
prog_stuff.text.set('The text I want to appear')
The progress bar widget, like all progress bars, has a (numeric) maximum size as well as a number indicating how far along its progress is. To set its maximum size, in whatever units you wish, type:
prog_stuff.bar.config(maximum=theMaximumNumber)
Then, to update its current value so that it progresses rightwards as your algorithm makes progress:
prog_stuff.bar.value.set(someNumberLessThanTheMaximumNumber) prog_stuff.bar.update_idletasks()
On Thursday, Nov. 2nd, we'll have a bracket-style single-elimination chess tournament on the big screen. Best of five games will determine each pairing's winner, with players alternating the black and white pieces for each game. This contest will take place on the 6×6 board in crazy mode. The winner and runner-up will obtain valuable prizes (see below).
If your agent can consistently beat my random player (provided with the simulation code), it will earn at least +30XP.
If your agent can consistently beat my pretty sucky attempt at an actual thinking player (not provided), it will earn at least +50XP.
The runner-up in the Nov. 2 Chess Challenge will earn +60XP. The grand prize winner will earn a whopping +70XP, plus a year's supply reasonable amount of Swedish Fish, plus a valuable trophy not available in stores.
Finally, if your agent can beat Magnus Carlsen in a best-of-7 match under actual live tournament conditions, you will earn +10,000XP and a lifetime's supply of Norwegian Fish.
You will turn this assignment in by attaching your properly-named ChessPlayer Python file to an email with subject line "CPSC 415 Program #3 turnin". For an additional +1XP, include in the body of the email the name of your own original chess piece and a description of how it would work.
Come to office hours, or send me email with subject line "CPSC 415 Program #3 help!!"