using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Media;
using System.IO;
namespace GameExample.Reversi
{
/// <summary>
/// Handles game video, input, and sounds.
/// </summary>
public class GameRender : IDisposable
{
/// <summary>
/// The height of the status bar
/// </summary>
private const int _statusMessageHeight = 14;
/// <summary>
/// The size of the score circle.
/// </summary>
private const float _circleSize = .85f;
/// <summary>
/// Instance of the active game.
/// </summary>
private Game _game;
/// <summary>
/// The control to paint the game board
/// </summary>
private Control _renderArea;
/// <summary>
/// Control that has status info
/// </summary>
private Control _statusArea;
/// <summary>
/// Backbuffer.
/// </summary>
private Image _backBuffer;
/// <summary>
/// Graphics object for the backbuffer.
/// </summary>
private Graphics _gameGraphics;
/// <summary>
/// The position size.
/// </summary>
private Size _positionSize;
/// <summary>
/// The sound player.
/// </summary>
private SoundPlayer _soundPlayer = new SoundPlayer();
/// <summary>
/// The game options.
/// </summary>
private GameOptions _options;
/// <summary>
/// Player one
/// </summary>
private IPlayer _playerOne;
/// <summary>
/// Player two.
/// </summary>
private IPlayer _playerTwo;
/// <summary>
/// The game timer.
/// </summary>
private Timer _timer;
/// <summary>
/// The game manager.
/// </summary>
private GameManager _gameManager;
/// <summary>
/// The message to display in the status bar.
/// </summary>
private string _message;
/// <summary>
/// The duration to display the message if no moves are made.
/// </summary>
private static readonly TimeSpan _defaultMessage = TimeSpan.FromSeconds(2);
private static readonly TimeSpan _skipWaitTime = TimeSpan.FromSeconds(1.5);
private TimeSpan _messageWait = TimeSpan.FromSeconds(2);
private DateTime _messageStart;
private Image _statusBackBuffer;
/// <summary>
/// Graphics object for the backbuffer.
/// </summary>
private Graphics _statusGraphics;
/// <summary>
/// Initializes a new instance of the <see cref="GameRender"/> class.
/// </summary>
/// <param name="game">The game.</param>
/// <param name="renderControl">The render control.</param>
public GameRender(GameManager manager, GameOptions options, Game game, Control renderControl, Control statusControl)
{
// Verify params
if (manager == null)
{
throw new ArgumentNullException("manager");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
if (game == null)
{
throw new ArgumentNullException("game");
}
if (renderControl == null)
{
throw new ArgumentNullException("renderControl");
}
if (statusControl == null)
{
throw new ArgumentNullException("statusControl");
}
_gameManager = manager;
_game = game;
_options = options;
_renderArea = renderControl;
_statusArea = statusControl;
// Create the game timer.
_timer = new Timer();
_timer.Site = _renderArea.Site;
_timer.Tick += new EventHandler(Timer_Tick);
_timer.Interval = 200;
_timer.Enabled = true;
ResetScreen();
// Create the players.
_playerOne = _options.PlayerOne == PlayerType.Computer ? (IPlayer)new ComputerPlayer() : (IPlayer)new HumanPlayer();
_playerTwo = _options.PlayerTwo == PlayerType.Computer ? (IPlayer)new ComputerPlayer() : (IPlayer)new HumanPlayer();
// Play the start sound.
PlaySound(GameStyles.StartSound);
// Set the first turn.
CurrentPlayer.SetTurn(_game.CurrentPlayer, _game);
}
public void ResetScreen()
{
// The size of each position/cell
_positionSize = new Size(
_renderArea.Width / _game.Board.BoardWidth,
_renderArea.Height / _game.Board.BoardHeight
);
// setup the controls
SetupRenderArea();
SetupStatusArea();
_renderArea.Invalidate();
_statusArea.Invalidate();
_renderArea.Update();
_statusArea.Update();
}
/// <summary>
/// Shows a hint.
/// </summary>
public void ShowHint()
{
}
public void Dispose()
{
if (_timer != null)
{
_timer.Dispose();
}
}
/// <summary>
/// Handles the Tick event of the Timer control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void Timer_Tick(object sender, EventArgs e)
{
if (!_game.GameOver)
{
IPlayer currentPlayer = this.CurrentPlayer;
if (currentPlayer.HasMove)
{
if (Move(currentPlayer.Move))
{
currentPlayer.Clear();
CurrentPlayer.SetTurn(_game.CurrentPlayer, _game);
}
else
{
currentPlayer.Clear();
}
}
else if (_game.CurrentPlayerMoves.Count == 0)
{
// Skip the current player.
_game.Skip();
currentPlayer.Clear();
// If the current player is a computer player than wait longer.
ComputerPlayer computer = CurrentPlayer as ComputerPlayer;
if (computer != null)
{
computer.SetTurn(_game.CurrentPlayer, _game, _skipWaitTime);
}
else
{
CurrentPlayer.SetTurn(_game.CurrentPlayer, _game);
}
PlaySound(GameStyles.SkipSound);
SetMessage(string.Format("{0} skipped", _game.CurrentPlayer == Player.PlayerOne ?
Player.PlayerTwo : Player.PlayerOne));
}
}
else
{
// Game over. Print the winner.
Player player = _game.Leader;
if ( player == Player.NotSet )
{
SetMessage("Gameover. Tie");
}
else
{
SetMessage(string.Format("Gameover. {0} wins !", player));
}
}
// Clear the message.
if (DateTime.Now > (_messageStart + _messageWait))
{
SetMessage(null, TimeSpan.MaxValue);
}
}
/// <summary>
/// Event handler that quits the game.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void QuitGame_Click(object sender, EventArgs e)
{
_gameManager.Quit();
}
/// <summary>
/// Event handler that shows game hint.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void ShowHint_Click(object sender, EventArgs e)
{
ShowHint();
}
/// <summary>
/// Handles the Paint event of the hostControl control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
private void hostControl_Paint(object sender, PaintEventArgs e)
{
// Render the screen
RenderScreen(_gameGraphics);
// Flip the backbuffer.
e.Graphics.DrawImage(_backBuffer, 0, 0);
}
private void statusArea_Paint(object sender, PaintEventArgs e)
{
RenderStatusArea(_statusGraphics);
e.Graphics.DrawImage(_statusBackBuffer, 0, 0);
}
/// <summary>
/// Handles the Paint event of the statusArea control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
/// <summary>
/// Handles the Click event of the hostControl control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void RenderControl_Click(object sender, EventArgs e)
{
// Ignore the move if not our return.
HumanPlayer player = CurrentPlayer as HumanPlayer;
if (player == null)
{
return;
}
// Translate the move
Point translatedPoint = TranslatePoint(_renderArea.PointToClient(Control.MousePosition));
// Try to move.
player.SetPosition(translatedPoint);
}
/// <summary>
/// Setups the render area.
/// </summary>
private void SetupRenderArea()
{
// Clear host control and subscribe to events.
_renderArea.Controls.Clear();
_renderArea.Click -= new EventHandler(RenderControl_Click);
_renderArea.Paint -= new PaintEventHandler(hostControl_Paint);
_renderArea.Click += new EventHandler(RenderControl_Click);
_renderArea.Paint += new PaintEventHandler(hostControl_Paint);
// Create the backbuffer and graphics.
_backBuffer = new Bitmap(_renderArea.Width, _renderArea.Height,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
_gameGraphics = Graphics.FromImage(_backBuffer);
}
/// <summary>
/// Setups the status area.
/// </summary>
private void SetupStatusArea()
{
const int buttonWidth = 40;
_statusArea.Controls.Clear();
GameButton exitGame = new GameButton();
exitGame.ButtonText = " Quit";
exitGame.Click += new EventHandler(QuitGame_Click);
exitGame.Location = new Point(_statusArea.Width - (buttonWidth+5), 1);
exitGame.Size = new Size(buttonWidth, (_statusArea.Height - _statusMessageHeight) - 1);
exitGame.VerticalAlignment = VerticalAlignment.Center;
exitGame.HorizontalAlignment = HorizontalAlignment.Left;
exitGame.BorderStyle = BorderStyle.Fixed3D;
exitGame.BackColor = Color.White;
_statusArea.Controls.Add(exitGame);
//GameButton showHint = new GameButton();
//showHint.ButtonText = " Hint";
//showHint.Click += new EventHandler(ShowHint_Click);
//showHint.Location = new Point(exitGame.Left - buttonWidth, 0);
//showHint.Size = new Size(_statusArea.Width, _statusArea.Height - _statusMessageHeight);
//showHint.VerticalAlignment = VerticalAlignment.Center;
//showHint.HorizontalAlignment = HorizontalAlignment.Left;
//showHint.BorderStyle = BorderStyle.Fixed3D;
//_statusArea.Controls.Add(showHint);
// Create the backbuffer and graphics.
_statusBackBuffer = new Bitmap(_statusArea.Width, _statusArea.Height,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
_statusGraphics = Graphics.FromImage(_statusBackBuffer);
_statusArea.Paint += new PaintEventHandler(statusArea_Paint);
}
/// <summary>
/// Renders the screen.
/// </summary>
private void RenderScreen(Graphics graphics)
{
const float circleReduce = .2F;
// Get the positions and board width,height
Position[] positions = _game.Board.Data;
int boardWidth = _game.Board.BoardWidth, boardHeight = _game.Board.BoardHeight;
Rectangle positionRectangle;
int x = 0, y = 0;
// Draw the background.
TransparentControl.PaintBackground(graphics, _renderArea);
Player player;
for (int i = 0; i < positions.Length; i++)
{
// Get the position rectangle.
positionRectangle = new Rectangle(
x, y,
(int)(_positionSize.Width),
(int)(_positionSize.Height));
// Draw the grid.
graphics.DrawRectangle(new Pen(Color.Black, 2),
positionRectangle);
// Get the player.
player = positions[i].Player;
Rectangle playerPosition = positionRectangle;
// Render any circles for any positions that are occupied.
if (player != Player.NotSet)
{
playerPosition.Inflate(-(int)(positionRectangle.Width * circleReduce), -(int)(positionRectangle.Height * circleReduce));
graphics.FillEllipse(
new SolidBrush(player == Player.PlayerOne ? Color.Black : Color.White),
playerPosition
);
}
// Move to next position.
if (i != 0 && ((i + 1) % boardWidth) == 0)
{
x = 0;
y += (int)_positionSize.Height;
}
else
{
x += (int)_positionSize.Width;
}
}
}
/// <summary>
/// Renders the status area.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
private void RenderStatusArea(Graphics graphics)
{
TransparentControl.PaintBackground(graphics,_statusArea);
if (!string.IsNullOrEmpty(_message))
{
graphics.DrawString(_message, GameStyles.MessageFont, new SolidBrush(Color.Black), 0, _statusArea.Height - _statusMessageHeight);
}
const int scoreWidth = 20;
int statusHeight = _statusArea.Height - _statusMessageHeight;
int circleSize = (int)(statusHeight * _circleSize);
Rectangle area = new Rectangle(0, 0, 30, _statusArea.Height - _statusMessageHeight);
area.Inflate(circleSize - statusHeight, circleSize - statusHeight);
string value = "Score : ";
area.Width = (int)(graphics.MeasureString("Score : ", GameStyles.MessageFont).Width);
graphics.DrawString(value,
GameStyles.ScoreHeaderFont,
new SolidBrush(Color.Black),
area
);
// Draw the black player circle.
area.X += area.Width;
area.Width = circleSize;
graphics.FillEllipse(new SolidBrush(Color.Black),
area
);
// Draw the black player score.
area.X += area.Width;
area.Width = scoreWidth;
graphics.DrawString(_game.PlayerOnePositions.Count.ToString(),
GameStyles.MessageFont, new SolidBrush(Color.Black),
area,
new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }
);
// Draw the white player circle
area.X += area.Width;
area.Width = circleSize;
graphics.FillEllipse(new SolidBrush(Color.White),
area
);
// Draw the white player score
area.X += area.Width;
area.Width = scoreWidth;
graphics.DrawString(_game.PlayerTwoPositions.Count.ToString(),
GameStyles.MessageFont, new SolidBrush(Color.Black),
area,
new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }
);
}
/// <summary>
/// Sets the message displayed at the bottom of the screen.
/// </summary>
/// <param name="message">The message.</param>
private void SetMessage(string message)
{
SetMessage(message, _defaultMessage);
}
/// <summary>
/// Sets the message displayed at the bottom of the screen.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="waitTime">The wait time.</param>
private void SetMessage(string message, TimeSpan waitTime)
{
_messageStart = DateTime.Now;
_message = message;
_statusArea.Invalidate();
_statusArea.Update();
}
/// <summary>
/// Moves to the specified point.
/// </summary>
/// <param name="point">The point.</param>
/// <returns></returns>
private bool Move(Point point)
{
Player player = _game.CurrentPlayer;
if (_game.Move(point, player))
{
_renderArea.Invalidate();
_renderArea.Update();
SetMessage(string.Format("{0} move ({1},{2})", player, point.X, point.Y));
PlaySound(GameStyles.MoveSound);
return true;
}
PlaySound(GameStyles.InvalidMoveSound);
return false;
}
/// <summary>
/// Translates a point from pixels to game board coordinates.
/// </summary>
/// <param name="point">The point.</param>
/// <returns></returns>
private Point TranslatePoint(Point point)
{
int x = (int)((point.X) / _positionSize.Width),
y = (int)((point.Y) / _positionSize.Height);
return new Point(x, y);
}
/// <summary>
/// Plays a sound.
/// </summary>
/// <param name="stream">The stream.</param>
private void PlaySound(Stream stream)
{
if (!_options.SoundsEnabled)
{
return;
}
_soundPlayer.Stop();
stream.Seek(0, SeekOrigin.Begin);
_soundPlayer.Stream = stream;
_soundPlayer.Play();
}
/// <summary>
/// Gets the current player.
/// </summary>
/// <value>The current player.</value>
internal IPlayer CurrentPlayer
{
get
{
return _game.CurrentPlayer == Player.PlayerOne ?
_playerOne : _playerTwo;
}
}
}
}