1 package se
.umu
.cs
.dit06ajnajs
;
3 import java
.awt
.Graphics
;
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
;
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
;
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)
37 public class ATDController
{
38 private static Logger logger
= Logger
.getLogger("AntiTD");
40 private final int FRAMES_PER_SECOND
= 20;
42 private ATDModel model
;
45 private Thread animationThread
;
46 private Thread creditThread
;
48 private boolean paused
;
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
);
65 this.animationThread
= new Thread(new AnimationThread());
66 this.creditThread
= new Thread(new CreditThread());
67 animationThread
.start();
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());
88 * Creates a new game. Resets old information (if present) and starts the
91 public void newGame() {
92 SoundPlayer
.killMusic();
94 view
.updateScoreboard();
95 view
.updateBackgroundImage();
97 SoundPlayer
.playMusic(model
.getNextTrack());
101 * Restarts a Level. Resets credit and score to initial values when current
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
116 public void winGame() {
117 SoundPlayer
.killMusic();
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.");
130 } catch (FailureFaultException e
) {
131 view
.printMessage("Couldn't store score");
132 } catch (MalformedURLException e
) {
133 // TODO - fix error message
139 * Game is lost user is presented with option to start a new game or quit
142 public void loseGame() {
143 SoundPlayer
.killMusic();
144 if (view
.showLevelLostDialog() == 0) {
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
)) {
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");
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() {
199 ATDController
.this.paused
= false;
200 view
.updatePauseMenu("Pause");
201 view
.printMessage("");
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");
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
233 // Check if game is paused
237 } catch (InterruptedException e
) {
243 long startTime
= System
.currentTimeMillis();
246 List
<Unit
> units
= model
.getUnits();
247 List
<Unit
> deadUnits
= new ArrayList
<Unit
>();
248 for (Unit unit
: units
) {
249 if (unit
.isAlive()) {
251 for (Unit otherUnit
: units
) {
252 if (unit
!= otherUnit
) {
253 if (unit
.intersectsNextMove(otherUnit
)) {
255 unit
.collision(otherUnit
);
263 logger
.info("Dead unit is collected to list deadUnits");
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");
279 List
<Tower
> towers
= model
.getTowers();
280 for (Tower tower
: towers
) {
284 // Remove units from goalsquares and count points
285 GoalSquare
[] goalSquares
= model
.getGoalSquares();
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
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
) {
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
) {
329 for (Tower tower
: towers
) {
333 // Refresh the game view
336 this.usedTime
= System
.currentTimeMillis() - startTime
;
337 // Try to keep a given number of frames per second.
339 //System.out.println("usedTime: >" + usedTime + "<");
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
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
369 // Check if game is paused
373 } catch (InterruptedException e
) {
379 // Calculate earned credits
380 int credit
= calculateCredit();
382 model
.addCredit(credit
);
383 view
.updateScoreboard();
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) {
398 if (model
.isLevelLost()) {
400 //System.out.println("You lose!");
406 } catch (InterruptedException e
) {
413 * Method to calculate total credit earned. Every unit on Level gives a
416 * @return The total creadit earned from units currently existing in
419 private int calculateCredit() {
420 int credit
= model
.getUnits().size() * 10;
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.
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
) {
475 * Used to detect when window is closed, quits this application.
477 * @param we a <code>WindowEvent</code> value
480 public void windowClosing(WindowEvent we
) {
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
{
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
{
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
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
) {
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
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
) {
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!");
594 view
.printMessage("");