Javadoc, small bugfix in levels.xml
[AntiTD.git] / src / se / umu / cs / dit06ajnajs / ATDController.java
blob635adb87900c0f1ca62f396878c7b7089a4267f8
1 package se.umu.cs.dit06ajnajs;
3 import java.awt.Graphics;
4 import java.awt.Point;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.MouseAdapter;
8 import java.awt.event.MouseEvent;
9 import java.awt.event.WindowAdapter;
10 import java.awt.event.WindowEvent;
11 import java.net.MalformedURLException;
12 import java.net.URL;
13 import java.util.ArrayList;
14 import java.util.Date;
15 import java.util.List;
16 import java.util.logging.Logger;
17 import se.umu.cs.dit06ajnajs.agent.AgentPrototypeFactory;
18 import se.umu.cs.dit06ajnajs.agent.Tower;
19 import se.umu.cs.dit06ajnajs.agent.Unit;
20 import se.umu.cs.dit06ajnajs.level.GoalSquare;
21 import se.umu.cs.dit06ajnajs.level.Level;
22 import se.umu.cs.dit06ajnajs.level.StartSquare;
23 import se.umu.cs.dit06ajnajs.util.SoundPlayer;
24 import se.umu.cs.edu.jap.highscoreservice.Entry;
25 import se.umu.cs.edu.jap.highscoreservice.HighScoreServiceClient;
26 import se.umu.cs.edu.jap.highscoreservice.stubs.FailureFaultException;
28 /**
29 * The controller of this AntiTD game. Given a list of levels this class creates
30 * and starts everyting needed to run this game. Two Threads are started to to
31 * run and control the game.
33 * @author Anton Johansson (dit06ajn@cs.umu.se)
34 * @author Andreas Jakobsson (dit06ajs@cs.umu.se)
35 * @version 1.0
37 public class ATDController {
38 private static Logger logger = Logger.getLogger("AntiTD");
40 private final int FRAMES_PER_SECOND = 20;
42 private ATDModel model;
43 private ATDView view;
45 private Thread animationThread;
46 private Thread creditThread;
48 private boolean paused;
50 /**
51 * Creates a model and a view according to the MVC design pattern. The model
52 * gets a list of Levels and the View will be aware of the model so that it
53 * can fetch updates from it. A new game is started directly.
55 * @param levels A list containg the Levels to use in this game.
57 public ATDController(List<Level> levels) {
58 // Create model and view
59 this.model = new ATDModel(levels);
60 this.view = new ATDView(model);
61 connectListeners();
63 newGame();
65 this.animationThread = new Thread(new AnimationThread());
66 this.creditThread = new Thread(new CreditThread());
67 animationThread.start();
68 creditThread.start();
71 /**
72 * Takes the game to next Level. Musictrack is changed. If there is no other
73 * Level the game is won.
75 public void advanceLevel() {
76 SoundPlayer.killMusic();
77 if (model.advanceLevel()) {
78 view.updateScoreboard();
79 view.updateBackgroundImage();
80 SoundPlayer.playMusic(model.getNextTrack());
81 } else {
82 // Game won
83 winGame();
87 /**
88 * Creates a new game. Resets old information (if present) and starts the
89 * first Level.
91 public void newGame() {
92 SoundPlayer.killMusic();
93 model.newGame();
94 view.updateScoreboard();
95 view.updateBackgroundImage();
96 view.repaintGame();
97 SoundPlayer.playMusic(model.getNextTrack());
101 * Restarts a Level. Resets credit and score to initial values when current
102 * Level was started.
104 public void restartLevel() {
105 SoundPlayer.killMusic();
106 model.restartLevel();
107 view.updateScoreboard();
108 view.updateBackgroundImage();
109 SoundPlayer.playMusic(model.getNextTrack());
113 * Game is won. Promts user for username and stores a highscore entry to a
114 * HighScoreService.
116 public void winGame() {
117 SoundPlayer.killMusic();
118 try {
119 String urlString = "http://salt.cs.umu.se:37080/axis2/services/HighScoreService";
120 URL url = new URL(urlString);
121 HighScoreServiceClient client = new HighScoreServiceClient(url);
123 String name = view.promtForHighScoreEntry();
124 String date = new Date().toString();
125 String score = model.getScore() + "";
127 client.store(new Entry(name, date, score));
128 view.printMessage("Score stored.");
129 newGame();
130 } catch (FailureFaultException e) {
131 view.printMessage("Couldn't store score");
132 } catch (MalformedURLException e) {
133 // TODO - fix error message
134 e.printStackTrace();
139 * Game is lost user is presented with option to start a new game or quit
140 * this application.
142 public void loseGame() {
143 SoundPlayer.killMusic();
144 if (view.showLevelLostDialog() == 0) {
145 newGame();
146 } else {
147 quitApplication();
152 * Wrapper method, calls clickPoint(int x, int y).
154 * @param point The point to click.
156 public void clickPoint(Point point) {
157 clickPoint(point.x, point.y);
162 * Clicks all Clickables at the specified x, y point.
164 * @param x The x-location to click.
165 * @param y The y-location to click.
167 public void clickPoint(int x, int y) {
168 List<Unit> units = model.getUnits();
169 for (Unit unit : units) {
170 if (unit.contains(x, y)) {
171 unit.click();
174 model.getCurrentLevel().getMapSquareAtPoint(x, y).click();
178 * Toggles sound on/off.
180 private void toggleMute() {
181 if (SoundPlayer.isMute()) {
182 SoundPlayer.setMute(false);
183 view.updateMuteMenu("Mute");
184 view.printMessage("Sound is on");
185 } else {
186 SoundPlayer.setMute(true);
187 view.updateMuteMenu("unMute");
188 view.printMessage("Sound is muted");
193 * Toggles pause, which freezes game.
195 * TODO: Show a pause-screen, transparent grey image over the GameComponent.
197 private void togglePause() {
198 if (paused) {
199 ATDController.this.paused = false;
200 view.updatePauseMenu("Pause");
201 view.printMessage("");
202 } else {
203 ATDController.this.paused = true;
204 view.updatePauseMenu("Resume");
205 view.printMessage("Game is paused");
210 * Quits this application.
212 private void quitApplication() {
213 logger.info("Closing program");
214 System.exit(0);
219 * AnimationThread is used to update the game state and repaint information
220 * at a rapid rate. Code is running in an infinite loop and sleeps for the
221 * specified milliseconds in constant FRAMES_PER_SECOND.
223 private class AnimationThread implements Runnable {
224 private long usedTime;
227 * While running, the Thread loops through all actions the game can
228 * generate as many times per second determined by the final class
229 * variable FRAMES_PER_SECOND
231 public void run() {
232 while (true) {
233 // Check if game is paused
234 while (paused) {
235 try {
236 Thread.sleep(100);
237 } catch (InterruptedException e) {
238 // Should not happen
239 e.printStackTrace();
240 System.exit(-1);
243 long startTime = System.currentTimeMillis();
245 // Update all units
246 List<Unit> units = model.getUnits();
247 List<Unit> deadUnits = new ArrayList<Unit>();
248 for (Unit unit : units) {
249 if (unit.isAlive()) {
250 // Collisioncheck
251 for (Unit otherUnit : units) {
252 if (unit != otherUnit) {
253 if (unit.intersectsNextMove(otherUnit)) {
254 // Collision!
255 unit.collision(otherUnit);
259 unit.act();
260 } else {
261 // Add dead unit
262 deadUnits.add(unit);
263 logger.info("Dead unit is collected to list deadUnits");
264 //TODO
265 /*the towers should be rewarded for dead units
266 Remember that cleared units are also "dead"
267 but should not be counted
272 // Collect dead units
273 if (!deadUnits.isEmpty()) {
274 model.removeUnits(deadUnits);
275 logger.info("Dead agents cleared");
278 // Update all towers
279 List<Tower> towers = model.getTowers();
280 for (Tower tower : towers) {
281 tower.act();
284 // Remove units from goalsquares and count points
285 GoalSquare[] goalSquares = model.getGoalSquares();
286 int credit = 0;
287 int numOfUnits = 0;
288 for (GoalSquare square : goalSquares) {
289 numOfUnits += square.getNumUnits();
290 credit += square.getCredit();
292 // Only update model if changes has been made and level is not
293 // completed
294 if ((credit > 0 || numOfUnits > 0)
295 && !model.isLevelComplete()) {
296 model.addCredit(credit);
297 model.addGoalUnit(numOfUnits);
298 view.updateScoreboard();
299 SoundPlayer.play(model.getSound("goal"));
302 // Release Unit from StartSquares
303 StartSquare[] startSquares = model.getStartSquares();
304 for (StartSquare startSquare : startSquares) {
305 Unit unitToStart = startSquare.getUnitToStart();
306 boolean unitToStartCollided = false;
307 if (unitToStart != null) {
308 for (Unit unit : units) {
309 unitToStartCollided = unitToStart.intersects(unit);
310 if (unitToStartCollided) {
311 // Collision
312 break;
315 if (!unitToStartCollided) {
316 // No collision found
317 startSquare.removeUnit(unitToStart);
319 model.releaseUnit(unitToStart);
324 // Repaint all agents
325 Graphics g = view.getGameGraphics();
326 for (Unit unit : units) {
327 unit.paint(g);
329 for (Tower tower : towers) {
330 tower.paint(g);
333 // Refresh the game view
334 view.repaintGame();
336 this.usedTime = System.currentTimeMillis() - startTime;
337 // Try to keep a given number of frames per second.
338 try {
339 //System.out.println("usedTime: >" + usedTime + "<");
340 long waitTime
341 = ((1000 - usedTime) / FRAMES_PER_SECOND);
342 waitTime = (waitTime < 0) ? 0 : waitTime;
343 Thread.sleep(waitTime);
344 } catch (InterruptedException e) {
345 // If game is slow 1000 - usedTime can be negative
346 logger.warning("Missed frame");
353 * While running, the thread sleeps for an interval and then calculates
354 * the earned credits from units on the map. The credits are then added
355 * to the player
357 private class CreditThread implements Runnable {
358 public CreditThread() {
363 * While running, the thread sleeps for an interval and then calculates
364 * the earned credits from units on the map. The credits are then added
365 * to the player
367 public void run() {
368 while (true) {
369 // Check if game is paused
370 while(paused) {
371 try {
372 Thread.sleep(100);
373 } catch (InterruptedException e) {
374 // Should not happen
375 e.printStackTrace();
376 System.exit(-1);
379 // Calculate earned credits
380 int credit = calculateCredit();
381 if (credit > 0) {
382 model.addCredit(credit);
383 view.updateScoreboard();
385 // Goal check
386 if (model.isLevelComplete()) {
387 //System.out.println("Victory!");
388 SoundPlayer.killMusic();
389 SoundPlayer.play(model.getSound("victory"));
390 // Check if user wants to restart level or go to next
391 if(view.showLevelCompleteDialog() == 0) {
392 advanceLevel();
393 } else {
394 restartLevel();
397 // Lose check
398 if (model.isLevelLost()) {
400 //System.out.println("You lose!");
401 loseGame();
403 try {
404 // Sleep an interval
405 Thread.sleep(1000);
406 } catch (InterruptedException e) {
407 e.printStackTrace();
413 * Method to calculate total credit earned. Every unit on Level gives a
414 * score of 10.
416 * @return The total creadit earned from units currently existing in
417 * Level.
419 private int calculateCredit() {
420 int credit = model.getUnits().size() * 10;
421 return credit;
426 * Connect listeners to View
428 private void connectListeners() {
429 this.view.addMapListener(new MapListener());
430 this.view.addClosingListener(new ClosingListener());
431 this.view.addReleaseUnitListener(new ReleaseUnitListener());
432 this.view.addPauseMenuItemListener(new PausResumeListener());
433 this.view.addMuteMenuItemListener(new MuteListener());
434 this.view.addNewGameMenuItemListener(new NewGameListener());
435 this.view.addAboutMenuItemListener(new AboutListener());
436 this.view.addHelpMenuItemListener(new HelpListener());
437 this.view.addRestartLevelMenuItemListener(new RestartLevelListener());
440 /* Inner Listener classes ****************************************/
443 * Should listen to GameComponent in View and run method click() on every
444 * clickable item at the position clicked.
446 private class MapListener extends MouseAdapter {
448 * Clicks every clickble at MouseEvents x-, y-position.
450 * @param me The Event that invoked this method.
452 @Override
453 public void mouseReleased(MouseEvent me) {
454 clickPoint(me.getX(), me.getY());
455 // TODO: Only update what is needed.
456 view.updateBackgroundImage();
461 * Listenes for program to close.
463 private class ClosingListener extends WindowAdapter
464 implements ActionListener {
466 * Used quitMenuItem, quits the program.
468 * @param ae Not used.
470 public void actionPerformed(ActionEvent ae) {
471 quitApplication();
475 * Used to detect when window is closed, quits this application.
477 * @param we a <code>WindowEvent</code> value
479 @Override
480 public void windowClosing(WindowEvent we) {
481 quitApplication();
486 * Listener to listen for PausResume events.
488 private class PausResumeListener implements ActionListener {
490 * Toggles paus/resume
492 * @param ae Not used.
494 public void actionPerformed(ActionEvent ae) {
495 ATDController.this.togglePause();
500 * Listener to listen for Mute/unMute events.
502 private class MuteListener implements ActionListener {
504 * Toggle mute/unmute
506 * @param ae Not used.
508 public void actionPerformed(ActionEvent ae) {
509 ATDController.this.toggleMute();
514 * Listener to listen for new game events.
516 private class NewGameListener implements ActionListener {
518 * Creates a new game.
520 * @param ae Not used.
522 public void actionPerformed(ActionEvent ae) {
523 ATDController.this.newGame();
528 * Listener to listen for restart level events.
530 private class RestartLevelListener implements ActionListener {
532 * Restarts Level.
534 * @param ae Not used.
536 public void actionPerformed(ActionEvent ae) {
537 ATDController.this.restartLevel();
542 * Listener invoked when a user whants to read the information about the
543 * game.
545 private class AboutListener implements ActionListener {
547 * Pauses game and shows an about dialog.
549 * @param ae Not used.
551 public void actionPerformed(ActionEvent ae) {
552 if (!paused) {
553 ATDController.this.togglePause();
555 view.showAboutDialog();
556 ATDController.this.togglePause();
561 * Listener invoked when a user whants to read the information about how the
562 * game is played.
564 private class HelpListener implements ActionListener {
566 * Pauses game and shows a help dialog.
568 * @param ae Not used.
570 public void actionPerformed(ActionEvent ae) {
571 if (!paused) {
572 ATDController.this.togglePause();
574 view.showHelpDialog();
575 ATDController.this.togglePause();
580 * Listener to listen for a user to want to add a new Unit in the game.
582 private class ReleaseUnitListener implements ActionListener {
584 * Creates a new unit and adds it to the game model if the current
585 * player have sufficient fund.
587 * @param ae an <code>ActionEvent</code> value
589 public void actionPerformed(ActionEvent ae) {
590 AgentPrototypeFactory factory = AgentPrototypeFactory.getInstance();
591 if (!model.addUnit(factory.createUnit(view.getSelectedUnitType()))) {
592 view.printMessage("Insufficient funds!");
593 } else {
594 view.printMessage("");