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 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.
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
) {
480 * Used to detect when window is closed, quits this application.
482 * @param we a <code>WindowEvent</code> value
485 public void windowClosing(WindowEvent we
) {
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
{
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
{
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
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
) {
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
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
) {
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!");
599 view
.printMessage("");