Added screenshots, new level, and balanced the game
[AntiTD.git] / src / se / umu / cs / dit06ajnajs / ATDController.java
blobf894d42da514f4f0278f361452f1af9c2392cdd4
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 public void saveToHighScoreService() {
427 // Save highscore to Highscoreservice
431 * Connect listeners to View
433 private void connectListeners() {
434 this.view.addMapListener(new MapListener());
435 this.view.addClosingListener(new ClosingListener());
436 this.view.addReleaseUnitListener(new ReleaseUnitListener());
437 this.view.addPauseMenuItemListener(new PausResumeListener());
438 this.view.addMuteMenuItemListener(new MuteListener());
439 this.view.addNewGameMenuItemListener(new NewGameListener());
440 this.view.addAboutMenuItemListener(new AboutListener());
441 this.view.addHelpMenuItemListener(new HelpListener());
442 this.view.addRestartLevelMenuItemListener(new RestartLevelListener());
445 /* Inner Listener classes ****************************************/
448 * Should listen to GameComponent in View and run method click() on every
449 * clickable item at the position clicked.
451 private class MapListener extends MouseAdapter {
453 * Clicks every clickble at MouseEvents x-, y-position.
455 * @param me The Event that invoked this method.
457 @Override
458 public void mouseReleased(MouseEvent me) {
459 clickPoint(me.getX(), me.getY());
460 // TODO: Only update what is needed.
461 view.updateBackgroundImage();
466 * Listenes for program to close.
468 private class ClosingListener extends WindowAdapter
469 implements ActionListener {
471 * Used quitMenuItem, quits the program.
473 * @param ae Not used.
475 public void actionPerformed(ActionEvent ae) {
476 quitApplication();
480 * Used to detect when window is closed, quits this application.
482 * @param we a <code>WindowEvent</code> value
484 @Override
485 public void windowClosing(WindowEvent we) {
486 quitApplication();
491 * Listener to listen for PausResume events.
493 private class PausResumeListener implements ActionListener {
495 * Toggles paus/resume
497 * @param ae Not used.
499 public void actionPerformed(ActionEvent ae) {
500 ATDController.this.togglePause();
505 * Listener to listen for Mute/unMute events.
507 private class MuteListener implements ActionListener {
509 * Toggle mute/unmute
511 * @param ae Not used.
513 public void actionPerformed(ActionEvent ae) {
514 ATDController.this.toggleMute();
519 * Listener to listen for new game events.
521 private class NewGameListener implements ActionListener {
523 * Creates a new game.
525 * @param ae Not used.
527 public void actionPerformed(ActionEvent ae) {
528 ATDController.this.newGame();
533 * Listener to listen for restart level events.
535 private class RestartLevelListener implements ActionListener {
537 * Restarts Level.
539 * @param ae Not used.
541 public void actionPerformed(ActionEvent ae) {
542 ATDController.this.restartLevel();
547 * Listener invoked when a user whants to read the information about the
548 * game.
550 private class AboutListener implements ActionListener {
552 * Pauses game and shows an about dialog.
554 * @param ae Not used.
556 public void actionPerformed(ActionEvent ae) {
557 if (!paused) {
558 ATDController.this.togglePause();
560 view.showAboutDialog();
561 ATDController.this.togglePause();
566 * Listener invoked when a user whants to read the information about how the
567 * game is played.
569 private class HelpListener implements ActionListener {
571 * Pauses game and shows a help dialog.
573 * @param ae Not used.
575 public void actionPerformed(ActionEvent ae) {
576 if (!paused) {
577 ATDController.this.togglePause();
579 view.showHelpDialog();
580 ATDController.this.togglePause();
585 * Listener to listen for a user to want to add a new Unit in the game.
587 private class ReleaseUnitListener implements ActionListener {
589 * Creates a new unit and adds it to the game model if the current
590 * player have sufficient fund.
592 * @param ae an <code>ActionEvent</code> value
594 public void actionPerformed(ActionEvent ae) {
595 AgentPrototypeFactory factory = AgentPrototypeFactory.getInstance();
596 if (!model.addUnit(factory.createUnit(view.getSelectedUnitType()))) {
597 view.printMessage("Insufficient funds!");
598 } else {
599 view.printMessage("");