/**
*
*/
package com.szuppe.jakub.controller;
import java.util.concurrent.BlockingQueue;
import com.szuppe.jakub.common.ReschedulableTimer;
import com.szuppe.jakub.events.*;
import com.szuppe.jakub.model.AllBricksDestroyedException;
import com.szuppe.jakub.model.Model;
import com.szuppe.jakub.model.NoMoreMapsException;
import com.szuppe.jakub.model.PlayerDiedException;
import com.szuppe.jakub.view.View;
import java.util.HashMap;
import java.util.Map;
/**
* Kontroler jest trzonem aplikacji "Vaus". Zarządza modelem oraz widokiem,
* a także organizuje komunikacje.
*
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
public class Controller
{
/** Model. Symuluje świat gry. */
private final Model model;
/** Widok. Odpowiada za wyświetlanie stanu modelu. */
private final View view;
/** Kolejka blokująca zdarzeń aplikacji {@link AppEvent} */
private final BlockingQueue<AppEvent> eventsBlockingQueue;
/** Mapa strategii. Każdemu zdarzeniu aplikacji odpowiada strategia. */
private final Map<Class<? extends AppEvent>, AppStrategy> strategyMap;
/** Timer aplikacji. */
private final ReschedulableTimer modelTimer;
/** Zadanie obudzenia modelu, gdy minął czas poprawności jego stanu. */
private final Runnable validTimePassedTask;
/**
* Konstruktor. Tworzy obiekt typu {@link Controller}, który jest trzonem aplikacji i
* zarządza podanym modelem oraz widokiem. Aplikacja nie startuje do czasu wykonania
* metody {@link #init()}.
*
* @param model - model, nad którym odbywa się kontrola.
* @param view - widok, do który będzie wyświetlał stan modelu.
* @param eventsBlockingQueue - kolejka blokująca zdarzeń aplikacji.
*/
public Controller(final Model model, final View view,
final BlockingQueue<AppEvent> eventsBlockingQueue)
{
this.model = model;
this.view = view;
this.eventsBlockingQueue = eventsBlockingQueue;
this.strategyMap = new HashMap<Class<? extends AppEvent>, AppStrategy>();
this.modelTimer = new ReschedulableTimer();
this.validTimePassedTask = new Runnable()
{
@Override
public void run()
{
eventsBlockingQueue.add(new ValidTimePassedEvent());
}
};
initStrategyMap();
}
/**
* Uruchamia aplikacje.
*/
public void init()
{
view.init();
// Główną pętla gry.
while (true)
{
AppEvent nextAppEvent = null;
try
{
// Jeżeli nie ma zdarzenia, to usypia.
nextAppEvent = eventsBlockingQueue.take();
} catch (InterruptedException e)
{
// Nie powinno się zdarzyć.
throw new RuntimeException();
}
strategyMap.get(nextAppEvent.getClass()).process(nextAppEvent);
}
}
/**
* Wypełnia {@link #strategyMap} strategiami, które odpowiadają wszystkim zdarzeniom,
* występującym w aplikacji.
*/
private void initStrategyMap()
{
strategyMap.clear();
strategyMap.put(StartGameEvent.class, new StartGameStrategy());
strategyMap.put(BallReleaseEvent.class, new BallReleaseStrategy());
strategyMap.put(MovePaddleEvent.class, new MovePaddleStrategy());
strategyMap.put(ValidTimePassedEvent.class, new ValidTimePassedStrategy());
strategyMap.put(LoadNextLevelEvent.class, new LoadNextMapStrategy());
strategyMap.put(GameWinEvent.class, new GameWinStrategy());
strategyMap.put(GameLostEvent.class, new GameLostStrategy());
}
/**
* Abstrakcyjna klasa zdarzenia aplikacji. Wszystkie zdarzenia aplikacji powinny
* rozszerzać tą klasę.
*
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private abstract class AppStrategy
{
public abstract void process(AppEvent appEvent);
}
/**
* Abstrakcyjna klasa zdarzenia zachodzącego w czasie aktywnej rozgrywki. Wszystkie
* zdarzenia, które zachodzą podczas rozgrywki powinny rozszerzać właśnie tą klasę.
*
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private abstract class GameStrategy extends AppStrategy
{
}
/**
* Abstrakcyjna klasa zdarzenia zachodzącego, gdy rozgrywka jest nieaktywna (menu).
* Wszystkie zdarzenia, które zachodzą podczas rozgrywki powinny rozszerzać właśnie tą
* klasę.
*
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private abstract class NonGameStrategy extends AppStrategy
{
}
/**
* Strategia zarządzająca rozpoczęciem gry.
* <li>Model: Rozpoczęcie gry.</li>
* <li>Widok: Przejście z menu, do widoku gry. Przekazanie stanu modelu.</li>
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
class StartGameStrategy extends NonGameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.Controller.AppStrategy#process(com.szuppe.jakub
* .events.AppEvent)
*/
public void process(AppEvent appEvent)
{
if (!model.gameStarted())
{
model.startGame();
view.startGame(model.getGameMockup());
}
}
}
/**
* Strategia uruchamiana, gdy piłka zostanie wypuszczona przez gracza.
* <li>Model: Wypuszczenie piłki.</li>
* <li>Widok: Przekazanie nowego stanu modelu.</li>
* <br/>
* Strategia dba o to, żeby model został pobudzony do przeliczenia nowego stanu, kiedy
* jest stan przestanie być ważny.
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private class BallReleaseStrategy extends GameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.Controller.AppStrategy#process(com.szuppe.jakub
* .events.AppEvent)
*/
public void process(AppEvent appEvent)
{
if (!model.ballIsBouncing() && model.gameStarted())
{
try
{
// Przeliczenie stanu modelu
model.recalculateValidTime();
// Wypuszczenie piłki
model.releaseBall();
// Przeliczenie stanu modelu.
model.recalculateValidTime();
// Przesłanie danych o piłce.
view.setGameUsingMockup(model.getGameMockup());
// Jeżeli model jest bezterminowo ważny nie kasujemy
// zadanie, czyścimy timer.
// WPP:
// Ustawienie operacji validTimePassedTask
// na timerze aplikacji. Aby stan modelu przeliczył się
// po czasie validTime.
// Patrz: ValidTimePassedEvent, ValidTimePassedStrategy
if(!model.isValidWithoutTimeLimit())
{
long validTime = model.getValidTime();
modelTimer.reschedule(validTimePassedTask, validTime);
}
else
{
modelTimer.cancelTask();
}
} catch (AllBricksDestroyedException e)
{
// Model poinformował, że wszystkie klocki zostały zbite
eventsBlockingQueue.add(new LoadNextLevelEvent());
} catch (PlayerDiedException e)
{
// Model poinformował, że gracz umarł.
eventsBlockingQueue.add(new GameLostEvent());
}
}
}
}
/**
* Strategia wykonywana, gdy użytkownik chce poruszyć paletką.
* <li>Model: Odpowiednie przesunięcie paletki.</li>
* <li>Widok: Przekazanie nowego stanu modelu.</li>
* <br/>
* Strategia dba o to, żeby model został pobudzony do przeliczenia nowego stanu, kiedy
* jest stan przestanie być ważny.
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private class MovePaddleStrategy extends GameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.AppStrategy#process(com.szuppe.jakub.events.AppEvent
* , com.szuppe.jakub.model.Model, com.szuppe.jakub.view.View,
* java.util.concurrent.BlockingQueue, java.util.Timer)
*/
public void process(AppEvent appEvent)
{
try
{
MovePaddleEvent movePaddleEvent = (MovePaddleEvent) appEvent;
// Przeliczenie stanu modelu
model.recalculateValidTime();
// Wykonanie
model.acceleratePaddle(movePaddleEvent.getDirection());
// Ponowne przeliczenie stanu modelu.
model.recalculateValidTime();
// Pobranie makiet z modelu i przesłanie do widoku.
view.setGameUsingMockup(model.getGameMockup());
// Jeżeli model jest bezterminowo ważny nie kasujemy
// zadanie, czyścimy timer.
// WPP:
// Ustawienie operacji validTimePassedTask
// na timerze aplikacji. Aby stan modelu przeliczył się
// po czasie validTime.
// Patrz: ValidTimePassedEvent, ValidTimePassedStrategy
if(!model.isValidWithoutTimeLimit())
{
long validTime = model.getValidTime();
modelTimer.reschedule(validTimePassedTask, validTime);
}
else
{
modelTimer.cancelTask();
}
} catch (AllBricksDestroyedException e)
{
// Model poinformował, że wszystkie klocki zostały zbite
eventsBlockingQueue.add(new LoadNextLevelEvent());
} catch (PlayerDiedException e)
{
// Model poinformował, że gracz umarł.
eventsBlockingQueue.add(new GameLostEvent());
}
}
}
/**
* Strategia wykonywana, gdy minie okres poprawności stanu modelu. Model musi obliczyć nowy stan.
* <li>Model: Przeliczenie nowego stanu.</li>
* <li>Widok: Przekazanie nowego stanu modelu.</li>
* <br/>
* Strategia dba o to, żeby model został pobudzony do przeliczenia nowego stanu, kiedy
* jest stan przestanie być ważny.
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private class ValidTimePassedStrategy extends GameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.Controller.AppStrategy#process(com.szuppe.jakub
* .events.AppEvent)
*/
public void process(AppEvent appEvent)
{
try
{
model.recalculateValidTime();
view.setGameUsingMockup(model.getGameMockup());
// Jeżeli model jest bezterminowo ważny nie kasujemy
// zadanie, czyścimy timer.
// WPP:
// Ustawienie operacji validTimePassedTask
// na timerze aplikacji. Aby stan modelu przeliczył się
// po czasie validTime.
// Patrz: ValidTimePassedEvent, ValidTimePassedStrategy
if(!model.isValidWithoutTimeLimit())
{
long validTime = model.getValidTime();
modelTimer.reschedule(validTimePassedTask, validTime);
}
else
{
modelTimer.cancelTask();
}
} catch (AllBricksDestroyedException e)
{
// Model poinformował, że wszystkie klocki zostały zbite
eventsBlockingQueue.add(new LoadNextLevelEvent());
} catch (PlayerDiedException e)
{
// Model poinformował, że gracz umarł.
eventsBlockingQueue.add(new GameLostEvent());
}
}
}
/**
* Strategia wykonywana, gdy potrzeba załadować kolejną plansze/mapę gry.
* <li>Model: Wczytanie nowej mapy. Ustawienie piłeczki i platformy
* na startowych pozycjach.</li>
* <li>Widok: Przekazanie nowego stanu modelu.</li>
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private class LoadNextMapStrategy extends GameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.Controller.AppStrategy#process(com.szuppe.jakub
* .events.AppEvent)
*/
public void process(AppEvent appEvent)
{
try
{
// Zatrzymanie zadania ValidTimePassedTask
// No bo i tak zmieniam stan modelu poprzez załadowanie
// nowej mapy i ustawienie obiektów na pozycjach startowych
modelTimer.cancelTask();
model.loadNextMap();
// Wysłanie całego stanu, stan zawiera nowe elementy
// Zatem trzeba wykonać revalidate na gamePanel.
view.setGameUsingMockupAndRevalidate(model.getGameMockup());
} catch (NoMoreMapsException e)
{
// Nie ma już map, tj. gracz wygrał.
eventsBlockingQueue.add(new GameWinEvent());
}
}
}
/**
* Strategia wykonywana, gdy gra została wygrana.
* <li>Model: Zakończenie gry.</li>
* <li>Widok: Przejście to ekranu z gratulacjami.</li>
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private class GameWinStrategy extends GameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.Controller.AppStrategy#process(com.szuppe.jakub
* .events.AppEvent)
*/
public void process(AppEvent appEvent)
{
model.endGame();
view.endGame(true);
}
}
/**
* Strategia wykonywana, gdy gracz stracił wszystkie życia i przegrał.
* <li>Model: Zakończenie gry.</li>
* <li>Widok: Przejście to ekranu z propozycją ponownej gry.</li>
* <br/>
* @author Jakub Szuppe <j.szuppe at gmail.com>
*/
private class GameLostStrategy extends GameStrategy
{
/*
* (non-Javadoc)
* @see
* com.szuppe.jakub.controller.Controller.AppStrategy#process(com.szuppe.jakub
* .events.AppEvent)
*/
public void process(AppEvent appEvent)
{
model.endGame();
view.endGame(false);
}
}
}