1 | package jpacman.model; |
2 | |
3 | import java.util.Vector; |
4 | |
5 | /** |
6 | * Representation of the board and its guests. This class's responsibilities |
7 | * include the correct movement of moving guests, and keeping track of the state |
8 | * of the game (whether the player died, or whether everything has been eaten, |
9 | * for example) |
10 | * <p> |
11 | * |
12 | * @author Arie van Deursen; Aug 24, 2003 |
13 | * @version $Id: Game.java,v 1.10 2008/02/10 20:46:01 arie Exp $ |
14 | */ |
15 | public class Game { |
16 | |
17 | /** |
18 | * The board containing all guests. |
19 | */ |
20 | private Board theBoard; |
21 | |
22 | /** |
23 | * The player of the game. |
24 | */ |
25 | private Player thePlayer; |
26 | |
27 | /** |
28 | * All monsters active in this game. |
29 | */ |
30 | private Vector<Monster> monsters; |
31 | |
32 | /** |
33 | * The total number of points that can be earned in this game. |
34 | */ |
35 | private int totalPoints; |
36 | |
37 | /** |
38 | * The initial map / layout on the board. |
39 | */ |
40 | private String[] theMap; |
41 | |
42 | |
43 | /** |
44 | * Create a new Game using a default map. |
45 | */ |
46 | public Game() { |
47 | this(DEFAULT_WORLD_MAP); |
48 | } |
49 | |
50 | /** |
51 | * Create a new Game using a custom map. |
52 | * @param map The world to be used in the game. |
53 | */ |
54 | public Game(String[] map) { |
55 | theMap = map; |
56 | initialize(); |
57 | assert invariant(); |
58 | } |
59 | |
60 | /** |
61 | * (Re)set the fields of the game to their initial values. * |
62 | */ |
63 | void initialize() { |
64 | monsters = new Vector<Monster>(); |
65 | totalPoints = 0; |
66 | thePlayer = null; |
67 | theBoard = null; |
68 | loadWorld(theMap); |
69 | assert invariant(); |
70 | } |
71 | |
72 | /** |
73 | * Check whether all relevant fields have been initialized. |
74 | * |
75 | * @return true iff initialization has completed successfully. |
76 | */ |
77 | public boolean initialized() { |
78 | return theBoard != null && thePlayer != null && monsters != null |
79 | && totalPoints >= 0; |
80 | } |
81 | |
82 | /** |
83 | * The game should always be in a consistent state, |
84 | * in particular, a player cannot win and die at the same time. |
85 | * |
86 | * @return True iff the above holds. |
87 | */ |
88 | public boolean consistent() { |
89 | return !(playerDied() && playerWon()) |
90 | && thePlayer.getPointsEaten() <= totalPoints; |
91 | } |
92 | |
93 | /** |
94 | * A game is either not yet initialized, or it is in a consistent state. |
95 | * |
96 | * @return True iff this is the case. |
97 | */ |
98 | public boolean invariant() { |
99 | return initialized() && consistent(); |
100 | } |
101 | |
102 | /** |
103 | * Create the default world map. |
104 | * |
105 | * @return A string array of the default world map. |
106 | */ |
107 | private static final String[] DEFAULT_WORLD_MAP = new String[] { |
108 | "WWWWWWWWWWWWWWWWWWWW", |
109 | "W000M0W00000M0000F0W", |
110 | "W0F00MW000000000000W", |
111 | "W0000000000M000F000W", |
112 | "W000000000000F00000W", |
113 | "W000MM0000M00000000W", |
114 | "W000000M00000000000W", |
115 | "W00M00000F0000000WWW", |
116 | "W0000000000000WWW00W", |
117 | "W000000000000000000W", |
118 | "W0000WW0WWW00000000W", |
119 | "W0000W00F0W00000000W", |
120 | "W0P00W0M00W00000000W", |
121 | "W0000WWWWWW00000F00W", |
122 | "W000000000000000000W", |
123 | "W000000000000000000W", |
124 | "W0F0000000000F00M00W", |
125 | "W0WWWW0000000000000W", |
126 | "W00F00WWWW000000WWWW", |
127 | "WWWWWWWWWWWWWWWWWWWW" |
128 | }; |
129 | |
130 | |
131 | /** |
132 | * Get the board of this game, which can be null if |
133 | * the game has not been initialized. |
134 | * |
135 | * @return The game's board. |
136 | */ |
137 | public Board getBoard() { |
138 | return theBoard; |
139 | } |
140 | |
141 | /** |
142 | * Precondition: the player doesn't exist yet. |
143 | * @return a new Player. |
144 | */ |
145 | private Player createPlayer() { |
146 | assert thePlayer == null : "Player exists already"; |
147 | thePlayer = new Player(); |
148 | return thePlayer; |
149 | } |
150 | |
151 | /** |
152 | * @return a new food element. |
153 | */ |
154 | private Food createFood() { |
155 | Food f = new Food(); |
156 | totalPoints += f.getPoints(); |
157 | return f; |
158 | } |
159 | |
160 | /** |
161 | * Create monster, and add it to the list of known monsters. |
162 | * @return a new monster. |
163 | */ |
164 | private Monster createMonster() { |
165 | Monster m = new Monster(); |
166 | monsters.add(m); |
167 | return m; |
168 | } |
169 | |
170 | /** |
171 | * Return the current player, which can be null if the game has not yet been |
172 | * initialized. |
173 | * |
174 | * @return the player of the game. |
175 | */ |
176 | public Player getPlayer() { |
177 | return thePlayer; |
178 | } |
179 | |
180 | /** |
181 | * Return a fresh Vector containing all the monsters in the game. |
182 | * |
183 | * @return All the monsters. |
184 | */ |
185 | public Vector<Monster> getMonsters() { |
186 | assert invariant(); |
187 | Vector<Monster> result = new Vector<Monster>(); |
188 | result.addAll(monsters); |
189 | assert result != null; |
190 | return result; |
191 | } |
192 | |
193 | /** |
194 | * Add a new guest to the board. |
195 | * @param code Representation of the sort of guest |
196 | * @param x x-position |
197 | * @param y y-position |
198 | */ |
199 | private void addGuestFromCode(char code, int x, int y) { |
200 | assert getBoard() != null : "Board should exist"; |
201 | assert getBoard().withinBorders(x, y); |
202 | Guest theGuest = null; |
203 | switch (code) { |
204 | case Guest.WALL_TYPE: |
205 | theGuest = new Wall(); |
206 | break; |
207 | case Guest.PLAYER_TYPE: |
208 | theGuest = createPlayer(); |
209 | break; |
210 | case Guest.FOOD_TYPE: |
211 | theGuest = createFood(); |
212 | break; |
213 | case Guest.MONSTER_TYPE: |
214 | theGuest = createMonster(); |
215 | break; |
216 | case Guest.EMPTY_TYPE: |
217 | theGuest = null; |
218 | break; |
219 | default: |
220 | assert false : "unknown cell type``" + code + "'' in worldmap"; |
221 | break; |
222 | } |
223 | assert code == Guest.EMPTY_TYPE ? theGuest == null : theGuest != null; |
224 | if (theGuest != null) { |
225 | theGuest.occupy(getBoard().getCell(x, y)); |
226 | } |
227 | assert theGuest == null |
228 | || getBoard().getCell(x, y).equals(theGuest.getLocation()); |
229 | } |
230 | |
231 | /** |
232 | * Load a custom map. Postcondition: the invariant holds. |
233 | * |
234 | * @param map |
235 | * String array for a customized world map. |
236 | */ |
237 | private void loadWorld(String[] map) { |
238 | int height = map.length; |
239 | assert height > 0 : "at least one cell with one player required."; |
240 | int width = map[0].length(); |
241 | assert width > 0 : "empty rows not permitted."; |
242 | |
243 | assert theBoard == null; |
244 | theBoard = new Board(width, height); |
245 | |
246 | // read the map into the cells |
247 | for (int y = 0; y < height; y++) { |
248 | assert map[y].length() == width |
249 | : "all lines in map should be of equal length."; |
250 | for (int x = 0; x < width; x++) { |
251 | assert getBoard().getGuest(x, y) == null |
252 | : "only empty cells can be filled."; |
253 | addGuestFromCode(map[y].charAt(x), x, y); |
254 | } |
255 | } |
256 | assert invariant(); |
257 | } |
258 | |
259 | /** |
260 | * Move the player to offsets (x+dx,y+dy). If the move is not possible |
261 | * (wall, beyond borders), the move is not carried out. Precondition: |
262 | * initialized and game isn't over yet. Postcondition: if the move is |
263 | * possible, it has been carried out, and the game has been updated to |
264 | * reflect the new situation. |
265 | * |
266 | * @param dx |
267 | * Horizontal movement |
268 | * @param dy |
269 | * Vertical movement |
270 | */ |
271 | void movePlayer(int dx, int dy) { |
272 | assert invariant(); |
273 | assert !gameOver() : "can only move when game isn't over"; |
274 | Cell targetCell = |
275 | getPlayer().getLocation().cellAtOffset(dx, dy); |
276 | PlayerMove playerMove = new PlayerMove(getPlayer(), targetCell); |
277 | applyMove(playerMove); |
278 | getPlayer().setLastDirection(dx, dy); |
279 | assert invariant(); |
280 | } |
281 | |
282 | |
283 | /** |
284 | * Actually apply the given move, if it is possible. |
285 | * @param move The move to be made. |
286 | */ |
287 | private void applyMove(Move move) { |
288 | assert move != null; |
289 | assert invariant(); |
290 | assert !gameOver(); |
291 | if (move.movePossible()) { |
292 | move.apply(); |
293 | assert move.moveDone(); |
294 | assert !playerDied() : "move possible => not killed"; |
295 | } else { |
296 | if (move.playerDies()) { |
297 | assert !playerWon() : "you can't win by dying"; |
298 | getPlayer().die(); |
299 | assert playerDied(); |
300 | } |
301 | } |
302 | assert invariant(); |
303 | } |
304 | |
305 | |
306 | /** |
307 | * Check if the player has died. Precondition: initialization completed. |
308 | * |
309 | * @return True iff the player is dead. |
310 | */ |
311 | public boolean playerDied() { |
312 | assert initialized(); |
313 | return !getPlayer().living(); |
314 | } |
315 | |
316 | /** |
317 | * Check if the player has eaten all food. Precondition: initialization |
318 | * completed. |
319 | * |
320 | * @return True iff the player has won. |
321 | */ |
322 | public boolean playerWon() { |
323 | assert initialized(); |
324 | return getPlayer().getPointsEaten() >= totalPoints; |
325 | } |
326 | |
327 | /** |
328 | * Check whether the game is over. |
329 | * |
330 | * @return True iff the game is over. |
331 | */ |
332 | public boolean gameOver() { |
333 | assert initialized(); |
334 | return playerWon() || playerDied(); |
335 | } |
336 | |
337 | /** |
338 | * Return the delta in the x-direction of the last |
339 | * (attempted or succesful) player move. |
340 | * Returns 0 if player hasn't moved yet. |
341 | * @return delta in x direction |
342 | */ |
343 | public int getPlayerLastDx() { |
344 | return getPlayer().getLastDx(); |
345 | } |
346 | |
347 | /** |
348 | * Return the delta in the y-direction of the last |
349 | * (attempted or succesful) player move. |
350 | * Returns 0 if player hasn't moved yet. |
351 | * @return delta in y direction |
352 | */ |
353 | public int getPlayerLastDy() { |
354 | return getPlayer().getLastDy(); |
355 | } |
356 | |
357 | /** |
358 | * Return the width of the Board used. |
359 | * @return width of the board. |
360 | */ |
361 | public int boardWidth() { |
362 | return getBoard().getWidth(); |
363 | } |
364 | |
365 | /** |
366 | * Return the height of the Board used. |
367 | * @return height of the board. |
368 | */ |
369 | public int boardHeight() { |
370 | return getBoard().getHeight(); |
371 | } |
372 | |
373 | /** |
374 | * Return the guest code at position (x,y). |
375 | * @param x x-coordinate of guest |
376 | * @param y y-coordinate of guest |
377 | * @return Character representing guest at x,y |
378 | */ |
379 | public char getGuestCode(int x, int y) { |
380 | return getBoard().guestCode(x, y); |
381 | } |
382 | } |