/* CellularPrisoner.java, Version 1.1.3 (14-Sep-96) by Robert Rothenburg Walking-Owl An implementation of the cellular automata in the paper "Undecideability in the Spatialized Prisoner's Dilemma" by Patrick Grim (Group for Logic and Formal Semantics, Dept. of Philosophy, SUNY at Stony Brook). This has been tested on the Win95 JDK 1.02, Netscape 3.0, and OS/2 JDK. History: 1.0 - Original version 1.1 - Commented the code maxGridWidth, maxGridHeight separated from gridWidth and gridHeight to allow variable grid sizes Restarts every 25 generations Changed score table for limits of infinite rounds Added parameters from calling .html file Added window resizing Added generation counter label Moved drawing routines to initField() and playRound() methods (faster?); lessens unsynchronized screen updates when bogged down 1.1.1 Minor tweaks. 1.1.2 Fixed bug in comparing scores (ignored lower/right neighbors?) Black background 1.1.3 allows scaleFactor == 1 Bugs: Reloading the .html file from Netscape 3.0 causes the applet to ignore the generationCycle limit Restarting from the JDK 1.02 Applet | Restart seems to mess up the generation counter display Different implementations of Java handle backgrounds differently. To-do: Allow initial field to be defined in a file rather than randomly Rewrite a custom (better) Random class (the current Java implementation uses a Linear Congruential Generator?) Rewrite a generic CellularAutomata class? Have the class run as a separate thread and update it when changed? Add Restart/Stop (Pause) Buttons Color/Strategy key, Population graphs There may be a page fault under Win95 JDK 1.02 when closing or quitting the applet. License: Copyright (C) 1996, Robert Rothenburg Walking-Owl and the Group for Logic and Formal Semantics, Dept. of Philosophy, SUNY at Stony Brook, Stony Brook, New York, 11794. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ import java.awt.*; import java.applet.*; import java.lang.*; import java.util.*; public class CellularPrisoner extends Applet implements Runnable { Thread MyThread = null; static final int maxGridWidth = 64, maxGridHeight = 64; // max grid size // 64 by 64 cells is the largest most machines can sanely // handle at the moment (100 by 100 can even bogs down a i586) int gridWidth = maxGridWidth, // number of cells gridHeight = maxGridHeight; int scaleFactor = 4; // number of pixels per cell int strategyField[][] = new int [maxGridWidth] [maxGridHeight]; int scoreField[][] = new int [maxGridWidth] [maxGridHeight]; static final int UNDEFINED = -1; // static final int EXTRA = 32; // extra space in window int delay = 200, // delay (in ms) changeCount = 4; // no of run cycles to repaint same image // 2 should be the minimum; 4 seems best // the value of 'delay' does not matter if the machine is too slow // changeCount should be greater than 1, because slow machines may // not yeild large enough timeslices to paint the updated image int changed = 0, // non-zero == changed image (repaint) generationCount = 0; // generation counter int generationCycle = 25; // reset every n generations (0 == no reset) Image offScreen; // off-screen image to paint to Random r = new Random(); // Random-number generation class Panel genPanel = new Panel(); // for generation-counter display Label genLabel = new Label("Generation: "); Label numLabel = new Label(" "); // four spaces // Strategies are encoded as 3-bit binary numbers (000..111, or 0..7) // bit 2 == 0 (initially defect) or 1 (initially cooperate) // " 1 == 0 (defect if opponent cooperated) or 1 (cooperate...) // " 0 == 0 (defect if opponent defected) or 1 (cooperate if defected) // Limit scores for infinite rounds of iterated Prisoner's Dilemma // Scores A receives when playing strategies 000..111 on horizontal // axis against B playing stratgies 000..111 on vertical axis. static int scoreTable[][] = { { 100, 0, 100, 0, 100, 0, 100, 0 }, { 500, 200, 225, 0, 500, 0, 225, 0 }, { 100, 225, 100, 300, 100, 225, 250, 300 }, { 500, 500, 300, 300, 500, 500, 300, 300 }, { 100, 0, 100, 0, 100, 0, 100, 0 }, { 500, 500, 225, 0, 500, 200, 225, 0 }, { 100, 225, 250, 300, 100, 225, 300, 300 }, { 500, 500, 300, 300, 500, 500, 300, 300 } }; // scoreTable[B][A] /* // Scores for 200 rounds of iterated Prisoner's Dilemma (from v1.0) static int scoreTable[][] = { { 200, 1, 200, 1, 199, 0, 199, 0 }, { 996, 400, 450, 4, 991, 0, 450, 0 }, { 200, 450, 200, 595, 203, 450, 500, 597 }, { 996, 994, 600, 598, 995, 993, 599, 597 }, { 204, 6, 203, 5, 202, 4, 201, 3 }, {1000, 1000, 450, 8, 994, 400, 450, 3 }, { 204, 450, 500, 599, 206, 450, 600, 600 }, {1000, 1000, 602, 602, 998, 998, 600, 600 } }; // scoreTable[B][A] */ Color strategyToColor(int s) { switch (s) { case 0: return Color.orange; // All-D case 1: return Color.cyan; case 2: return Color.red; // Suspicious Tit-for-Tat case 3: return Color.magenta; // D then All-C case 4: return Color.green; // C then All-D case 5: return Color.blue; // Tat-for-Tit case 6: return Color.gray; // Tit-for-Tat (TFT) case 7: return Color.yellow; // All-C ("Quaker") default: return Color.black; } } void updateScore(int x, int y, int s) // update player's score { if (x<0) x+=gridWidth; else if (x>=gridWidth) x%=gridWidth; if (y<0) y+=gridHeight; else if (y>=gridHeight) y%=gridHeight; // if (scoreField[x][y] == UNDEFINED) scoreField[x][y] = s; else scoreField[x][y] += s; } void playRound() // play a round (update generation) { Graphics gPseudo; int betterField[][] = new int [gridWidth] [gridHeight]; int A, B, score, x, y, x1, y1; if (changed == 0) { for(x=0; x=gridWidth) x%=gridWidth; if (y<0) y+=gridHeight; else if (y>=gridHeight) y%=gridHeight; return strategyField[x][y]; } int getScore(int x, int y) // get score of player at (x,y) { if (x<0) x+=gridWidth; else if (x>=gridWidth) x%=gridWidth; if (y<0) y+=gridHeight; else if (y>=gridHeight) y%=gridHeight; return scoreField[x][y]; } void initField() // initialize field(s) { Graphics gPseudo; gPseudo = offScreen.getGraphics(); for(int x=0; x maxGridWidth) gridWidth = maxGridWidth; else if (gridWidth<8) gridWidth = 8; gridHeight = (int) lgetParameter("FieldHeight", gridHeight); if (gridHeight > maxGridHeight) gridHeight = maxGridHeight; else if (gridHeight<8) gridHeight = 8; scaleFactor = (int) lgetParameter("Scale", scaleFactor); if (scaleFactor < 1) scaleFactor = 1; generationCycle = (int) lgetParameter("Generations", generationCycle); if ((d.width<(gridWidth*scaleFactor)) || (d.height<(EXTRA+(gridHeight*scaleFactor)))) { this.resize( gridWidth*scaleFactor, EXTRA+(gridHeight*scaleFactor) ); d = this.size(); } offScreen = createImage( d.width, d.height ); gPseudo = offScreen.getGraphics(); // JDK 1.02, Netscape, and OS/2 JDK all handle backgrounds differently // so we use fillRect() rather than clearRect() method gPseudo.setColor( Color.black ); gPseudo.fillRect(0, 0, d.width, d.height ); setLayout(new BorderLayout()); genPanel.setLayout(new FlowLayout()); genPanel.add(genLabel); genPanel.add(numLabel); add("South", genPanel); initField(); } public void paint(Graphics g) { String strGen; strGen = String.valueOf( generationCount ); numLabel.setText(strGen); if (offScreen != null) { g.drawImage( offScreen, 0, 0, this ); if (changed > 0) --changed; } } public void update(Graphics g) { paint(g); } public void run() { Graphics g = getGraphics(); while (MyThread != null) { if (offScreen != null) if (changed == 0) if ((generationCount == generationCycle) && ( generationCycle != 0)) initField(); else playRound(); paint(g); try { Thread.sleep( delay/changeCount ); } catch (InterruptedException e) {}; } } public void stop() { MyThread = null; } }