📄 boardctrl.cs
字号:
////////////////////////////////////////////////
//
// Project: Lines.NET
// Version: 1.1
// Author: Vladimir L.
//
// homepage: http://www.boomsoft.org
// e-mail: support@boomsoft.org
//
// Copyright (c) 2003-2004, Boomsoft.org
//
using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
using Lines.Core;
namespace Lines.GUI
{
/// <summary>
/// Represents the game board on the screen.
/// </summary>
/// <remarks>
/// <p>This control renders the game board on the screen using <see cref="BallCtrl.OnPaint(PaintEventArgs)"/> of
/// <see cref="BallCtrl"/> class to render balls in appropriate locations.</p>
/// <p>It is also responsible for handling user interaction. The user actions are transformed to
/// <see cref="BoardCtrl.CellClick"/> and <see cref="BoardCtrl.BallClick"/> events in response to
/// appropriate player activity.</p>
/// </remarks>
public class BoardCtrl : GenericCtrl
{
/// <summary>
/// Represents the method that will handle eventes related to the field (board control).
/// </summary>
public delegate void FildEventHandler(BoardCtrl board);
/// <summary>
/// Represents the method that will handle <b>CellClick</b> event of a board.
/// </summary>
public delegate void CellEventHandler(BoardCtrl board, Point cell);
/// <summary>
/// Represents the method that will handle <b>BallClick</b> event of a board.
/// </summary>
public delegate void BallEventHandler(BoardCtrl board, BallCtrl ball);
#region Events
/// <summary>
/// Occurs when player has clicked on an empty cell (board square).
/// </summary>
public event CellEventHandler CellClick;
/// <summary>
/// Occurs when player has clicked on a ball that is placed on the board.
/// </summary>
public event BallEventHandler BallClick;
#endregion
/// <summary>
/// Holds references to all ball controls that are currently displayed.
/// </summary>
private ArrayList balls = new ArrayList();
/// <summary>
/// Creates an instance of BoardCtrl class.
/// </summary>
public BoardCtrl()
{
this.MouseDown += new MouseEventHandler(this.OnMouseDown);
}
/// <summary>
/// Renders the control on the screen.
/// </summary>
/// <param name="e">Used to get reference to the Graphics object.</param>
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
if (e.ClipRectangle.Width < ((int)GetCellSize() + 1))
{
// Redraw clip rectangle only
// Draw field lines (has to be done)
//DrawFieldLines(g);
// Draw balls
g.FillRectangle(new SolidBrush(Preferences.FieldBgColor), e.ClipRectangle);
for (int i = 0; i < balls.Count; i++)
{
BallCtrl ball = (BallCtrl)balls[i];
if (e.ClipRectangle.IntersectsWith(ball.Bounds))
{
ball.OnPaint(e);
}
}
// Draw virtual balls
if (Game.VirtualBalls != null)
{
for (int i = 0; i < Game.VirtualBalls.Length; i++)
{
Rectangle rect = GetCellRect(Game.VirtualBalls[i].Position);
if (e.ClipRectangle.IntersectsWith(rect))
{
DrawVirtualBall(g, Game.VirtualBalls[i]);
}
}
}
}
else
{
// Draw frame
Rectangle frame = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
g.DrawRectangle(new Pen(Color.DimGray), frame); // Color.RosyBrown
// Draw field frame
Pen fieldPen = new Pen(Preferences.FieldFgColor);
Rectangle fieldRect = GetFieldRect();
g.FillRectangle(new SolidBrush(Preferences.FieldBgColor), fieldRect);
g.DrawRectangle(fieldPen, fieldRect);
// Draw field lines
DrawFieldLines(g);
// Draw balls
for (int i = 0; i < balls.Count; i++)
{
((BallCtrl)balls[i]).OnPaint(e);
}
// Draw virtual balls
if (Game.VirtualBalls != null)
{
for (int i = 0; i < Game.VirtualBalls.Length; i++)
{
DrawVirtualBall(g, Game.VirtualBalls[i]);
}
}
}
}
/// <summary>
/// Helper method to draw lines that copose the playing field.
/// </summary>
/// <param name="g">The reference to graphics object to paint on.</param>
private void DrawFieldLines(Graphics g)
{
Size gameSize = Game.Board.BoardSize;
Rectangle fieldRect = GetFieldRect();
Pen fieldPen = new Pen(Preferences.FieldFgColor);
float size = GetCellSize();
// Draw horizontal lines
for (int i = 0; i < gameSize.Width; i++)
{
g.DrawLine(
fieldPen,
(int)(i * size) + fieldRect.Left,
fieldRect.Top,
(int)(i * size) + fieldRect.Left,
fieldRect.Bottom);
}
// Draw vertical lines
for (int i = 0; i < gameSize.Height; i++)
{
g.DrawLine(
fieldPen,
fieldRect.Left,
(int)(i * size) + fieldRect.Top,
fieldRect.Right,
(int)(i * size) + fieldRect.Top);
}
}
/// <summary>
/// Renders the "virtual" ball, the ball that will appear on the next step of the game.
/// It appears on the screen (board) as a ball of appropriate color but smaller size.
/// </summary>
/// <param name="g">The reference to graphics object to paint on.</param>
/// <param name="ball">The virtual ball to be rendered.</param>
private void DrawVirtualBall(Graphics g, Ball ball)
{
Rectangle rect = GetCellRect(ball.Position);
rect.Inflate(-rect.Width / 3, -rect.Height / 3);
g.FillEllipse(new SolidBrush(Preferences.GetBallColor(ball.Color)), rect);
}
/// <summary>
/// Handles resize event. Recalculates all ball controls' positions.
/// </summary>
/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
// recalculate ball positions
for (int i = 0; i < balls.Count; i++)
{
BallCtrl ballCtrl = (BallCtrl)balls[i];
ballCtrl.Bounds = GetCellRect(ballCtrl.Ball.Position);
}
}
/// <summary>
/// Adds a new ball to the board control.
/// </summary>
/// <param name="ball">The ball to be added and rendered.</param>
/// <remarks>
/// This method creates an object of <see cref="BallCtrl"/> class to render the ball on the
/// screen, adds this control to the displayed balls collection <see cref="balls"/>, and redraws
/// the region on the screen where this ball should be placed.
/// </remarks>
public void AddNewBall(Ball ball)
{
BallCtrl ballCtrl = FindBall(ball);
if (ballCtrl == null)
{
ballCtrl = new BallCtrl(ball, Preferences);
ballCtrl.Bounds = GetCellRect(ball.Position);
balls.Add(ballCtrl);
}
InvalidateCell(ball.Position);
//ballCtrl.Invalidate();
}
/// <summary>
/// Removes a ball from the board control.
/// <seealso cref="RemoveBalls(Ball[])"/>
/// <seealso cref="RemoveAllBalls()"/>
/// </summary>
/// <param name="ball">The ball to be removed.</param>
/// <remarks>
/// Removes an instance of <see cref="BallCtrl"/> that corresponds to the ball that should be
/// removed, and redraws the region on the screen where this ball was placed.
/// </remarks>
public void RemoveBall(Ball ball)
{
BallCtrl ballCtrl = FindBall(ball);
if (ballCtrl != null)
{
balls.Remove(ballCtrl);
}
InvalidateCell(ball.Position);
}
/// <summary>
/// Removes a collection of balls from the board control.
/// <seealso cref="RemoveBall(Ball)"/>
/// <seealso cref="RemoveAllBalls()"/>
/// </summary>
/// <param name="balls">The collection of balls to be removed.</param>
/// <remarks>
/// The regions where the balls were placed are invalidated accordingly.
/// </remarks>
public void RemoveBalls(Ball[] balls)
{
foreach (Ball ball in balls)
{
RemoveBall(ball);
}
}
/// <summary>
/// Removes all balls from the board.
/// <seealso cref="RemoveBall(Ball)"/>
/// <seealso cref="RemoveBalls(Ball[])"/>
/// </summary>
/// <remarks>
/// This method doesn't invalidate screen. You should call <see cref="Control.Invalidate()"/>
/// method yourself if it is necessary.
/// </remarks>
public void RemoveAllBalls()
{
balls.Clear();
}
/// <summary>
/// Calculates a cell size to fit playing field in a best way on the screen.
/// </summary>
/// <returns>The size of a cell (width and height have equal size).</returns>
private float GetCellSize()
{
Size gameSize = Game.Board.BoardSize;
float sizeX = (float)(this.Width - 1) / gameSize.Width;
float sizeY = (float)(this.Height - 1) / gameSize.Height;
return (sizeX > sizeY) ? sizeY : sizeX;
}
/// <summary>
/// Returns the rectangle of playing field, location and size of field on the screen.
/// </summary>
/// <returns>The rectangle of playing field (not the board control)</returns>
private Rectangle GetFieldRect()
{
Size gameSize = Game.Board.BoardSize;
float size = GetCellSize();
Rectangle fieldRect = new Rectangle(0, 0, (int)(size * gameSize.Width), (int)(size * gameSize.Height));
fieldRect.Offset((this.Width - 1 - fieldRect.Width) / 2, (this.Height - 1 - fieldRect.Height) / 2);
return fieldRect;
}
/// <summary>
/// Returns the rectangle of board cell, location and size of cell on the board.
/// </summary>
/// <param name="cell">Defines the cell (its coordinates) to calculate its rectangle.</param>
/// <returns>The rectangle of cell on the board control.</returns>
public Rectangle GetCellRect(Point cell)
{
Rectangle fieldRect = GetFieldRect();
float size = GetCellSize();
Rectangle cellRect = new Rectangle(
(int)(cell.X * size + fieldRect.Left + 1),
(int)(cell.Y * size + fieldRect.Top + 1),
(int)size - 1,
(int)size - 1
);
return cellRect;
}
/// <summary>
/// Invalidates a region of playing field that corresponds to a certain cell on the board.
/// </summary>
/// <param name="cell">The cell to invalidate.</param>
public void InvalidateCell(Point cell)
{
Invalidate(GetCellRect(cell));
}
/// <summary>
/// Handles the <b>MouseDown</b> event.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">A <see cref="MouseEventArgs"/> that contains the event data.</param>
/// <remarks>
/// Depending on where the mouse was clicked either fires <see cref="BallClick"/> event, or
/// fires <see cref="CellClick"/> event or does nothing.
/// </remarks>
protected void OnMouseDown(object sender, MouseEventArgs e)
{
Point cell = GetCellFromLocation(e.X, e.Y);
if ((cell.X >= 0) && (cell.Y >= 0))
{
BallCtrl ball = FindBall(cell);
if (ball != null)
{
FireBallClick(ball);
}
else
{
FireCellClick(cell);
}
}
}
/// <summary>
/// Matchs a point on the screen to the cell coordinates of game board.
/// </summary>
/// <param name="x">The x-coordinate of a point on the screen.</param>
/// <param name="y">The y-coordinate of a point on the screen.</param>
/// <returns>The coordinates of cell on the board, or <c>[-1, -1]</c> coordinates in case the point
/// on the screen doesn't fall to a region of any board cell.</returns>
protected Point GetCellFromLocation(int x, int y)
{
Size gameSize = Game.Board.BoardSize;
float size = GetCellSize();
Rectangle field = new Rectangle(0, 0, (int)(size * gameSize.Width), (int)(size * gameSize.Height));
field.Offset((this.Width - 1 - field.Width) / 2, (this.Height - 1 - field.Height) / 2);
if (field.Contains(x, y))
{
return new Point(
(int)((x - field.Left) / size),
(int)((y - field.Top) / size));
}
return new Point(-1, -1);
}
/// <summary>
/// Takes care of drawing a next phase of jumping ball.
/// </summary>
public void DrawJumpingBall()
{
// Find and repaint Jumping ball
Ball ball = Game.Board.GetSelectedBall();
if (ball != null)
{
BallCtrl ballCtrl = FindBall(ball);
if (ballCtrl != null)
{
ballCtrl.NexJumpPosition();
InvalidateCell(ball.Position);
}
}
}
/// <summary>
/// Gets the ball control that corresponds to a board position.
/// </summary>
/// <param name="pos">The ball position on the board.</param>
/// <returns>A reference to <see cref="BallCtrl"/> object that corresponds to the position
/// if such one exists, <c>null</c> in case such a control wasn't found.</returns>
public BallCtrl FindBall(Point pos)
{
for (int i = 0; i < balls.Count; i++)
{
BallCtrl ball = (BallCtrl)balls[i];
if (ball.Ball.Position == pos)
{
return ball;
}
}
return null;
}
/// <summary>
/// Gets the ball contrall that refers to a <see cref="Ball"/> object.
/// </summary>
/// <param name="ball">The ball the corresponding ball contral should be found.</param>
/// <returns>A reference to <see cref="BallCtrl"/> object that corresponds to the <see cref="Ball"/>
/// object if such control exists, <c>null</c> in case such one wasn't found.</returns>
public BallCtrl FindBall(Ball ball)
{
return FindBall(ball.Position);
}
/// <summary>
/// Moves ball from one position to another.
/// </summary>
/// <param name="ball">Defines a ball the corresponding to it ball control should be moved.</param>
/// <param name="oldPos">The old position of a ball on the board.</param>
/// <param name="newPos">The new position of a ball on the board.</param>
public void MoveBall(Ball ball, Point oldPos, Point newPos)
{
// Move
BallCtrl ballCtrl = FindBall(ball);
ballCtrl.Bounds = GetCellRect(newPos);
// Redraw
InvalidateCell(oldPos);
InvalidateCell(newPos);
}
/// <summary>
/// Fires <see cref="CellClick"/> event with a reference to this board control.
/// </summary>
/// <param name="cell">The board cell that was clicked.</param>
public void FireCellClick(Point cell)
{
CellClick(this, cell);
}
/// <summary>
/// Fires <see cref="BallClick"/> event with a reference to this board control.
/// </summary>
/// <param name="ball">The ball on the board that was clicked.</param>
public void FireBallClick(BallCtrl ball)
{
BallClick(this, ball);
}
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -