add Critter game assignment from WCC CS 145 Spring 2019.

This commit is contained in:
Matthew Jensen 2019-12-14 18:08:37 -08:00
parent 63d3de5191
commit fd6cfb770d
12 changed files with 964 additions and 0 deletions

54
critters/Bear.java Normal file
View File

@ -0,0 +1,54 @@
/*
*
* Matt Jensen
* CS145
* Lab 2 - Critters
* 4/23/19
* Partners: Melissa, Devante
*
*/
import java.awt.*;
import java.util.*;
public class Bear extends Critter {
private Color color = Color.BLACK;
private String symbol = "/";
// white if polar
public Bear(boolean polar) {
if(polar) {
this.color = Color.WHITE;
}
if( Math.random() > 0.5) {
this.changeSymbol();
}
}
//getter
public Color getColor() {
return this.color;
}
//getter
public String toString() {
return this.symbol;
}
public Action getMove(CritterInfo info) {
this.changeSymbol();
Neighbor front = info.getFront();
if( front == Neighbor.OTHER ) { // check for enemy in front.
return Action.INFECT;
}
if ( front == Neighbor.EMPTY ) { // check for obstacle.
return Action.HOP;
}
return Action.LEFT;
}
// toggles between / and \
public void changeSymbol() {
if(this.symbol.equals("/")) {
this.symbol = "\\";
}
else {
this.symbol = "/";
}
}
}

62
critters/Critter.java Normal file
View File

@ -0,0 +1,62 @@
// This is the superclass of all of the Critter classes. Your class should
// extend this class. The class provides several kinds of constants:
//
// type Neighbor : WALL, EMPTY, SAME, OTHER
// type Action : HOP, LEFT, RIGHT, INFECT
// type Direction : NORTH, SOUTH, EAST, WEST
//
// Override the following methods to change the behavior of your Critter:
//
// public Action getMove(CritterInfo info)
// public Color getColor()
// public String toString()
//
// The CritterInfo object passed to the getMove method has the following
// available methods:
//
// public Neighbor getFront(); neighbor in front of you
// public Neighbor getBack(); neighbor in back of you
// public Neighbor getLeft(); neighbor to your left
// public Neighbor getRight(); neighbor to your right
// public Direction getDirection(); direction you are facing
// public Direction getFrontDirection(); direction of front neighbor
// public Direction getBackDirection(); direction of back neighbor
// public Direction getLeftDirection(); direction of left neighbor
// public Direction getRightDirection(); direction of right neighbor
import java.awt.*;
public class Critter {
public static enum Neighbor {
WALL, EMPTY, SAME, OTHER
};
public static enum Action {
HOP, LEFT, RIGHT, INFECT
};
public static enum Direction {
NORTH, SOUTH, EAST, WEST
};
// This method should be overriden (default action is turning left)
public Action getMove(CritterInfo info) {
return Action.LEFT;
}
// This method should be overriden (default color is black)
public Color getColor() {
return Color.BLACK;
}
// This method should be overriden (default display is "?")
public String toString() {
return "?";
}
// This prevents critters from trying to redefine the definition of
// object equality, which is important for the simulator to work properly.
public final boolean equals(Object other) {
return this == other;
}
}

203
critters/CritterFrame.java Normal file
View File

@ -0,0 +1,203 @@
// Class CritterFrame provides the user interface for a simple simulation
// program.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.util.*;
public class CritterFrame extends JFrame {
private CritterModel myModel;
private CritterPanel myPicture;
private javax.swing.Timer myTimer;
private JButton[] counts;
private JButton countButton;
private boolean started;
private static boolean created;
public CritterFrame(int width, int height) {
// this prevents someone from trying to create their own copy of
// the GUI components
if (created)
throw new RuntimeException("Only one world allowed");
created = true;
// create frame and model
setTitle("CSE142 critter simulation");
setDefaultCloseOperation(EXIT_ON_CLOSE);
myModel = new CritterModel(width, height);
// set up critter picture panel
myPicture = new CritterPanel(myModel);
add(myPicture, BorderLayout.CENTER);
addTimer();
constructSouth();
// initially it has not started
started = false;
}
// construct the controls and label for the southern panel
private void constructSouth() {
// add timer controls to the south
JPanel p = new JPanel();
final JSlider slider = new JSlider();
slider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
double ratio = 1000.0 / (1 + Math.pow(slider.getValue(), 0.3));
myTimer.setDelay((int) (ratio - 180));
}
});
slider.setValue(20);
p.add(new JLabel("slow"));
p.add(slider);
p.add(new JLabel("fast"));
JButton b1 = new JButton("start");
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myTimer.start();
}
});
p.add(b1);
JButton b2 = new JButton("stop");
b2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myTimer.stop();
}
});
p.add(b2);
JButton b3 = new JButton("step");
b3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
doOneStep();
}
});
p.add(b3);
// add debug button
JButton b4 = new JButton("debug");
b4.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myModel.toggleDebug();
myPicture.repaint();
}
});
p.add(b4);
// add 100 button
JButton b5 = new JButton("next 100");
b5.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
multistep(100);
}
});
p.add(b5);
add(p, BorderLayout.SOUTH);
}
// starts the simulation...assumes all critters have already been added
public void start() {
// don't let anyone start a second time and remember if we have started
if (started) {
return;
}
// if they didn't add any critters, then nothing to do
if (myModel.getCounts().isEmpty()) {
System.out.println("nothing to simulate--no critters");
return;
}
started = true;
addClassCounts();
myModel.updateColorString();
pack();
setVisible(true);
}
// add right-hand column showing how many of each critter are alive
private void addClassCounts() {
Set<Map.Entry<String, Integer>> entries = myModel.getCounts();
JPanel p = new JPanel(new GridLayout(entries.size() + 1, 1));
counts = new JButton[entries.size()];
for (int i = 0; i < counts.length; i++) {
counts[i] = new JButton();
p.add(counts[i]);
}
// add simulation count
countButton = new JButton();
countButton.setForeground(Color.BLUE);
p.add(countButton);
add(p, BorderLayout.EAST);
setCounts();
}
private void setCounts() {
Set<Map.Entry<String, Integer>> entries = myModel.getCounts();
int i = 0;
int max = 0;
int maxI = 0;
for (Map.Entry<String, Integer> entry: myModel.getCounts()) {
String s = String.format("%s =%4d", entry.getKey(),
(int) entry.getValue());
counts[i].setText(s);
counts[i].setForeground(Color.BLACK);
if (entry.getValue() > max) {
max = entry.getValue();
maxI = i;
}
i++;
}
counts[maxI].setForeground(Color.RED);
String s = String.format("step =%5d", myModel.getSimulationCount());
countButton.setText(s);
}
// add a certain number of critters of a particular class to the simulation
public void add(int number, Class<? extends Critter> c) {
// don't let anyone add critters after simulation starts
if (started) {
return;
}
// temporarily turning on started flag prevents critter constructors
// from calling add
started = true;
myModel.add(number, c);
started = false;
}
// post: creates a timer that calls the model's update
// method and repaints the display
private void addTimer() {
ActionListener updater = new ActionListener() {
public void actionPerformed(ActionEvent e) {
doOneStep();
}
};
myTimer = new javax.swing.Timer(0, updater);
myTimer.setCoalesce(true);
}
// one step of the simulation
private void doOneStep() {
myModel.update();
setCounts();
myPicture.repaint();
}
// advance the simulation until step % n is 0
private void multistep(int n) {
myTimer.stop();
do {
myModel.update();
} while (myModel.getSimulationCount() % n != 0);
setCounts();
myPicture.repaint();
}
}

15
critters/CritterInfo.java Normal file
View File

@ -0,0 +1,15 @@
// The CritterInfo interface defines a set of methods for querying the
// state of a critter simulation. You should not alter this file. See
// the documentation in the Critter class for a more detailed explanation.
public interface CritterInfo {
public Critter.Neighbor getFront();
public Critter.Neighbor getBack();
public Critter.Neighbor getLeft();
public Critter.Neighbor getRight();
public Critter.Direction getDirection();
public Critter.Direction getFrontDirection();
public Critter.Direction getBackDirection();
public Critter.Direction getLeftDirection();
public Critter.Direction getRightDirection();
}

25
critters/CritterMain.java Normal file
View File

@ -0,0 +1,25 @@
// CSE 142 Homework 9 (Critters)
// Authors: Stuart Reges and Marty Stepp
// modified by Kyle Thayer
//
// CritterMain provides the main method for a simple simulation program. Alter
// the number of each critter added to the simulation if you want to experiment
// with different scenarios. You can also alter the width and height passed to
// the CritterFrame constructor.
public class CritterMain {
public static void main(String[] args) {
CritterFrame frame = new CritterFrame(60, 40);
// uncomment each of these lines as you complete these classes
frame.add(30, Bear.class);
frame.add(30, Lion.class);
frame.add(30, Giant.class);
frame.add(30, Orca.class);
frame.add(30, FlyTrap.class);
frame.add(30, Food.class);
frame.start();
}
}

327
critters/CritterModel.java Normal file
View File

@ -0,0 +1,327 @@
// Class CritterModel keeps track of the state of the critter simulation.
import java.util.*;
import java.awt.Point;
import java.awt.Color;
import java.lang.reflect.*;
public class CritterModel {
private int height;
private int width;
private Critter[][] grid;
private Map<Critter, PrivateData> info;
private SortedMap<String, Integer>critterCount;
private boolean debugView;
private int simulationCount;
private static boolean created;
public CritterModel(int width, int height) {
// this prevents someone from trying to create their own copy of
// the GUI components
if (created)
throw new RuntimeException("Only one world allowed");
created = true;
this.width = width;
this.height = height;
grid = new Critter[width][height];
info = new HashMap<Critter, PrivateData>();
critterCount = new TreeMap<String, Integer>();
this.debugView = false;
}
public Iterator<Critter> iterator() {
return info.keySet().iterator();
}
public Point getPoint(Critter c) {
return info.get(c).p;
}
public Color getColor(Critter c) {
return info.get(c).color;
}
public String getString(Critter c) {
return info.get(c).string;
}
public void add(int number, Class<? extends Critter> critter) {
Random r = new Random();
Critter.Direction[] directions = Critter.Direction.values();
if (info.size() + number > width * height)
throw new RuntimeException("adding too many critters");
for (int i = 0; i < number; i++) {
Critter next;
try {
next = makeCritter(critter);
} catch (IllegalArgumentException e) {
System.out.println("ERROR: " + critter + " does not have" +
" the appropriate constructor.");
System.exit(1);
return;
} catch (Exception e) {
System.out.println("ERROR: " + critter + " threw an " +
" exception in its constructor.");
System.exit(1);
return;
}
int x, y;
do {
x = r.nextInt(width);
y = r.nextInt(height);
} while (grid[x][y] != null);
grid[x][y] = next;
Critter.Direction d = directions[r.nextInt(directions.length)];
info.put(next, new PrivateData(new Point(x, y), d));
}
String name = critter.getName();
if (!critterCount.containsKey(name))
critterCount.put(name, number);
else
critterCount.put(name, critterCount.get(name) + number);
}
@SuppressWarnings("unchecked")
private Critter makeCritter(Class critter) throws Exception {
Constructor c = critter.getConstructors()[0];
if (critter.toString().equals("class Bear")) {
// flip a coin
boolean b = Math.random() < 0.5;
return (Critter) c.newInstance(new Object[] {b});
} else {
return (Critter) c.newInstance();
}
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getAppearance(Critter c) {
// Override specified toString if debug flag is true
if (!debugView)
return info.get(c).string;
else {
PrivateData data = info.get(c);
if (data.direction == Critter.Direction.NORTH) return "^";
else if (data.direction == Critter.Direction.SOUTH) return "v";
else if (data.direction == Critter.Direction.EAST) return ">";
else return "<";
}
}
public void toggleDebug() {
this.debugView = !this.debugView;
}
private boolean inBounds(int x, int y) {
return (x >= 0 && x < width && y >= 0 && y < height);
}
private boolean inBounds(Point p) {
return inBounds(p.x, p.y);
}
// returns the result of rotating the given direction clockwise
private Critter.Direction rotate(Critter.Direction d) {
if (d == Critter.Direction.NORTH) return Critter.Direction.EAST;
else if (d == Critter.Direction.SOUTH) return Critter.Direction.WEST;
else if (d == Critter.Direction.EAST) return Critter.Direction.SOUTH;
else return Critter.Direction.NORTH;
}
private Point pointAt(Point p, Critter.Direction d) {
if (d == Critter.Direction.NORTH) return new Point(p.x, p.y - 1);
else if (d == Critter.Direction.SOUTH) return new Point(p.x, p.y + 1);
else if (d == Critter.Direction.EAST) return new Point(p.x + 1, p.y);
else return new Point(p.x - 1, p.y);
}
private Info getInfo(PrivateData data, Class original) {
Critter.Neighbor[] neighbors = new Critter.Neighbor[4];
Critter.Direction d = data.direction;
Critter.Direction[] neighborDirections = new Critter.Direction[4];
for (int i = 0; i < 4; i++) {
neighbors[i] = getStatus(pointAt(data.p, d), original);
if (neighbors[i] == Critter.Neighbor.OTHER ||
neighbors[i] == Critter.Neighbor.SAME){
Point p = pointAt(data.p, d);
PrivateData oldData = info.get(grid[p.x][p.y]);
neighborDirections[i] = oldData.direction;
} else {
neighborDirections[i] = Critter.Direction.NORTH;
}
d = rotate(d);
}
return new Info(neighbors, data.direction, neighborDirections);
}
private Critter.Neighbor getStatus(Point p, Class original) {
if (!inBounds(p))
return Critter.Neighbor.WALL;
else if (grid[p.x][p.y] == null)
return Critter.Neighbor.EMPTY;
else if (grid[p.x][p.y].getClass() == original)
return Critter.Neighbor.SAME;
else
return Critter.Neighbor.OTHER;
}
@SuppressWarnings("unchecked")
public void update() {
simulationCount++;
Object[] list = info.keySet().toArray();
Collections.shuffle(Arrays.asList(list));
// This keeps track of critters that are locked and cannot be
// infected this turn. The happens when:
// * a Critter is infected
// * a Critter hops
Set<Critter> locked = new HashSet<Critter>();
for (int i = 0; i < list.length; i++) {
Critter next = (Critter)list[i];
PrivateData data = info.get(next);
if (data == null) {
// happens when creature was infected earlier in this round
continue;
}
Point p = data.p;
Point p2 = pointAt(p, data.direction);
// try to perform the critter's action
Critter.Action move = next.getMove(getInfo(data, next.getClass()));
if (move == Critter.Action.LEFT)
data.direction = rotate(rotate(rotate(data.direction)));
else if (move == Critter.Action.RIGHT)
data.direction = rotate(data.direction);
else if (move == Critter.Action.HOP) {
if (inBounds(p2) && grid[p2.x][p2.y] == null) {
grid[p2.x][p2.y] = grid[p.x][p.y];
grid[p.x][p.y] = null;
data.p = p2;
locked.add(next); //successful hop locks a critter from
// being infected for the rest of the
// turn
}
} else if (move == Critter.Action.INFECT) {
if (inBounds(p2) && grid[p2.x][p2.y] != null
&& grid[p2.x][p2.y].getClass() != next.getClass()
&& !locked.contains(grid[p2.x][p2.y])) {
Critter other = grid[p2.x][p2.y];
// remember the old critter's private data
PrivateData oldData = info.get(other);
// then remove that old critter
String c1 = other.getClass().getName();
critterCount.put(c1, critterCount.get(c1) - 1);
String c2 = next.getClass().getName();
critterCount.put(c2, critterCount.get(c2) + 1);
info.remove(other);
// and add a new one to the grid
try {
grid[p2.x][p2.y] = makeCritter(next.getClass());
// This critter has been infected and is now locked
// for the rest of this turn
locked.add(grid[p2.x][p2.y]);
} catch (Exception e) {
throw new RuntimeException("" + e);
}
// and add to the map
info.put(grid[p2.x][p2.y], oldData);
}
}
}
updateColorString();
}
// calling this method causes each critter to update the stored color and
// text for toString; should be called each time update is performed and
// once before the simulation begins
public void updateColorString() {
for (Critter next : info.keySet()) {
info.get(next).color = next.getColor();
info.get(next).string = next.toString();
}
}
public Set<Map.Entry<String, Integer>> getCounts() {
return Collections.unmodifiableSet(critterCount.entrySet());
}
public int getSimulationCount() {
return simulationCount;
}
private class PrivateData {
public Point p;
public Critter.Direction direction;
public Color color;
public String string;
public PrivateData(Point p, Critter.Direction d) {
this.p = p;
this.direction = d;
}
public String toString() {
return p + " " + direction;
}
}
// an object used to query a critter's state (neighbors, direction)
private static class Info implements CritterInfo {
private Critter.Neighbor[] neighbors;
private Critter.Direction direction;
private Critter.Direction[] neighborDirections;
public Info(Critter.Neighbor[] neighbors, Critter.Direction d,
Critter.Direction[] neighborDirections) {
this.neighbors = neighbors;
this.direction = d;
this.neighborDirections = neighborDirections;
}
public Critter.Neighbor getFront() {
return neighbors[0];
}
public Critter.Neighbor getBack() {
return neighbors[2];
}
public Critter.Neighbor getLeft() {
return neighbors[3];
}
public Critter.Neighbor getRight() {
return neighbors[1];
}
public Critter.Direction getDirection() {
return direction;
}
public Critter.Direction getFrontDirection() {
return neighborDirections[0];
}
public Critter.Direction getBackDirection() {
return neighborDirections[2];
}
public Critter.Direction getLeftDirection() {
return neighborDirections[3];
}
public Critter.Direction getRightDirection() {
return neighborDirections[1];
}
}
}

View File

@ -0,0 +1,48 @@
// Class CritterPanel displays a grid of critters
import javax.swing.*;
import java.awt.Point;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class CritterPanel extends JPanel {
private CritterModel myModel;
private Font myFont;
private static boolean created;
public static final int FONT_SIZE = 12;
public CritterPanel(CritterModel model) {
// this prevents someone from trying to create their own copy of
// the GUI components
if (created)
throw new RuntimeException("Only one world allowed");
created = true;
myModel = model;
// construct font and compute char width once in constructor
// for efficiency
myFont = new Font("Monospaced", Font.BOLD, FONT_SIZE + 4);
setBackground(Color.CYAN);
setPreferredSize(new Dimension(FONT_SIZE * model.getWidth() + 20,
FONT_SIZE * model.getHeight() + 20));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(myFont);
Iterator<Critter> i = myModel.iterator();
while (i.hasNext()) {
Critter next = i.next();
Point p = myModel.getPoint(next);
String appearance = myModel.getAppearance(next);
g.setColor(Color.BLACK);
g.drawString("" + appearance, p.x * FONT_SIZE + 11,
p.y * FONT_SIZE + 21);
g.setColor(myModel.getColor(next));
g.drawString("" + appearance, p.x * FONT_SIZE + 10,
p.y * FONT_SIZE + 20);
}
}
}

23
critters/FlyTrap.java Normal file
View File

@ -0,0 +1,23 @@
// This defines a simple class of critters that infect whenever they can and
// otherwise just spin around, looking for critters to infect. This simple
// strategy turns out to be surpisingly successful.
import java.awt.*;
public class FlyTrap extends Critter {
public Action getMove(CritterInfo info) {
if (info.getFront() == Neighbor.OTHER) {
return Action.INFECT;
} else {
return Action.LEFT;
}
}
public Color getColor() {
return Color.RED;
}
public String toString() {
return "T";
}
}

18
critters/Food.java Normal file
View File

@ -0,0 +1,18 @@
// This defines a simple class of critters that sit around waiting to be
// taken over by other critters.
import java.awt.*;
public class Food extends Critter {
public Action getMove(CritterInfo info) {
return Action.INFECT;
}
public Color getColor() {
return Color.GREEN;
}
public String toString() {
return "F";
}
}

63
critters/Giant.java Normal file
View File

@ -0,0 +1,63 @@
/*
*
* Matt Jensen
* CS145
* Lab 2 - Critters
* 4/23/19
* Partners: Melissa, Devante
*
*/
import java.awt.*;
import java.util.*;
public class Giant extends Critter {
private Color color = Color.GRAY;
private String symbol;
public int moveCount;
public Giant() {
}
public Color getColor() {
return this.color;
}
public String toString() {
this.changeSymbol();
return this.symbol;
}
private void changeSymbol() {
if( this.moveCount > 24 ) {
this.moveCount = 0;
}
if( this.moveCount <= 6) {
this.symbol = "FEE";
}
if( this.moveCount > 6 && this.moveCount <= 12 ) {
this.symbol = "FIE";
}
if ( this.moveCount > 12 && this.moveCount <= 18) {
this.symbol = "FOO";
}
if ( this.moveCount > 18 ) {
this.symbol = "FUM";
}
}
public Action getMove(CritterInfo info) {
this.moveCount++;
Neighbor front = info.getFront();
if( front == Neighbor.OTHER) {
return Action.INFECT;
}
if( front == Neighbor.EMPTY) {
return Action.HOP;
}
return Action.RIGHT;
}
}

72
critters/Lion.java Normal file
View File

@ -0,0 +1,72 @@
/*
*
* Matt Jensen
* CS145
* Lab 2 - Critters
* 4/23/19
* Partners: Melissa, Devante
*
*/
import java.awt.*;
import java.util.*;
public class Lion extends Critter {
private Color color = Color.BLUE;
private int moves = 0;
public Lion() {
this.changeColor();
}
public int getMoveCount() {
return this.moves;
}
public Color getColor() {
return this.color;
}
public String toString() {
return "L";
}
public void changeColor() {
if(this.getMoveCount() == 0 || this.getMoveCount() % 3 == 0) {
int rand = this.randomWithRange(0, 2);
if(rand == 0 ) {
this.color = Color.RED;
}
if(rand == 1 ) {
this.color = Color.GREEN;
}
if(rand == 2 ) {
this.color = Color.BLUE;
}
}
}
public Action getMove(CritterInfo info) {
this.moves = this.moves++;
this.changeColor();
Neighbor front = info.getFront();
if( front == Neighbor.OTHER ) { // check for enemy in front.
return Action.INFECT;
}
if ( front == Neighbor.WALL || info.getRight() == Neighbor.WALL ) { // check for obstacle.
return Action.LEFT;
}
if ( front == Neighbor.SAME ) { // check for obstacle.
return Action.RIGHT;
}
return Action.HOP;
}
public int randomWithRange(int min, int max)
{
int range = (max - min) + 1;
return (int)(Math.random() * range) + min;
}
}

54
critters/Orca.java Normal file
View File

@ -0,0 +1,54 @@
/*
*
* Matt Jensen
* CS145
* Lab 2 - Critters
* 4/23/19
* Partners: Melissa, Devante
*
*/
import java.awt.*;
import java.util.*;
public class Orca extends Critter {
private Color color = Color.BLACK;
private String symbol = "0-<";
public int moveCount;
public Orca() {
}
public Color getColor() {
if( this.color == Color.BLACK) {
this.color = Color.WHITE;
}
else{
this.color = Color.BLACK;
}
return this.color;
}
public String toString() {
return this.symbol;
}
public Action getMove(CritterInfo info) {
this.moveCount++;
Neighbor front = info.getFront();
if(front == Neighbor.OTHER) {
return Action.INFECT;
}
if(front == Neighbor.EMPTY) {
return Action.HOP;
}
if(front == Neighbor.WALL) {
return Action.RIGHT;
}
return Action.LEFT;
}
}