DATA 420 - Modeling and Simulation - Spring 2026

Homework #7 — Competitive Agents (Part I: Rules-based agents)

Possible experience: +40XP

Due: Monday, April 27 Thursday, April 30, midnight

Iterated Prisoner's Dilemma Tournament

Phase 1: Rules-based agents

In this project, you'll be designing a rules-based agent that can play Iterated Prisoner's Dilemma on a dynamic network, competing against your fellow students and (ideally) slaughtering them.

The project will proceed in two phases: first, a rules-based agent (worth up to +40XP), and later, an LLM-based agent (worth up to another +40XP). Stay tuned for the latter. The simulation scaffolding is proving challenging for Stephen to put together cleanly.

Instructions

Preparation

Clone my IPD repo somewhere on your system. (If you don't know how to clone a repo, google it.)

Make a virtual environment for the project, and install all the required packages for the project, which I've put in requirements.txt. (The workflow for this, if you've forgotten or never learned it, is:

$ python -m venv IPD_venv
$ source IPD_venv/bin/activate
$ pip install -r requirements.txt

These are Linux/Mac commands; your mileage may vary on Windows, but I think if you use "PowerShell" you should be basically okay.)

Getting a pulse

Go into the players directory of your cloned repo, and copy student_rule_based_template.py to another Python file name, which does not have the word "template" anywhere in it. (Some good choices would be yourFirstName.py, yourUMWNetID.py, or yourFavoriteWheelOfTimeCharacter.py.)

You'll be putting all your code into this file. The way the simulator works is this: in addition to instantiating quantities of agents as controlled by the --agent-fracs parameter, it also searches the directory called players for student players that it will treat as additional "one-offs." (i.e., each student player that exists in that directory will be instantiated once and added to the environment.) See the command-line arguments (by typing "cli.py -h") for how to control the number and types of other kinds of agents.

Open your new Python file in your editor, and change the places where it says StudentRulesBasedTemplate to some other name that does not have the word "template" in it anywhere. A good choice would be YourFirstName (unless your name is Josh or Michael, in which case YourFirstAndLastName is a better choice).

For the moment, change nothing else. Run this from the command line:

$ python cli.py 10 --agent-fracs Mean 1.0 \
    --p-same 0.3 --p-diff 0.3 --seed 124 \
    --num-iter 10 \
    --plot --plot-interactive \
    --players players

and make sure you can see your named agent (and its node number in the graph) on the screen with a bunch of Mean folks (hated to do that to you, sry).

Celebrate and grab a Coke.

Stuff you gotta know

I'm hoping that the comments I spent a lot of time building into the template file will be self-explanatory in most cases. Here are the highlights:

Spend a little time looking through the command-line parameters of the simulator, because they are numerous and a few of them are very important to grok. Run this command at the top level of the repo:

$ python cli.py -h

and spend a few moments just reading what it says and trying to figure out what the various options do. This is time well spent.

Things you inherit from IPDAgent superclass

By virtue of the fact that your class inherits from IPDAgent, and therefore is an IPDAgent, it has all the rights and privileges therein, including the ability to access these variables/methods:

self.node

This is the node number representing your agent, under self.node. Keep in mind that the rewiring (and other) operations are all keyed on an agent's node number, not its Mesa unique_id.

self.history

The self.history instance variable contains your entire move history against every opponent. Its value is a dictionary that looks something like this:

 { 3: [
{'step':1,'self_move':'C','other_move':'D'},
{'step':2,'self_move':'D','other_move':'D'} ], 8: [
{'step':2,'self_move':'D','other_move':'C'}, ] } 

In case it's not obvious, that structure means:

  1. You're currently adjacent (directly connected) to two other nodes who you're playing IPD against: node #3 and node #8.
  2. On step 1, you played C and jerk face (agent #3) played D.
  3. On step 2, you decided to pay #3 back in kind (ignoring the advice in Matt. 5:38-44) and defect against them.
  4. On step 2, you also got a new neighbor (welcome, player #8! nice to meet you!) This player was nice to you, but you weren't nice back, defecting on the first round. We'll have to see how things continue to play out.

Some of you are thinking "waitaminit, how could I suddenly have a new opponent in round 2?" Answer: because either you added #8 or #8 added you, after the first round. Get used to this -- it's gonna happen all the time.

self.neighbors()

At any point in time, you can call self.neighbors() which gives a list of (the node numbers of) your current graph neighbors. Unlike making a FOAF request, this is free to perform at any time, and will always give truthful information.

The Model object

There are several things available to you in the Model object, which is accessible from inside your class's methods via self.model. It contains:

  1. Perhaps most importantly, the method request_foaf_info_from()
  2. An RNG (random number generator) under the name "self.model.random". If you do any random generation in your player, you must use "self.model.random.whatever...()" to do it, not "someRandomNumberGeneratorYouMadeInSomeOtherWay.whatever...()". This is to make simulation runs (mostly) reproducible.
  3. The values self.model.steps that tell you what move number you're about to make, and self.model.num_iter which contains how many rounds the overall game will last. Those two help you understand how close to the end you are, which can (and probably should) change your strategy.

This is whatcha got to work with.

Implementing a strategy

Now you need to begin to develop your strategy in earnest. You have one method that you must change, and two others that are optional:

Method
.decide_against()required
.inform_foaf()optional
.request_rewire()optional

The comments give gory details about all of these. Send email if you're confused about what any of them may do.

Making it more better

I strongly recommend an iterative approach to this entire project, in which you gradually make changes to your code, each time making it smarter and more sophisticated, and seeing how it responds to various mixes of the other types of agents (specified via --agent-fracs on the command line).

Sharing agents with a buddy is an excellent and praiseworthy way of assessing your strategy, since the two (or three, or four...) of you will expose each others' weaknesses and give you ideas for improvement.

Turning it in

Send me an email with subject line "DATA 420 homework #7 turnin". It should have attached one .py file: your modified student rule-based player that I can insert into my players directory and have it play in a simulated world with all the other kinds of agents. (This is what you'll want to do as well, with different random number seeds, to understand what kinds of agents, and in how many quantities, and with what connection probabilities, cause your agent to do well versus to suck.) Your program should have some comments indicating what the various aspects of your strategy do, so that I can understand and judge your intent.

Getting help

Come to office hours, or send me email with subject line "DATA 420 Homework #7 help!!"