package mapmaker;
import java.util.*;
import java.awt.*;
import java.lang.*;
import mapmaker.mapcmd.*;
import util.*;
/** the Model in mapmaker's MVC pattern
*/
public class ConcreteMap implements Map {
Room[][] rooms;
Link[] links;
Room selected;
Text desc;
Hashtable roomPos; // used to get the position of a room
BufferedObservable bufObs;
MapFactory factory;
/** @param sizeX the number of rooms stored in width
* @param sizeY you figure it out ;)
*/
public ConcreteMap(int sizeX, int sizeY, Observable obs) {
rooms = new Room[sizeX][sizeY];
roomPos = new Hashtable(sizeX * sizeY);
links = new Link[0]; // not links = null !!!
factory = new MapFactory();
bufObs = new BufferedObservable(obs);
} // Map
/** deletes all rooms and links and the room description
*/
protected void clearMap() {
Dimension size = getSize();
rooms = new Room[size.width][size.height];
roomPos.clear();
links = new Link[0]; // not links = null !!!
selected = null;
desc = null;
} // clearMap
/** executes the given command from the mapmaker.mapcmd package;
* the following commands are supported:
* CompositeCommand,
* RoomCommand,
* CmdNewRoom,
* CmdKillRoom,
* CmdSelectRoom,
* CmdUnselectRoom,
* CmdSwapRooms,
* CmdLinkRooms,
* CmdClearMap,
* CmdSetSize,
* CmdSetDesc;
* example: map.execute(new CmdNewRoom(new Point(x, y)));
* notifies Observers only after the complete command is
* executed in case of CompositeCommands
*/
public void execute(MapCommand cmd) {
bufObs.delayNotify();
executeWithoutNotify(cmd);
// make sure some notify occurs
notifyOfChange(null);
bufObs.fireNotify();
} // execute
protected void executeWithoutNotify(MapCommand cmd) {
if (cmd instanceof CompositeCommand) {
Iterator it = ((CompositeCommand)cmd).iterator();
while (it.hasNext())
executeWithoutNotify((MapCommand)it.next());
return;
}
if (cmd instanceof RoomCommand) {
Room target = getRoom(((RoomCommand)cmd).mapPos);
if (target != null)
target.execute((RoomCommand)cmd);
return;
}
if (cmd instanceof CmdNewRoom) {
newRoom(((CmdNewRoom)cmd).pos);
return;
}
if (cmd instanceof CmdKillRoom) {
killRoom(((CmdKillRoom)cmd).pos);
return;
}
if (cmd instanceof CmdSelectRoom) {
selectRoom(((CmdSelectRoom)cmd).pos);
return;
}
if (cmd instanceof CmdUnselectRoom) {
unselectRoom();
return;
}
if (cmd instanceof CmdSwapRooms) {
swapRooms(((CmdSwapRooms)cmd).pos1,
((CmdSwapRooms)cmd).pos2);
return;
}
if (cmd instanceof CmdLinkRooms) {
CmdLinkRooms cmdLink = (CmdLinkRooms)cmd;
if (cmdLink.autoLink)
linkRooms(cmdLink.pos1, cmdLink.pos2);
else {
Room room1 = getRoom(cmdLink.pos1);
Room room2 = getRoom(cmdLink.pos2);
if (room1 == null || room2 == null)
return;
linkRooms(room1, cmdLink.exit1, room2, cmdLink.exit2);
}
return;
}
if (cmd instanceof CmdClearMap) {
clearMap();
return;
}
if (cmd instanceof CmdSetSize) {
setSize(((CmdSetSize)cmd).dim);
return;
}
if (cmd instanceof CmdSetDesc) {
setDesc(((CmdSetDesc)cmd).desc);
return;
}
} // executeWithoutNotify
/** makes a deep-copy of the given map's data (Rooms and Links only)
* and replaces its own data with it
* @original the map to be copied from
*/
public void deepCopyFrom(Map original) {
// copy rooms
roomPos.clear();
Dimension dim = original.getSize();
rooms = new Room[dim.width][dim.height];
Room[] orgRooms = original.getRooms();
for (int i = 0; i < orgRooms.length; i++) {
Point pos = orgRooms[i].getPos();
Room newRoom = factory.createRoom(this);
newRoom.deepCopyFrom(orgRooms[i]);
setRoom(pos, newRoom);
}
// copy selected
Room orgSelected = original.getSelected();
if (orgSelected == null)
selected = null;
else
selected = getRoom(orgSelected.getPos());
// copy links - must be AFTER rooms copied
Link[] orgLinks = original.getLinks();
links = new Link[orgLinks.length];
for (int i = 0; i < orgLinks.length; i++) {
links[i] = factory.createLink(this, orgLinks[i]);
}
// copy description
desc = (Text)original.getDesc().clone();
notifyOfChange(MapEvent.ChangeAll);
} // deepCopyFrom
public Room getRoom(Point pos) {
return rooms[pos.x][pos.y];
} // getRoom
void setRoom(Point pos, Room room) {
// sets rooms[pos] to room,
// automatically validates hashtable
// check if room already stored -> exception
if (room != null && roomPos.containsKey(room))
throw new IllegalArgumentException();
if (rooms[pos.x][pos.y] != null)
roomPos.remove(rooms[pos.x][pos.y]);
rooms[pos.x][pos.y] = room;
if (room != null)
// use a clone to ensure that memorized object stays constant
roomPos.put(room, new Point(pos));
} // setRoom
/** returns the map position of room
*/
public Point getRoomPos(Room room) {
// use Hashtable to speed things up
// check for invalid hashtable too
Point pos = (Point)roomPos.get(room);
// use a clone to ensure that memorized object stays constant
pos = new Point(pos.x, pos.y);
return pos;
} // getRoomPos
/** updates the Hashtable storing the room positions
*/
protected void updateRoomPos() {
Dimension dim = getSize();
roomPos.clear();
for (int x = 0; x < dim.width; x++)
for (int y = 0; y < dim.height; y++)
if (rooms[x][y] != null)
roomPos.put(rooms[x][y], new Point(x, y));
} // updateRoomPos
/** creates a new Room if the position is empty
* @param pos position on map where to create the new Room
*/
protected void newRoom(Point pos) {
if (getRoom(pos) == null) {
setRoom(pos, factory.createRoom(this));
notifyOfChange(MapEvent.ChangeRoom);
}
} // newRoom
/** removes the room at give position if there is one
*/
protected void killRoom(Point pos) {
if (getRoom(pos) != null) {
getRoom(pos).suicide();
notifyOfChange(MapEvent.ChangeRoom);
}
} // killRoom
/** returns the room currently selected
*/
public Room getSelected() {
return selected;
} // getSelected
protected void selectRoom(Point pos) {
selected = getRoom(pos);
notifyOfChange(MapEvent.ChangeSelect);
} // selectRoom
protected void unselectRoom() {
selected = null;
notifyOfChange(MapEvent.ChangeSelect);
} // unselectRoom
/** returns the map's description
*/
public Text getDesc() {
if (desc != null)
return (Text)desc.clone();
else
return null;
} // getDesc
/** sets the room's description without notifying observers;
* used by DescViewer, others use CmdSetDesc
*/
public void setDesc(Text desc) {
if (desc != null)
this.desc = (Text)desc.clone();
else
desc = null;
} // setDesc
/* swaps the rooms at the given positions
* ('moves' a room if one position is empty)
*/
protected void swapRooms(Point pos1, Point pos2) {
// swap the rooms at given positions
Room room1 = getRoom(pos1);
Room room2 = getRoom(pos2);
if (room1 != null || room2 != null) {
// make sure that no room is stored twice at any give time
// messes up hashtable
setRoom(pos2, null);
setRoom(pos1, room2);
setRoom(pos2, room1);
notifyOfChange(MapEvent.ChangeRoom);
}
} // swapRooms
int getLinkIndex(Link link) {
for (int i = 0; i < links.length; i++)
if (links[i] == link)
return i;
throw new IllegalArgumentException();
} // getLinkIndex
/** should only be called by Link objects committing suicide
*/
public void removeLink(Link link) {
// removes link from the links list
// called by link when it self-destructs
int index = getLinkIndex(link);
Link[] linkBuf = new Link[links.length - 1];
for (int i = 0; i < linkBuf.length; i++)
linkBuf[i] = links[i];
if (index < linkBuf.length)
linkBuf[index] = links[linkBuf.length];
links = linkBuf;
} // removeLink
/** should only be called by Room objects committing suicide
*/
public void removeRoom(Room room) {
// removes room from the rooms array
// called by room when it self-destructs
setRoom(getRoomPos(room), null);
// check if selected
if (selected == room)
selected = null;
} // removeRoom
/** notifys the map's Observers of a certain change
* @param change passed on to Observers
*/
public void notifyOfChange(Object change) {
bufObs.notifyObservers(change);
} // notifyOfChange
/** creates a link between the given rooms at give exits
*/
protected void linkRooms(Room room1, int exit1, Room room2, int exit2) {
if (room1.exitLinked(exit1) || room2.exitLinked(exit2))
throw new IllegalArgumentException();
else {
Link newLink = factory.createLink(this, room1, exit1, room2, exit2);
Link[] linkBuf = new Link[links.length + 1];
for (int i = 0; i < links.length; i++)
linkBuf[i] = links[i];
linkBuf[links.length] = newLink;
links = linkBuf;
notifyOfChange(MapEvent.ChangeLink);
}
} // linkRooms
/** creates a link between the given rooms
* automatically chooses exits
*/
protected void linkRooms(Room room1, Room room2) {
// automatically choose exit1 and exit2
if (room1 == null || room2 == null)
return;
int exit1 = room1.bestFreeExitTo(room2);
int exit2 = room2.bestFreeExitTo(room1);
if (exit1 == -1 || exit2 == -1)
return;
// try to make exit1 and exit2 oppose each other
int opp1 = Dir.opposite(exit1);
int opp2 = Dir.opposite(exit2);
if (exit1 != opp2) {
boolean opp1free = !room2.exitLinked(opp1);
boolean opp2free = !room1.exitLinked(opp2);
if (opp1free)
exit2 = opp1;
else
if (opp2free)
exit1 = opp2;
}
linkRooms(room1, exit1, room2, exit2);
} // linkRooms
/** links rooms at given positions if there are any
*/
protected void linkRooms(Point pos1, Point pos2) {
linkRooms(getRoom(pos1), getRoom(pos2));
} // linkRooms
/** returns an array containing all rooms of the map
*/
public Room[] getRooms() {
// count number of rooms
int roomNr = 0;
for (int x = 0; x < rooms.length; x++)
for (int y = 0; y < rooms[x].length; y++)
if (rooms[x][y] != null)
roomNr++;
// create array containing rooms
Room[] roomArray = new Room[roomNr];
int roomArrayPos = 0;
for (int x = 0; x < rooms.length; x++)
for (int y = 0; y < rooms[x].length; y++)
if (rooms[x][y] != null)
roomArray[roomArrayPos++] = rooms[x][y];
return roomArray;
} // getRooms
/** returns an array containing all link of the map
*/
public Link[] getLinks() {
return links;
} // getLinks
/** returns the Dimension of the map in terms of room numbers
*/
public Dimension getSize() {
int height = 0;
if (rooms.length > 0)
height = rooms[0].length;
return new Dimension(rooms.length, height);
} // getSize
/** changes the map's size to given dimensions if that is possible
* without deleting rows or collums containing rooms; otherwise
* reduces size to the minimum size possible without performing
* such deletions
*/
protected void setSize(Dimension dim) {
Dimension oldDim = getSize();
// check which columns and rows are empty
boolean[] rowEmpty = new boolean[oldDim.height];
boolean[] columnEmpty = new boolean[oldDim.width];
// check rows
for (int row = 0; row < rowEmpty.length; row++) {
rowEmpty[row] = true;
for (int i = 0; i < oldDim.width; i++)
if (rooms[i][row] != null)
rowEmpty[row] = false;
}
// check columns
for (int column = 0; column < columnEmpty.length; column++) {
columnEmpty[column] = true;
for (int i = 0; i < oldDim.height; i++)
if (rooms[column][i] != null)
columnEmpty[column] = false;
}
// calculate resize information
int[] YIndex = Compressor.compress(rowEmpty, dim.height);
int[] XIndex = Compressor.compress(columnEmpty, dim.width);
Room[][] newRooms = new Room[XIndex.length][YIndex.length];
// copy rooms into new room array
for (int x = 0; x < XIndex.length; x++)
if (XIndex[x] != -1)
for (int y = 0; y < YIndex.length; y++)
if (YIndex[y] != -1)
newRooms[x][y] = rooms[XIndex[x]][YIndex[y]];
// set new data
rooms = newRooms;
updateRoomPos();
notifyOfChange(MapEvent.ChangeAll);
} // setSize
} // ConcreteMap