/** * Constructs a new UnresolvedReferenceException that takes a list of strings corresponding to * the unresolved room IDs left over. */ public UnresolvedReferenceException(ArrayList<String> list) { super( list.size() > 1 ? "rooms referenced but never actually defined: " + list : "room referenced but never actually defined: " + list.get(0)); }
@SuppressWarnings({"unchecked", "rawtypes"}) public static void main(String[] args) throws IOException { boolean useArguments = false; Map config = null; try { yamlInstance = new Yaml(); /* * Try to load configuration file */ FileReader configFile = new FileReader(YAML_PATH + CONFIGURATION_FILE); config = (Map) yamlInstance.load(configFile); } catch (FileNotFoundException e) { System.out.println( "Could not load configuration file. Using program " + "arguments instead."); useArguments = true; } /* Scan for program arguments */ int port = -1; try { if (useArguments) port = Integer.parseInt(args[0]); else // take the port number from the config file port = (Integer) config.get("port"); } catch (ArrayIndexOutOfBoundsException e) { System.err.println("DungeonServer: specify a port to which to bind"); System.out.println(usage()); System.exit(1); } /* Attempt to bind to port */ ServerSocket server = null; try { server = new ServerSocket(port); } catch (IOException e) { System.err.printf("DungeonServer: could not listen on port %d\n", port); System.exit(2); } System.out.printf("bound to port %d\n", port); System.out.println("loading universe:"); /* Load universe */ try { System.out.println("\tfinding universe file"); FileReader universeFile = null; if (useArguments) universeFile = new FileReader(YAML_PATH + DEFAULT_UNIVERSE_FILE); else universeFile = new FileReader(YAML_PATH + config.get("world") + ".universe.yml"); Object[] docs = new Object[3]; Map<String, Object> preamble = null; Map<String, Map<String, Object>> rooms = null, items = null; try { System.out.println("\tchecking universe file"); int i = 0; for (Object o : yamlInstance.loadAll(universeFile)) docs[i++] = o; preamble = (Map<String, Object>) docs[0]; rooms = (Map<String, Map<String, Object>>) docs[1]; items = (Map<String, Map<String, Object>>) docs[2]; if (preamble == null || rooms == null || items == null) throw new NullPointerException(); } catch (ArrayIndexOutOfBoundsException e) { System.err.println( "DungeonServer: error parsing universe " + "file: too many documents in universe file"); System.exit(3); } catch (NullPointerException e) { System.err.println( "DungeonServer: error parsing universe " + "file: too few documents in universe file"); System.exit(3); } /* * Used after parsing rooms to set universe parameters */ boolean doWeather = false; String spawnRoomID = null; int timescale = -1; try { System.out.println("\treading preamble"); /* * Load universe parameters from the preamble */ doWeather = (Boolean) validateAndGet(preamble, "weather", Boolean.class); spawnRoomID = (String) validateAndGet(preamble, "spawn", String.class); timescale = (Integer) validateAndGet(preamble, "timescale", Integer.class); } catch (Exception e) { System.err.println("DungeonServer: failed parsing preamble (" + e.getMessage() + ")"); System.exit(4); } /* * Loop through room definitions in universe file */ /** * This hash map is used to resolve references from one room to another. Each time a room is * parsed, it is added to this map, and henceforth back references to the newly added room * will be resolved by checking this map. */ HashMap<String, Room> knownRooms = new HashMap<String, Room>(); /** * This list is maintained to easily check at the end of parsing if there are still references * to unseen rooms. */ ArrayList<String> unseenRooms = new ArrayList<String>(); /** * This is a list of triples (A, B, C) such that A is a room waiting for a reference to * another room, C, through a direction B. For A and B, the string ID of the rooms are used * (the same key used in the knownRooms hash map). This list is used whenever a room's exit * references cannot actually be resolved because the destination room has not yet been * parsed. At the end of parsing, as long as the unseenRooms list is empty, this list is * traversed to resolve the remaining references. */ ArrayList<Triple<String, Direction, String>> unresolved; unresolved = new ArrayList<Triple<String, Direction, String>>(); String thisRoomID = null; try { System.out.println("\tparsing rooms"); for (Map.Entry<String, Map<String, Object>> m : rooms.entrySet()) { thisRoomID = m.getKey(); Map<String, Object> thisMap = m.getValue(); String roomName = (String) validateAndGet(thisMap, "name", String.class); String description = (String) validateAndGet(thisMap, "description", String.class); Hashtable<Pair<DayPart, Weather>, String> details; details = getDetails(thisMap); Room r = new Room(roomName, description, details); if (thisMap.containsKey("neverUseArticle")) { boolean neverUseArticle = (Boolean) validateAndGet(thisMap, "neverUseArticle", Boolean.class); r.setNeverUseArticle(neverUseArticle); } if (unseenRooms.contains(thisRoomID)) unseenRooms.remove(thisRoomID); knownRooms.put(thisRoomID, r); /* * Process exits out of this room */ Map<String, String> exits = (Map) validateAndGet(thisMap, "exits", Map.class); for (Map.Entry<String, String> exit : exits.entrySet()) { String thisDirection = exit.getKey(); String toRoomID = exit.getValue(); /* * Verify the direction from the file */ Direction dir; dir = Direction.fromString(thisDirection); if (dir == null) throw new InvalidDirectionException(thisDirection); /* * Look up the destination room in the hash map */ if (knownRooms.containsKey(toRoomID)) r.addExit(dir, knownRooms.get(toRoomID)); else { if (!unseenRooms.contains(toRoomID)) unseenRooms.add(toRoomID); Triple<String, Direction, String> t; t = new Triple<String, Direction, String>(thisRoomID, dir, toRoomID); unresolved.add(t); } } } } catch (Exception e) { System.err.println( "DungeonServer: failed parsing room '" + thisRoomID + "' (" + e.getMessage() + ")"); System.exit(4); } if (!unseenRooms.isEmpty()) throw new UnresolvedReferenceException(unseenRooms); /* * Invariant: There were no references to undefined rooms in the * file. Invariant: All the rooms in the file have been * instantiated. * * All rooms in the universe file have been parsed, but there may * still be exits waiting to be added because their destination was * not yet parsed at the time. Now loop through the unresolved list * to set them up. */ for (Triple<String, Direction, String> t : unresolved) { Room fromRoom = knownRooms.get(t.first); Room toRoom = knownRooms.get(t.third); Direction dir = t.second; fromRoom.addExit(dir, toRoom); } /* * Invariant: All exits in the file have been set up among the * rooms. */ Room spawnRoom = knownRooms.get(spawnRoomID); universe = new DungeonUniverse(spawnRoom, doWeather, timescale, knownRooms.values()); universeFile.close(); } catch (Exception e) { System.err.println("DungeonServer: failed loading universe " + "(" + e.getMessage() + ")"); System.exit(2); } System.out.println("loaded universe"); /* Start narrator */ try { narrator = new DungeonNarrator(); } catch (Exception e) { System.err.println("DungeonServer: failed starting narrator"); System.exit(3); } System.out.println("started narrator"); /* Start the game tick */ try { tick = new DungeonGameTick(); tick.start(); } catch (Exception e) { System.err.println("DungeonServer: failed starting game tick"); System.exit(3); } System.out.println("started game tick"); /* Start accepting events */ try { events = new DungeonDispatcher(); events.start(); } catch (Exception e) { System.err.println("DungeonServer: failed starting event queue"); System.exit(3); } System.out.println("started event queue"); /* Listen for clients */ try { System.out.println("listening for clients"); while (true) new DungeonConnectionThread(server.accept()).start(); } catch (IOException e) { System.err.printf("DungeonServer: failed accepting client on port %d\n", port); System.exit(2); } finally { server.close(); } }