/// <summary> /// Выделить внутренний массив под игровое поле. /// </summary> /// <param name="newWidth"></param> /// <param name="newHeight"></param> private void Relocate(int newWidth, int newHeight) { if (newWidth < 0) { throw new ArgumentOutOfRangeException("newWidth", newWidth, "Negative width is not allowed."); } if (newHeight < 0) { throw new ArgumentOutOfRangeException("newHeight", newHeight, "Negative height is not allowed."); } if (m_Width > 0 && m_Height > 0) { CellPlate[] newArray = new CellPlate[newWidth * newHeight]; for (int x = 0; x < m_Width && x < newWidth; x++) { for (int y = 0; y < m_Height && y < newHeight; y++) { newArray[y * newWidth + x] = internalPlateArray[y * m_Width + x]; } } m_Width = newWidth; m_Height = newHeight; internalPlateArray = newArray; } else { m_Width = newWidth; m_Height = newHeight; internalPlateArray = new CellPlate[m_Width * m_Height]; } }
/// <summary> /// Конструктор: задаём шашку, чтобы знали, где нажим происходит. /// </summary> /// <param name="cell">Шашка, на которую действует вытесняющая сила.</param> public CellClickEventArgs(CellPlate cell) { if (cell == null) { throw new ArgumentNullException("cell"); } m_Cell = cell; }
/// <summary> /// Вызывает запуск анимации при помощи внутренного AnimationManager. /// </summary> /// <param name="cell">Какая ячейка должна прокатится?</param> /// <param name="startPosition">С какого места исходит каталово?</param> internal void AddTransition( CellPlate cell, Rectangle startPosition) { animationManager.StartTransition( cell, startPosition); }
// Стили окна для "пухлой" границы. // Специальные методы для кеширования графических объектов. /// <summary> /// Отрисовка игрового поля, с бортиками, направляющими, /// цельным боками и хорошо проклеенным дном. /// </summary> /// <param name="e">Обычный параметр. См. описание у базового класса.</param> protected override void OnPaint(PaintEventArgs e) { UpdateGraphics(); e.Graphics.FillRectangle( backBrush, e.ClipRectangle); e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; e.Graphics.CompositingMode = CompositingMode.SourceOver; e.Graphics.CompositingQuality = CompositingQuality.HighQuality; //e.Graphics.=CompositingMode.SourceOver; for (int x = 0; x < Cells.Width; x++) { for (int y = 0; y < Cells.Height; y++) { CellPlate cell = Cells[x, y]; if (cell != null && cell != m_movingCell) { AnimationManager.TransitionInfo transition = animationManager[cell]; if (transition == null) { painter.DrawCell( e, cell, CellBackColor, ForeColor, Font); } else { painter.DrawCellTransition( e, cell, CellBackColor, ForeColor, Font, transition.StartPosition, transition.CompletionRatio); } } } } if (m_movingCell != null) { painter.DrawCell( e, m_movingCell, CellBackColor, ForeColor, Font); } }
/// <summary> /// Создание "судьбы". /// </summary> /// <param name="cell">Шашка, которая поедет на своё место.</param> /// <param name="startPosition">Старое состояние шашки.</param> public TransitionInfo( CellPlate cell, Rectangle startPosition) { Cell = cell; StartPosition = startPosition; StartTime = DateTime.Now; TransitionArea = PaintUtils.Union(startPosition, cell.CellRectangle); }
/// <summary> /// Метод, который внешний код вызывает для отрисовки статической шашки (шашки, которая не движется). /// </summary> /// <param name="e">Стандартный параметр метода OnPaint любого контрола. Совмещает полотно для отрисовки и границы.</param> /// <param name="cell">Шашка, которую нужно нарисовать.</param> /// <param name="cellBackColor">В каком тоне следует рисовать шашку.</param> /// <param name="cellCaptionColor">В каком тоне следует рисовать циферки.</param> /// <param name="font">Каким вообще-то шрифтом нужно рисовать цифры.</param> public void DrawCell( PaintEventArgs e, CellPlate cell, Color cellBackColor, Color cellCaptionColor, Font font) { Rectangle cellRect = cell.CellRectangle; DrawCellAt( cellRect, e, cell, cellBackColor, cellCaptionColor, font); }
/// <summary> /// Чтобы расставить шашки, есть только один путь. /// </summary> /// <param name="x">Координата шашки (колонка).</param> /// <param name="y">Координата (строка).</param> /// <returns>Созданная и расставленная шашка.</returns> public CellPlate CreateCell(int x, int y) { if (this[x, y] != null) { throw new ArgumentException("Cell at this point is already present."); } CellPlate result = new CellPlate(this, x, y); internalPlateArray[y * Width + x] = result; result.Invalidate(); return(result); }
/// <summary> /// Обрабатываем клик на контроле, находим шашку и проводим по бумагам как клик на шашке. /// </summary> protected override void OnMouseUp(MouseEventArgs e) { mousePos = new Point(e.X, e.Y); base.OnMouseUp(e); m_Cells.IsCellRectangleValid = false; CellPlate movingCell = m_movingCell; m_movingCell = null; if (movingCell != null) { for (int x = 0; x < Cells.Width; x++) { for (int y = 0; y < Cells.Height; y++) { CellPlate cell = Cells[x, y]; if (cell == null) { if (m_Cells.emptyRect.Contains(mousePos)) { Cells.InternalShiftCellRelative(movingCell, x - movingCell.X, y - movingCell.Y, true); Invalidate(); return; } } else if (cell.CellRectangle.Contains(mousePos)) { if (cell == movingCell) { OnCellClick(new CellClickEventArgs(cell)); } else { Cells.InternalShiftCellRelative(movingCell, x - movingCell.X, y - movingCell.Y, false); } Invalidate(); return; } } } Invalidate(); } }
// Методы для упрощения кешированием графических объектов. // Если, например, цвет кисте совпадает с требуемым, то кисть используется как есть. // Просто аккуратная работа. /// <summary> /// Внутренний метод класса-рисовальщика CellPainter. /// Рисует шашку в некоторой позиции. /// </summary> /// <param name="cellRect">Позиция, где должна быть порисована шашка.</param> /// <param name="e">Стандартный параметр OnPaint.</param> /// <param name="cell">Шашка собственной персоной.</param> /// <param name="cellBackColor">Цвет фона шашки.</param> /// <param name="cellCaptionColor">Цвет шрифта шашки.</param> /// <param name="font">Шрифт для циферок. Очень удобно.</param> private void DrawCellAt( Rectangle cellRect, PaintEventArgs e, CellPlate cell, Color cellBackColor, Color cellCaptionColor, Font font) { if (cell == null) { throw new ArgumentNullException("cell"); } if (!e.ClipRectangle.IntersectsWith(cellRect)) { return; } UpdateGraphics(cellBackColor, cellCaptionColor); cellRect.Inflate(-2, -2); Rectangle fillRect = cellRect; //fillRect.Offset(0,-1); e.Graphics.FillRectangle( backBrush, fillRect); DrawCellBorder(e, cellRect); // caption Rectangle captionRect = cellRect; captionRect.Inflate(-2, -2); captionRect.Offset(1, 1); e.Graphics.DrawString( cell.Caption, font, captionBrush, captionRect, SF); }
/// <summary> /// Нужно рассчитать положения шашек в точных пикселах. /// Пока ничего не двигается, специальный флажок "запирает" пересчёт, /// то есть насчитанные прямоугольники используются. /// При изменении пересчёт вызывается один раз для всего поля. /// </summary> internal void NeedCellRectangle() { if (IsCellRectangleValid) { return; } Rectangle tableRect = new Rectangle( Point.Empty, Pool.ClientRectangle.Size); tableRect.Inflate(-6, -6); Size cellSize = new Size( tableRect.Width / Width, tableRect.Height / Height); int adjX = tableRect.Width - cellSize.Width * Width; int adjY = tableRect.Height - cellSize.Height * Height; tableRect.Offset(adjX / 2, adjY / 2); tableRect.Width -= adjX - adjX / 2; tableRect.Height -= adjY - adjY / 2; for (int x = 0; x < Width; x++) { for (int y = 0; y < Height; y++) { CellPlate cell = this[x, y]; Rectangle rect = new Rectangle( tableRect.X + cellSize.Width * x, tableRect.Y + cellSize.Height * y, cellSize.Width, cellSize.Height); if (cell != null) { cell.SetCellRectangle(rect); } else { emptyRect = rect; } } } }
/// <summary> /// Метод, который внешний код вызывает для отрисовки движущейся шашки. /// Класс AnimationManager отвечает за то, чтобы этот метод был вызван, но не занимается рисованием. /// </summary> /// <param name="e">Стандартный параметр метода OnPaint.</param> /// <param name="cell">Шашка, которая должна быть запечатлена в движении.</param> /// <param name="cellBackColor">Цвет фона шашки.</param> /// <param name="cellCaptionColor">Цвет цифер на шашке.</param> /// <param name="font">Шрифт для цифер. Желательно, разборчивый.</param> /// <param name="startPosition">Начальная позиция шашки. То место, с которого она едет.</param> /// <param name="completionRatio">Свой путь земной пройдя до половины...</param> public void DrawCellTransition( PaintEventArgs e, CellPlate cell, Color cellBackColor, Color cellCaptionColor, Font font, Rectangle startPosition, decimal completionRatio) { Rectangle transitionRect = PaintUtils.Transition( startPosition, cell.CellRectangle, completionRatio); DrawCellAt( transitionRect, e, cell, cellBackColor, cellCaptionColor, font); }
/// <summary> /// Запуск анимации ячейки. Пройдёт время и анимация закончится, не стоит об этом беспокоиться. /// Всё работу берёт на себя AnimationManager. /// </summary> /// <param name="cell">Шашечка, которая поедет в дальний край. Шашка официально должна находится на финальной позиции.</param> /// <param name="startPosition">Позиция, с которой начинается движение.</param> public void StartTransition( CellPlate cell, Rectangle startPosition) { TransitionInfo transition = transitionByCell[cell] as TransitionInfo; if (transition != null) { transition.InvalidateTransitionArea(); transitionByCell.Remove(cell); } transition = new TransitionInfo( cell, startPosition); transitionByCell[cell] = transition; UpdateTimer(); }
/// <summary> /// Сместить шашку. /// </summary> /// <param name="cell">Шашка.</param> /// <param name="relativeX">Координата (колонка).</param> /// <param name="relativeY">Координата (строка).</param> /// <param name="toEmpty"></param> internal void InternalShiftCellRelative(CellPlate cell, int relativeX, int relativeY, bool toEmpty) { CellPlate oldCell = this[cell.X + relativeX, cell.Y + relativeY]; if (oldCell != null) { if (toEmpty) { throw new InvalidOperationException("Cannot shift, position is not empty."); } oldCell.SetXY(cell.X, cell.Y); } else { emptyCell.Offset(-relativeX, -relativeY); } internalPlateArray[cell.Y * Width + cell.X] = oldCell; cell.SetXY(cell.X + relativeX, cell.Y + relativeY); internalPlateArray[cell.Y * Width + cell.X] = cell; }
protected override void OnMouseDown(MouseEventArgs e) { mousePos = new Point(e.X, e.Y); Focus(); base.OnMouseDown(e); m_movingCell = null; for (int x = 0; x < Cells.Width; x++) { for (int y = 0; y < Cells.Height; y++) { CellPlate cell = Cells[x, y]; if (cell != null && cell.CellRectangle.Contains(mousePos)) { m_Cells.IsCellRectangleValid = true; m_movingCell = cell; return; } } } }
/// <summary> /// Выделить внутренний массив под игровое поле. /// </summary> /// <param name="newWidth"></param> /// <param name="newHeight"></param> private void Relocate(int newWidth, int newHeight) { if (newWidth < 0) throw new ArgumentOutOfRangeException("newWidth", newWidth, "Negative width is not allowed."); if (newHeight < 0) throw new ArgumentOutOfRangeException("newHeight", newHeight, "Negative height is not allowed."); if (m_Width > 0 && m_Height > 0) { CellPlate[] newArray = new CellPlate[newWidth*newHeight]; for (int x = 0; x < m_Width && x < newWidth; x++) for (int y = 0; y < m_Height && y < newHeight; y++) newArray[y*newWidth + x] = internalPlateArray[y*m_Width + x]; m_Width = newWidth; m_Height = newHeight; internalPlateArray = newArray; } else { m_Width = newWidth; m_Height = newHeight; internalPlateArray = new CellPlate[m_Width*m_Height]; } }
/// <summary> /// Получение информации о шашке: движется ли она и в какой точке пути. /// Оракул раскрывает судьбу. /// </summary> public TransitionInfo this[CellPlate cell] { get { return transitionByCell[cell] as TransitionInfo; } }
protected override void OnMouseDown(MouseEventArgs e) { mousePos = new Point(e.X, e.Y); Focus(); base.OnMouseDown(e); m_movingCell = null; for (int x = 0; x < Cells.Width; x++) for (int y = 0; y < Cells.Height; y++) { CellPlate cell = Cells[x, y]; if (cell != null && cell.CellRectangle.Contains(mousePos)) { m_Cells.IsCellRectangleValid = true; m_movingCell = cell; return; } } }
// Методы для упрощения кешированием графических объектов. // Если, например, цвет кисте совпадает с требуемым, то кисть используется как есть. // Просто аккуратная работа. /// <summary> /// Внутренний метод класса-рисовальщика CellPainter. /// Рисует шашку в некоторой позиции. /// </summary> /// <param name="cellRect">Позиция, где должна быть порисована шашка.</param> /// <param name="e">Стандартный параметр OnPaint.</param> /// <param name="cell">Шашка собственной персоной.</param> /// <param name="cellBackColor">Цвет фона шашки.</param> /// <param name="cellCaptionColor">Цвет шрифта шашки.</param> /// <param name="font">Шрифт для циферок. Очень удобно.</param> private void DrawCellAt( Rectangle cellRect, PaintEventArgs e, CellPlate cell, Color cellBackColor, Color cellCaptionColor, Font font) { if (cell == null) throw new ArgumentNullException("cell"); if (!e.ClipRectangle.IntersectsWith(cellRect)) return; UpdateGraphics(cellBackColor, cellCaptionColor); cellRect.Inflate(-2, -2); Rectangle fillRect = cellRect; //fillRect.Offset(0,-1); e.Graphics.FillRectangle( backBrush, fillRect); DrawCellBorder(e, cellRect); // caption Rectangle captionRect = cellRect; captionRect.Inflate(-2, -2); captionRect.Offset(1, 1); e.Graphics.DrawString( cell.Caption, font, captionBrush, captionRect, SF); }
/// <summary> /// Получение информации о шашке: движется ли она и в какой точке пути. /// Оракул раскрывает судьбу. /// </summary> public TransitionInfo this[CellPlate cell] { get { return(transitionByCell[cell] as TransitionInfo); } }
/// <summary> /// Обрабатываем клик на контроле, находим шашку и проводим по бумагам как клик на шашке. /// </summary> protected override void OnMouseUp(MouseEventArgs e) { mousePos = new Point(e.X, e.Y); base.OnMouseUp(e); m_Cells.IsCellRectangleValid = false; CellPlate movingCell = m_movingCell; m_movingCell = null; if (movingCell != null) { for (int x = 0; x < Cells.Width; x++) for (int y = 0; y < Cells.Height; y++) { CellPlate cell = Cells[x, y]; if (cell == null) { if (m_Cells.emptyRect.Contains(mousePos)) { Cells.InternalShiftCellRelative(movingCell, x - movingCell.X, y - movingCell.Y, true); Invalidate(); return; } } else if (cell.CellRectangle.Contains(mousePos)) { if (cell == movingCell) { OnCellClick(new CellClickEventArgs(cell)); } else { Cells.InternalShiftCellRelative(movingCell, x - movingCell.X, y - movingCell.Y, false); } Invalidate(); return; } } Invalidate(); } }
/// <summary> /// Конструктор: задаём шашку, чтобы знали, где нажим происходит. /// </summary> /// <param name="cell">Шашка, на которую действует вытесняющая сила.</param> public CellClickEventArgs(CellPlate cell) { if (cell == null) throw new ArgumentNullException("cell"); m_Cell = cell; }
/// <summary> /// Сместить шашку. /// </summary> /// <param name="cell">Шашка.</param> /// <param name="relativeX">Координата (колонка).</param> /// <param name="relativeY">Координата (строка).</param> /// <param name="toEmpty"></param> internal void InternalShiftCellRelative(CellPlate cell, int relativeX, int relativeY, bool toEmpty) { CellPlate oldCell = this[cell.X + relativeX, cell.Y + relativeY]; if (oldCell != null) { if (toEmpty) throw new InvalidOperationException("Cannot shift, position is not empty."); oldCell.SetXY(cell.X, cell.Y); } else { emptyCell.Offset(-relativeX, -relativeY); } internalPlateArray[cell.Y*Width + cell.X] = oldCell; cell.SetXY(cell.X + relativeX, cell.Y + relativeY); internalPlateArray[cell.Y*Width + cell.X] = cell; }
/// <summary> /// Чтобы расставить шашки, есть только один путь. /// </summary> /// <param name="x">Координата шашки (колонка).</param> /// <param name="y">Координата (строка).</param> /// <returns>Созданная и расставленная шашка.</returns> public CellPlate CreateCell(int x, int y) { if (this[x, y] != null) throw new ArgumentException("Cell at this point is already present."); CellPlate result = new CellPlate(this, x, y); internalPlateArray[y*Width + x] = result; result.Invalidate(); return result; }