1 /*
2  *  CVS: $Id: GameMap.java,v 1.12 2004/07/25 19:07:20 marcus Exp $
3  * 
4  *  This file is part of JZuul.
5  *
6  *  JZuul is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10 *
11 *  JZuul is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU General Public License for more details.
15 *
16 *  You should have received a copy of the GNU General Public License
17 *  along with Zuul; if not, write to the Free Software
18 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 * 
20 *  Copyrigth 2004 by marcus, leh
21 * 
22 */
23
24package org.jzuul.engine;
25
26import java.io.InputStream;
27import java.lang.reflect.Constructor;
28import java.lang.reflect.InvocationTargetException;
29import java.util.HashMap;
30import java.util.Iterator;
31import java.util.List;
32import java.util.Map;
33import java.util.Random;
34import java.util.Vector;
35
36import org.jdom.Element;
37import org.jzuul.engine.exceptions.ConnectAllRoomsFailed;
38import org.jzuul.engine.exceptions.NoSuchRoomException;
39import org.jzuul.engine.rooms.*;
40
41/**
42 * Diese Klasse bildet die Karte (das Raumgeflecht in dem sich der Spieler bewegen kann)
43 * in einem Spiel ab.
44 * Da durch die Struktur der Spieldateien es zulässt, das Wege ohne Room Objekte angelegt werden
45 * werden die Wege in einer Queue ähnlichen Struktur gehalten und erst zum schluss angelegt.
46 * 
47 * 
48 * @version $Revision: 1.12 $
49 */
50public class GameMap {
51    /**
52     * Diese Klasse implementiert einen in der Queue gehaltenen Weg.
53     * 
54     * 
55     */
56    private class QueueItem {
57        /**
58         * der Name des Raumes zu dem dieser Weg führt.
59         */
60        public String ofName;
61        /**
62         * die Richtung, ausgehend von dem Quellraum, in der der Raum liegt
63         */
64        int direction;
65
66        /**
67         * Erstellt einen neuen Weg für die Queue
68         * 
69         * @param direction die Richtung in der dieser Raum liegt
70         * @param ofName    der Name des Raumes
71         */
72        public QueueItem(int direction, String ofName) {
73            this.direction = direction;
74            this.ofName = ofName;
75        }
76    }
77
78    /**
79     * Der interne speicher für die Karte, enthällt Raumname auf Room Objekt mappings.
80     */
81    protected Map gameMap;
82    /**
83     * Der Name des Startraumes für diese Karte
84     */
85    protected String startRoom;
86    /**
87     * Die Queue der Wege die noch angelegt werden müssen. Enthällt Ausgangsraum auf QueueItem
88     * Mappings.
89     */
90    protected Map connectQueue;
91    /**
92     * Die Liste der NPCs die sich in den Räumen aufhalten.
93     */
94    protected List npcList;
95    /**
96     * Der Name dieser Karte
97     */
98    protected String name;
99
00    /**
01     * Erstell ein neues GameMap Objekt
02     * 
03     * @param name  der Name dieser Karte
04     */
05    public GameMap(String name) {
06        this.name = name;
07        gameMap = new HashMap();
08        connectQueue = new HashMap();
09        npcList = new Vector();
10    }
11
12    /**
13     * Fügt einen neuen Raum und einen Weg in die Karte ein.
14     * 
15     * @param name              der Name des neuen Raumes
16     * @param description       die Beschreibung des neuen Raumes
17     * @param direction         ein Richtung in der ein anderer Raum liegt
18     * @param ofRoom            der Name des Raumes der in dieser Richtung liegt
19     * @see org.jzuul.engine.Directions
20     */
21    public void addRoom(String name, String description, int direction, String ofRoom) {
22        this.addRoom(name, description);
23        this.enqueueWay(name, direction, ofRoom);
24    }
25
26    /**
27     * Fügt ein neues Room Objekt der Karte hinzu.
28     * Für diesen Raum müssen später noch Wege angelegt werden!
29     * 
30     * @param raum  ein Room Objekt das der Karte hinzugefügt werden soll.
31     */
32    public void addRoom(Room raum) {
33        gameMap.put(raum.getName(), raum);
34    }
35
36    /**
37     * Fügt einen Room Objekt und einen Weg der Karte hinzu
38     * 
39     * @param raum          das Room Objekt das hinzugefügt werden soll
40     * @param direction     die Richtung in der ein anderer Raum liegt
41     * @param ofRoom        der Name des Raumes der in dieser Richtung liegt
42     * @see org.jzuul.engine.Directions
43     */
44    public void addRoom(Room raum, int direction, String ofRoom) {
45        this.addRoom(raum);
46        this.enqueueWay(raum.getName(), direction, ofRoom);
47    }
48
49    /**
50     * Fügt einen Raum der Karte hinzu.
51     * Für diesen Raum müssen später noch Wege angelegt werden!
52     * 
53     * @param name              der Name des neuen Raums
54     * @param description       die Beschreibung des neuen Raums
55     */
56    public void addRoom(String name, String description) {
57        addRoom(new Room(name, description));
58    }
59
60    /**
61     * Fügt einen neuen typisierten Raum zu der Karte hinzu.
62     * Für diesen Raum müssen später noch Wege angelegt werden!
63     * 
64     * @param name                  der Name des neuen Raumes
65     * @param description           die Beschreibung des neuen Raumes
66     * @param typeOfRoom        der Typ (die Klasse) des neuen Raumes
67     * @see org.jzuul.engine.rooms
68     */
69    public void addRoom(String name, String description, String typeOfRoom) {
70        try {
71            Class classDefinition = Class.forName(typeOfRoom);
72            Class[] types = new Class[] { String.class, String.class };
73            Constructor cons = classDefinition.getConstructor(types);
74            Object[] args = new Object[] { name, description };
75
76            gameMap.put(name, cons.newInstance(args));
77        } catch (InstantiationException e) {
78            System.out.println(e);
79        } catch (IllegalAccessException e) {
80            System.out.println(e);
81        } catch (ClassNotFoundException e) {
82            System.out.println(e);
83        } catch (IllegalArgumentException e) {
84            e.printStackTrace();
85        } catch (InvocationTargetException e) {
86            e.printStackTrace();
87        } catch (SecurityException e) {
88            e.printStackTrace();
89        } catch (NoSuchMethodException e) {
90            e.printStackTrace();
91        }
92    }
93
94    /**
95     * Zugriff auf einzelne Room Objekte
96     * 
97     * @param name  der Name eines Raumes
98     * @return das Room Objekt falls der Raum existiert, null sonst
99     */
00    public Room getRoom(String name) {
01        if (gameMap.containsKey(name)) {
02            return (Room) gameMap.get(name);
03        } else {
04            return null;
05        }
06    }
07
08    /**
09     * Überpüft ob schon ein Weg zwischen Raum 1 und Raum 2 in der Queue existiert.
10     * 
11     * @param room1 der Name eines Raumes
12     * @param room2 der Name eines Raumes
13     * @return true wenn es einen Weg in der Queue von room1 nach room2 gibt, false sonst.
14     */
15    private boolean connectionExists(String room1, String room2) {
16        if (connectQueue.containsKey(room1)) {
17            Vector v = (Vector) connectQueue.get(room1);
18            for (Iterator i = v.iterator(); i.hasNext();) {
19                if (((QueueItem) i.next()).ofName.equals(room2)) {
20                    return true;
21                }
22            }
23        }
24        return false;
25    }
26
27    /**
28     * Überprüft bidirektional ob schon ein Weg zwischen beiden Räumen in der Queue existiert.
29     * 
30     * @param room1 der Name eines Raumes
31     * @param room2 der Name eines Raumes
32     * @return  true wenn es einen Weg zwischen den beiden Räumen in der Queue gibt, false sonst
33     */
34    private boolean wayExists(String room1, String room2) {
35        return connectionExists(room1, room2) || connectionExists(room2, room1);
36    }
37
38    /**
39     * Fügt einen Weg in die Wegequeue ein falls dieser noch nicht existiert.
40     * 
41     * @param name                  der Name des Ausgangsraumes
42     * @param direction             die Richtung in der der Weg liegt
43     * @param ofRoomName    der Name des Zielraumes.
44     */
45    public void enqueueWay(String name, int direction, String ofRoomName) {
46        if (wayExists(name, ofRoomName)) {
47            return;
48        }
49
50        if (connectQueue.containsKey(name)) {
51            ((Vector) connectQueue.get(name)).add(new QueueItem(direction, ofRoomName));
52        } else {
53            Vector v = new Vector();
54            v.add(new QueueItem(direction, ofRoomName));
55            connectQueue.put(name, v);
56        }
57    }
58
59    /**
60     * Erstellt alle Verbindungen für den gegebenen Raum.
61     * Die Räume für die Verbindungen erstellt wurden werden aus der Liste gelöscht.
62     * 
63     * @param name  der Name des Raumes bei dem begonnen werden soll
64     * @param seen  die Liste der noch nicht besuchten Räume
65     */
66    protected void processQueue(String name, List seen) {
67        if (connectQueue.containsKey(name)) {
68            Vector queueItems = (Vector) connectQueue.get(name);
69            if (seen.contains(name)) {
70                seen.remove(name);
71                for (Iterator i = queueItems.iterator(); i.hasNext();) {
72                    QueueItem qi = (QueueItem) i.next();
73                    connect(name, qi.direction, qi.ofName);
74                    processQueue(qi.ofName, seen);
75                }
76            }
77            connectQueue.remove(name);
78        }
79    }
80
81    /**
82     * Macht einen Verbindungsdurchlauf.
83     * Alle in der Wegequeue vorhandenen Wege werden angelegt.
84     *
85     */
86    public void processQueue() {
87        List seen = new Vector(gameMap.keySet());
88        for (Iterator i = this.gameMap.keySet().iterator(); i.hasNext();) {
89            this.processQueue((String) i.next(), seen);
90        }
91    }
92
93    /**
94     * Erstellt eine bidirektionale Verbindung zwischen zwei Räumen.
95     * 
96     * @param name          der Name des ersten Raumes
97     * @param direction     die Richtung in der der Weg liegt
98     * @param ofName        der Name des zweiten Raumes
99     * @see org.jzuul.engine.Directions
00     */
01    protected void connect(String name, int direction, String ofName) {
02        Room room = getRoom(name);
03        Room ofRoom = getRoom(ofName);
04
05        if (ofRoom == null) {
06            return;
07        }
08        if (room == null) {
09            return;
10        }
11
12        switch (direction) {
13            case Directions.NORTH_OF :
14                room.setExitByDirection(Directions.SOUTH, ofRoom);
15                ofRoom.setExitByDirection(Directions.NORTH, room);
16                break;
17            case Directions.EAST_OF :
18                room.setExitByDirection(Directions.WEST, ofRoom);
19                ofRoom.setExitByDirection(Directions.EAST, room);
20                break;
21            case Directions.SOUTH_OF :
22                room.setExitByDirection(Directions.NORTH, ofRoom);
23                ofRoom.setExitByDirection(Directions.SOUTH, room);
24                break;
25            case Directions.WEST_OF :
26                room.setExitByDirection(Directions.EAST, ofRoom);
27                ofRoom.setExitByDirection(Directions.WEST, room);
28                break;
29            case Directions.TOP_OF :
30                room.setExitByDirection(Directions.BELOW, ofRoom);
31                ofRoom.setExitByDirection(Directions.TOP, room);
32                break;
33            case Directions.BELOW_OF :
34                room.setExitByDirection(Directions.TOP, ofRoom);
35                ofRoom.setExitByDirection(Directions.BELOW, room);
36                break;
37        }
38    }
39
40    /**
41     * Fügt ein GameObject Objekt in einen Raum ein.
42     * 
43     * @param name  der Name des Raumes
44     * @param object    das GameObject Objekt das in den Raum eingefügt werden soll.
45     */
46    public void addItemToRoom(String name, GameObject object) throws NoSuchRoomException {
47        if (gameMap.containsKey(name)) {
48            ((Room) gameMap.get(name)).getContent().addGameObject(object);
49        } else {
50            throw new NoSuchRoomException("Raum " + name + " existiert nicht!");
51        }
52
53    }
54
55    /**
56     * Fügt ein Item Objekt in einen Raum ein
57     * 
58     * @param name              der Name des Raumes
59     * @param itemName      der Name des Item Objektes
60     * @deprecated Sollte nicht mehr funktionieren, da die Methode versucht das Objekt zu instanzieeren
61     */
62    public void addItemToRoom(String name, String itemName) throws NoSuchRoomException {
63        if (gameMap.containsKey(name)) {
64            Object object = null;
65            try {
66                Class classDefinition = Class.forName(itemName);
67                object = classDefinition.newInstance();
68                addItemToRoom(name, (GameObject) object);
69            } catch (InstantiationException e) {
70                System.out.println(e);
71            } catch (IllegalAccessException e) {
72                System.out.println(e);
73            } catch (ClassNotFoundException e) {
74                System.out.println(e);
75            }
76        } else {
77            throw new NoSuchRoomException("Raum " + name + " does not exist!");
78        }
79
80    }
81
82    /**
83     * Fügt eine Beschreibung für eine Himmelsrichtung einem Raum hinzu.
84     * 
85     * @param name  der Name des Raumes
86     * @param direction die Himmelrichtung
87     * @param description   die Beschreibung
88     * @see org.jzuul.engine.Directions
89     */
90    public void setWayDescription(String name, int direction, String description) {
91        Room r = getRoom(name);
92
93        r.setWayDescription(direction, description);
94
95    }
96    
97    /**
98     * Setzt die Beschreibung für alle Himmelrichtungen in einem bestimmten Raum
99     * 
00     * @param name  der Name des Raumes
01     * @param north die Beschreibung für Directions.NORTH
02     * @param east      die Beschreibung für Directions.EAST
03     * @param south die Beschreibung für Directions.SOUTH
04     * @param west      die Beschreibung für Directions.WEST
05     * @deprecated Benutzen wir das irgendwo?
06     */
07    public void setWayDescription(String name, String north, String east, String south, String west) {
08        Room r = getRoom(name);
09        r.setWayDescription(Directions.NORTH, north);
10        r.setWayDescription(Directions.EAST, east);
11        r.setWayDescription(Directions.SOUTH, south);
12        r.setWayDescription(Directions.WEST, west);
13
14    }
15
16    /**
17     * Testet übergänge von einem Raum in andere.
18     * Die Räume für die Übergange existieren werden aus der Liste gelöscht.
19     * 
20     * @param raum  der Raum von dem aus die Übergange getestet werden sollen
21     * @param seen  eine Liste von noch nicht besuchten Räumen.
22     */
23    public void verifyMap(Room raum, List seen) {
24        List ways = raum.getWays();
25        seen.remove(raum);
26        for (Iterator i = ways.iterator(); i.hasNext();) {
27            Room r = (Room) i.next();
28            if (seen.contains(r)) {
29                verifyMap(r, seen);
30            }
31        }
32    }
33
34    /**
35     * Versucht einen kompletten Durchlauf durch die Karte und überpüft ob alle
36     * Räume erreicht werden können.
37     * 
38     * @return  True falls alle Räume erreicht werden können, false sonst
39     * @throws NoSuchRoomException falls ein Raum besucht werden soll der nicht existiert
40     */
41    public boolean verifyMap() throws NoSuchRoomException, ConnectAllRoomsFailed{
42        Vector seen = new Vector(gameMap.values());
43        if (this.startRoom == null) {
44            Room roomObj = getRandomRoom();
45            if (roomObj == null) {
46                throw new NoSuchRoomException("Can not get any Room for map " + name + ". Does this Map have rooms?");
47            }
48            startRoom = roomObj.getName();
49        }
50        
51        this.processQueue();
52        Room sraum = this.getRoom(startRoom);
53        if (sraum == null)
54            throw new NoSuchRoomException("Raum " + startRoom + " does not exist!");
55        verifyMap(sraum, seen);
56        if (!this.connectQueue.isEmpty()) {
57            String roomNames = "";
58            for (Iterator i = connectQueue.keySet().iterator(); i.hasNext();) {
59                roomNames += ((String) i.next()) + "\n";
60            }
61            throw new NoSuchRoomException("Missing Rooms in Map " + name + ":\n" + roomNames);
62        }
63
64        if (seen.isEmpty()) {
65            return true;
66        } else {
67            String error = "Non reachable rooms in map " + name + ":\n";
68            for (Iterator i = seen.iterator(); i.hasNext();) {
69                error += (((Room) i.next()).getName()) + "\n";
70            }
71            throw new ConnectAllRoomsFailed(error);
72            
73        }
74    }
75
76    /**
77     * Setzt den Startraum für diese Karte
78     * 
79     * @param name  der Name des Startraumes
80     */
81    public void setStartRoom(String name) {
82        this.startRoom = name;
83    }
84
85    /**
86     * Wandelt die Karte in ein JDOM XML Element um.
87     * 
88     * @return  ein JDOM XML Element das den aktuellen Stand der Karte wiederspiegelt
89     */
90    public Element toElement() {
91        Element map = new Element("map");
92        map.setAttribute("name", getName());
93        map.setAttribute("startroom", getStartRoom());
94
95        for (Iterator i = this.gameMap.values().iterator(); i.hasNext();) {
96            map.addContent(((Room) i.next()).toElement());
97        }
98
99        return map;
00    }
01
02    /**
03     * Gibt einen beliebigen Raum aus der Karte zurück
04     * 
05     * @return  ein beliebiger Raum aus der Karte
06     */
07    public Room getRandomRoom() {
08        Vector v = new Vector(this.gameMap.values());
09        if (this.gameMap.size()>0) {
10            return (Room) v.get(new Random().nextInt(this.gameMap.size()));
11        } else {
12            return null;
13        }
14    }
15
16    /**
17     * Weist einem Raum eine Inventory Objekt zu.
18     * 
19     * @param raum  der Name des Raumes
20     * @param inv       das Inventory Objekt das dem Raum gesetzt werden soll
21     */
22    public void addInvToRoom(String raum, Inventory inv) throws NoSuchRoomException {
23        this.npcList.addAll(inv.getCharacterObjects());
24        if (getRoom(raum) == null) {
25            throw new NoSuchRoomException("The room " + raum + " does not exist");
26        } else {
27            getRoom(raum).setInv(inv);
28        }
29    }
30
31    /**
32     * Gibt eine Liste der Character Objekte die sich in den Räumen befinden zurück
33     * 
34     * @return  Liste von Character Objekten
35     */
36    public List getNpcList() {
37        return this.npcList;
38    }
39
40    /**
41     * Fügt einen {@link org.jzuul.engine.rooms.TransitionRoom} in die Karte ein.
42     * 
43     * @param name                  der Name des Raumes
44     * @param preconditions die Namen von GameObject Objekten die Vorbedingungen für einen Übergang sind
45     * @param isFinal                   ist das das Ende des Spieles?
46     * @param target                    der Name der Zielkarte
47     */
48    public void addTransRoom(String name, String[] preconditions, boolean isFinal, String target) {
49        TransitionRoom newRoom = new TransitionRoom(name, preconditions, isFinal, target);
50        this.addRoom(newRoom);
51    }
52
53    /**
54     * Zugriff auf den Namen der Karte.
55     * 
56     * @return  der Name der Karte
57     */
58    public String getName() {
59        return this.name;
60    }
61
62    /**
63     * Zugriff auf den Namen des Startraumes der Karte
64     * 
65     * @return der Name des Startraumes.
66     */
67    public String getStartRoom() {
68        return this.startRoom;
69    }
70
71    /**
72     * Zugriff auf das Room Objekt des Startraumes
73     * 
74     * @return  das Room Objekt des Startraume, null wenn kein Startraum gesetzt ist
75     */
76    public Room getStartRoomObj() {
77        return this.getRoom(this.startRoom);
78    }
79
80    /**
81     * Teilt allen Räumen in der Karte ein Event mit
82     * 
83     * @param eventId   eine Id eines Events
84     * @see org.jzuul.engine.Event
85     */
86    public void notifyRooms(int eventId) {
87        for (Iterator roomIter = gameMap.values().iterator(); roomIter.hasNext();) {
88            EventListener element = (EventListener) roomIter.next();
89            element.doEvent(eventId);
90        }
91    }
92
93    /**
94     * Setzt einem Raum einen EventHandler
95     * 
96     * @param room                  der Name des Raumes für den EventHandler
97     * @param eventName     der Name des Events
98     * @param eventHandler      das EventHandler Objekt das das Event behandelt
99     * @see org.jzuul.engine.Event
00     */
01    public void setRoomEventHandler(String room, String eventName, EventHandler eventHandler) {
02        Room r = getRoom(room);
03        r.setHandler(eventName, eventHandler);
04    }
05    
06    /**
07     * Weist einem Raum einen auf ein Bild verweisenden Stream zu
08     * 
09     * @param roomName  der Name des Raumes dem der Stream zugewiesen werden soll
10     * @param imageStream   der Stream der auf das Bild verweist.
11     */
12    public void setRoomImageStream(String roomName, InputStream imageStream) {
13        getRoom(roomName).setImageStream(imageStream);
14    }
15
16}
17