/*
* AP(r) Computer Science GridWorld Case Study:
* Copyright(c) 2005-2006 Cay S. Horstmann (http://horstmann.com)
*
* This code 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.
*
* This code 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.
*
* @author Cay Horstmann
*/
package info.gridworld.world;
import info.gridworld.grid.BoundedGrid;
import info.gridworld.grid.Grid;
import info.gridworld.grid.Location;
import info.gridworld.gui.WorldFrame;
import java.util.ArrayList;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.JFrame;
/**
*
* A <code>World</code> is the mediator between a grid and the GridWorld GUI.
* <br />
* This class is not tested on the AP CS A and AB exams.
*/
public class World<T>
{
private Grid<T> gr;
private Set<String> occupantClassNames;
private Set<String> gridClassNames;
private String message;
private JFrame frame;
private static Random generator = new Random();
private static final int DEFAULT_ROWS = 10;
private static final int DEFAULT_COLS = 10;
public World()
{
this(new BoundedGrid<T>(DEFAULT_ROWS, DEFAULT_COLS));
message = null;
}
public World(Grid<T> g)
{
gr = g;
gridClassNames = new TreeSet<String>();
occupantClassNames = new TreeSet<String>();
addGridClass("info.gridworld.grid.BoundedGrid");
addGridClass("info.gridworld.grid.UnboundedGrid");
}
/**
* Constructs and shows a frame for this world.
*/
public void show()
{
if (frame == null)
{
frame = new WorldFrame<T>(this);
frame.setVisible(true);
}
else
frame.repaint();
}
/**
* Gets the grid managed by this world.
* @return the grid
*/
public Grid<T> getGrid()
{
return gr;
}
/**
* Sets the grid managed by this world.
* @param newGrid the new grid
*/
public void setGrid(Grid<T> newGrid)
{
gr = newGrid;
repaint();
}
/**
* Sets the message to be displayed in the world frame above the grid.
* @param newMessage the new message
*/
public void setMessage(String newMessage)
{
message = newMessage;
repaint();
}
/**
* Gets the message to be displayed in the world frame above the grid.
* @return the message
*/
public String getMessage()
{
return message;
}
/**
* This method is called when the user clicks on the step button, or when
* run mode has been activated by clicking the run button.
*/
public void step()
{
repaint();
}
/**
* This method is called when the user clicks on a location in the
* WorldFrame.
*
* @param loc the grid location that the user selected
* @return true if the world consumes the click, or false if the GUI should
* invoke the Location->Edit menu action
*/
public boolean locationClicked(Location loc)
{
return false;
}
/**
* This method is called when a key was pressed. Override it if your world wants
* to consume some keys (e.g. "1"-"9" for Sudoku). Don't consume plain arrow keys,
* or the user loses the ability to move the selection square with the keyboard.
* @param description the string describing the key, in
* <a href="http://java.sun.com/javase/6/docs/api/javax/swing/KeyStroke.html#getKeyStroke(java.lang.String)">this format</a>.
* @param loc the selected location in the grid at the time the key was pressed
* @return true if the world consumes the key press, false if the GUI should
* consume it.
*/
public boolean keyPressed(String description, Location loc)
{
return false;
}
/**
* Gets a random empty location in this world.
* @return a random empty location
*/
public Location getRandomEmptyLocation()
{
Grid<T> gr = getGrid();
int rows = gr.getNumRows();
int cols = gr.getNumCols();
if (rows > 0 && cols > 0) // bounded grid
{
// get all valid empty locations and pick one at random
ArrayList<Location> emptyLocs = new ArrayList<Location>();
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
{
Location loc = new Location(i, j);
if (gr.isValid(loc) && gr.get(loc) == null)
emptyLocs.add(loc);
}
if (emptyLocs.size() == 0)
return null;
int r = generator.nextInt(emptyLocs.size());
return emptyLocs.get(r);
}
else
// unbounded grid
{
while (true)
{
// keep generating a random location until an empty one is found
int r;
if (rows < 0)
r = (int) (DEFAULT_ROWS * generator.nextGaussian());
else
r = generator.nextInt(rows);
int c;
if (cols < 0)
c = (int) (DEFAULT_COLS * generator.nextGaussian());
else
c = generator.nextInt(cols);
Location loc = new Location(r, c);
if (gr.isValid(loc) && gr.get(loc) == null)
return loc;
}
}
}
/**
* Adds an occupant at a given location.
* @param loc the location
* @param occupant the occupant to add
*/
public void add(Location loc, T occupant)
{
getGrid().put(loc, occupant);
repaint();
}
/**
* Removes an occupant from a given location.
* @param loc the location
* @return the removed occupant, or null if the location was empty
*/
public T remove(Location loc)
{
T r = getGrid().remove(loc);
repaint();
return r;
}
/**
* Adds a class to be shown in the "Set grid" menu.
* @param className the name of the grid class
*/
public void addGridClass(String className)
{
gridClassNames.add(className);
}
/**
* Adds a class to be shown when clicking on an empty location.
* @param className the name of the occupant class
*/
public void addOccupantClass(String className)
{
occupantClassNames.add(className);
}
/**
* Gets a set of grid classes that should be used by the world frame for
* this world.
* @return the set of grid class names
*/
public Set<String> getGridClasses()
{
return gridClassNames;
}
/**
* Gets a set of occupant classes that should be used by the world frame for
* this world.
* @return the set of occupant class names
*/
public Set<String> getOccupantClasses()
{
return occupantClassNames;
}
private void repaint()
{
if (frame != null)
frame.repaint();
}
/**
* Returns a string that shows the positions of the grid occupants.
*/
public String toString()
{
String s = "";
Grid<?> gr = getGrid();
int rmin = 0;
int rmax = gr.getNumRows() - 1;
int cmin = 0;
int cmax = gr.getNumCols() - 1;
if (rmax < 0 || cmax < 0) // unbounded grid
{
for (Location loc : gr.getOccupiedLocations())
{
int r = loc.getRow();
int c = loc.getCol();
if (r < rmin)
rmin = r;
if (r > rmax)
rmax = r;
if (c < cmin)
cmin = c;
if (c > cmax)
cmax = c;
}
}
for (int i = rmin; i <= rmax; i++)
{
for (int j = cmin; j < cmax; j++)
{
Object obj = gr.get(new Location(i, j));
if (obj == null)
s += " ";
else
s += obj.toString().substring(0, 1);
}
s += "\n";
}
return s;
}
}