// (c) 2005 kp@kp73.com
using System;
using System.Collections;
using System.Threading;
namespace KP.Tetris{
/// <summary>
/// Controls a game of tetirs.
/// </summary>
public class TetrisGame : IDisposable
{
#region Game instance values
/// <summary>
/// 10000000
/// </summary>
private const int TICKS_PER_SECOND = 1000;
/// <summary>
/// The player of the game.
/// </summary>
private Player m_player;
/// <summary>
/// Indicates when the game has finished.
/// </summary>
private bool m_isOver;
/// <summary>
/// Level of difficulty.
/// </summary>
private int m_level;
/// <summary>
/// Maximum amount of time between downward tetramino movements.
/// </summary>
private long dropDelayTicks;
/// <summary>
/// Number of tetraminos to queue up ready for play.
/// </summary>
private int queueLength = 4;
/// <summary>
/// Collection of tetraminos queued up to be played next.
/// </summary>
private Queue m_tetraminoQueue;
/// <summary>
/// The active tetramino.
/// </summary>
private Tetramino m_activeTetramino;
/// <summary>
/// The game play area.
/// </summary>
private Board m_playArea;
/// <summary>
/// Game observers, for UI presentation.
/// </summary>
private IGameViewer viewer;
/// <summary>
/// Game control thread.
/// </summary>
Thread gameLoopThread;
private bool _paused;
private int spawnRow;
private int spawnColumn;
private object sync = new object();
#endregion
/// <summary>
/// Initialises a new instance of the TetrisGame class.
/// </summary>
public TetrisGame(IGameViewer gameViewer)
{
m_isOver = true;
viewer = gameViewer;
}
public event TetrisEventHandler TetraminoLanded;
public event TetrisEventHandler RowCompleted;
/// <summary>
/// Get the player of the game.
/// </summary>
public Player Player
{
get { return m_player; }
}
/// <summary>
/// Get the current game level of difficulty.
/// </summary>
public int Level
{
get { return m_level + ((PlayArea.RowsCompleted - 1) / 10); }
}
/// <summary>
/// Start the game.
/// </summary>
public void Start(Player gamePlayer, int gameStartLevel)
{
m_player = gamePlayer;
m_level = gameStartLevel;
spawnColumn = (Board.Width / 2);
spawnRow = Board.Height - 1;
Reset();
ThreadStart threadStart = new ThreadStart(GameLoop);
gameLoopThread = new Thread(threadStart);
gameLoopThread.IsBackground = true;
gameLoopThread.Start();
}
/// <summary>
/// Stop the game.
/// </summary>
public void Stop()
{
m_isOver = true;
// not supported by compact framework, probably not even needed anyway!
// gameLoopThread.Join();
}
/// <summary>
/// Main game loop.
/// </summary>
private void GameLoop()
{
SpawnNewTetramino();
while (!IsOver)
{
if (!IsOver && !IsPaused)
{
DoGameRound();
}
Thread.Sleep(100);
}
}
/// <summary>
/// Get game over indicator.
/// </summary>
public bool IsOver
{
get { return m_isOver; }
}
/// <summary>
/// Initialise the game instance ready to begin a new game.
/// </summary>
private void Reset()
{
m_isOver = false;
m_tetraminoQueue = null;
m_activeTetramino = null;
m_playArea = new Board();
UpdateDropDelay();
TetraminoFactory.SeedRandom();
FillTetraminoQueue();
}
/// <summary>
/// Populate the tetramino queue collection with
/// psudeo random tetramino instances.
/// </summary>
private void FillTetraminoQueue()
{
m_tetraminoQueue = new Queue();
while (TetraminoQueue.Count < queueLength)
{
TetraminoQueue.Enqueue(TetraminoFactory.NewTetramino);
}
}
/// <summary>
/// Run one round of the game.
/// </summary>
private void DoGameRound()
{
bool refeshView = false;
if (DropDelayReached())
{
if (ActiveTetramino.State.HasLanded(PlayArea))
{
Player.AwardPoints(Level, ActiveTetramino.State.DropCount);
if (ActiveTetramino.State.Location.X == spawnColumn
&& ActiveTetramino.State.Location.Y == spawnRow)
{
m_isOver = true;
}
else
{
PlayArea.MergeTetramino(ActiveTetramino.State);
int removedRows = PlayArea.RemoveCompletedRows();
if (removedRows > 0 && RowCompleted != null)
RowCompleted(this, null); // fire row competed event
SpawnNewTetramino();
}
UpdateDropDelay();
if (TetraminoLanded != null)
TetraminoLanded(this, null); // fire tetramino landed event
}
else
{
ActiveTetramino.State.DropOneRow(false);
}
refeshView = true;
}
if (refeshView)
{
viewer.Refresh(PlayArea.GetGameView(this));
}
}
private bool DropDelayReached()
{
bool reached = false;
if (ActiveTetramino != null)
{
long nowTicks = Environment.TickCount; // was System.DateTime.Now.Ticks;
long ticksElapsed = nowTicks - ActiveTetramino.State.LastDropTicks;
reached = (ticksElapsed < 0 || ticksElapsed > dropDelayTicks);
}
return reached;
}
/// <summary>
/// Places a new tetramino into play at top of the play area.
/// </summary>
private void SpawnNewTetramino()
{
m_activeTetramino = (Tetramino)TetraminoQueue.Dequeue();
m_activeTetramino.SetState(new TetraminoState(ActiveTetramino));
m_activeTetramino.State.Location = new Coordinate(spawnColumn , spawnRow);
m_tetraminoQueue.Enqueue(TetraminoFactory.NewTetramino);
}
/// <summary>
/// Set the minimum amount of time between downward tetramino movements.
/// </summary>
private void UpdateDropDelay()
{
float dropDelaySeconds = (10.0f - Level) / 20.0f;
dropDelayTicks = (long)(dropDelaySeconds * TICKS_PER_SECOND);
}
/// <summary>
/// Get the active tetramino.
/// </summary>
internal Tetramino ActiveTetramino
{
get { return m_activeTetramino; }
}
/// <summary>
/// Attempt to move the active tetramino within the play area.
/// </summary>
/// <param name="movement"></param>
public void RequestMove(Movement movement)
{
if (!IsPaused && !IsOver && ActiveTetramino.State.RequestMove(movement, PlayArea))
viewer.Refresh(PlayArea.GetGameView(this));
}
/// <summary>
/// Get the collection of tetraminos queued up to play.
/// </summary>
public Queue TetraminoQueue
{
get { return m_tetraminoQueue; }
}
/// <summary>
/// Get the active play area.
/// </summary>
internal Board PlayArea
{
get { return m_playArea; }
}
/// <summary>
/// Get or set game paused indicator.
/// </summary>
public bool IsPaused
{
get { return _paused; }
set { _paused = value; }
}
#region IDisposable Members
/// <summary>
/// Cleanup.
/// </summary>
public void Dispose()
{
if (Player != null) Player.Dispose();
disposed = true;
GC.SuppressFinalize(this);
}
#endregion
bool disposed;
/// <summary>
/// Destructor dispose if not already disposed.
/// </summary>
~TetrisGame()
{
if (!disposed) this.Dispose();
}
}
}
|