using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SpiralTicTacToe
{
#region EVENT ARGS
public class MoveRequestRoutedEventArgs : RoutedEventArgs
{
private int _hub;
private int _spoke;
public MoveRequestRoutedEventArgs(RoutedEvent routedEvent, object source, int hub, int spoke)
: base(routedEvent, source)
{
_hub = hub;
_spoke = spoke;
}
public int Hub
{
get { return _hub; }
}
public int Spoke
{
get { return _spoke; }
}
}
#endregion
public class SpiralBoard : FrameworkElement
{
private const int TOLERANCE = 12;
private VisualCollection _visuals;
private Point[,] _positions;
private Color _backColor;
private Color _boardColor;
private Color _crossColor;
private Color _circleColor;
private Brush _backBrush;
private Pen _boardPen;
private Pen _crossPen;
private Pen _circlePen;
#region ROUTED EVENTS SETUP
public static readonly RoutedEvent MoveRequestEvent;
static SpiralBoard()
{
//register routed event
SpiralBoard.MoveRequestEvent = EventManager.RegisterRoutedEvent(
"MoveRequest", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SpiralBoard));
}
public event RoutedEventHandler MoveRequest
{
add { AddHandler(SpiralBoard.MoveRequestEvent, value); }
remove { RemoveHandler(SpiralBoard.MoveRequestEvent, value); }
}
#endregion
#region CONSTRUCTION
public SpiralBoard()
{
_visuals = new VisualCollection(this);
_boardColor = Colors.Black;
_crossColor = Colors.Black;
_circleColor = Colors.Black;
_boardPen = new Pen(new SolidColorBrush(_boardColor),1);
_crossPen = new Pen(new SolidColorBrush(_boardColor), 2);
_circlePen = new Pen(new SolidColorBrush(_boardColor), 2);
this.ClipToBounds = true;
this.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(Board_MouseLeftButtonUp);
}
#endregion
#region OVERRIDES
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
_visuals.Add(DrawBoardVisual()); //side-effect: initialize _positions
}
protected override int VisualChildrenCount
{
get { return _visuals.Count; }
}
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index > _visuals.Count)
{
throw new ArgumentOutOfRangeException();
}
return _visuals[index];
}
#endregion
#region EVENT HANDLERS
private void Board_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Point hit = e.GetPosition(this);
hit.Offset(-this.Width / 2.0, -this.Width / 2.0);
for (int hub = 0; hub < SpiralAI.HUBS; hub++)
{
for (int spoke = 0; spoke < SpiralAI.SPOKES; spoke++)
{
if (Math.Abs(_positions[hub, spoke].X - hit.X) <= TOLERANCE && Math.Abs(_positions[hub, spoke].Y - hit.Y) <= TOLERANCE)
{
RaiseEvent(new MoveRequestRoutedEventArgs(SpiralBoard.MoveRequestEvent, this, hub, spoke));
break;
}
}
}
}
#endregion
#region PUBLIC PROPERTIES
[Category("Colors")]
[TypeConverter(typeof(ColorConverter))]
public Color BackColor
{
get { return _backColor; }
set
{
_backColor = value;
_backBrush = new SolidColorBrush(_backColor);
}
}
[Category("Colors")]
[TypeConverter(typeof(ColorConverter))]
public Color BoardColor
{
get { return _boardColor; }
set
{
_boardColor = value;
_boardPen = new Pen(new SolidColorBrush(_boardColor), 1);
}
}
[Category("Colors")]
[TypeConverter(typeof(ColorConverter))]
public Color CrossColor
{
get { return _crossColor; }
set
{
_crossColor = value;
_crossPen = new Pen(new SolidColorBrush(_crossColor), 2);
}
}
[Category("Colors")]
[TypeConverter(typeof(ColorConverter))]
public Color CircleColor
{
get { return _circleColor; }
set
{
_circleColor = value;
_circlePen = new Pen(new SolidColorBrush(_circleColor), 2);
}
}
#endregion
#region PUBLIC METHODS
public void Reset()
{
if (_visuals.Count > 1)
{
_visuals.RemoveRange(1, _visuals.Count - 1);
}
}
public void ExecuteMove(int hub, int spoke, SpiralAIOccupier occupier)
{
//occupier width
double fromWidth = this.Width * 0.25;
double toWidth = this.Width * 0.025;
if (occupier == SpiralAIOccupier.Cross)
toWidth = this.Width * 0.04;
DrawingVisual visual = new DrawingVisual();
DrawingContext context = visual.RenderOpen();
//translate origin
context.PushTransform(new TranslateTransform(this.Width / 2.0, this.Width / 2.0));
//draw occupier
if (occupier == SpiralAIOccupier.Cross)
{
GeometryGroup group = new GeometryGroup();
LineGeometry line1 = new LineGeometry(
new Point(
_positions[hub, spoke].X - (fromWidth / 2.0),
_positions[hub, spoke].Y - (fromWidth / 2.0)),
new Point(
_positions[hub, spoke].X + (fromWidth / 2.0),
_positions[hub, spoke].Y + (fromWidth / 2.0)));
LineGeometry line2 = new LineGeometry(
new Point(
_positions[hub, spoke].X - (fromWidth / 2.0),
_positions[hub, spoke].Y + (fromWidth / 2.0)),
new Point(
_positions[hub, spoke].X + (fromWidth / 2.0),
_positions[hub, spoke].Y - (fromWidth / 2.0)));
group.Children.Add(line1);
group.Children.Add(line2);
GeometryDrawing drawing = new GeometryDrawing(null, _crossPen, group);
context.DrawDrawing(drawing);
//now animate it
PointAnimation line1p1 = new PointAnimation();
line1p1.To = new Point(
_positions[hub, spoke].X - (toWidth / 2.0),
_positions[hub, spoke].Y - (toWidth / 2.0));
PointAnimation line1p2 = new PointAnimation();
line1p2.To = new Point(
_positions[hub, spoke].X + (toWidth / 2.0),
_positions[hub, spoke].Y + (toWidth / 2.0));
PointAnimation line2p1 = new PointAnimation();
line2p1.To = new Point(
_positions[hub, spoke].X - (toWidth / 2.0),
_positions[hub, spoke].Y + (toWidth / 2.0));
PointAnimation line2p2 = new PointAnimation();
line2p2.To = new Point(
_positions[hub, spoke].X + (toWidth / 2.0),
_positions[hub, spoke].Y - (toWidth / 2.0));
line1.BeginAnimation(LineGeometry.StartPointProperty, line1p1);
line1.BeginAnimation(LineGeometry.EndPointProperty, line1p2);
line2.BeginAnimation(LineGeometry.StartPointProperty, line2p1);
line2.BeginAnimation(LineGeometry.EndPointProperty, line2p2);
}
else
{
EllipseGeometry ellipse = new EllipseGeometry(_positions[hub, spoke], fromWidth, fromWidth);
GeometryDrawing drawing = new GeometryDrawing(null, _circlePen, ellipse);
context.DrawDrawing(drawing);
//now animate it
DoubleAnimation radiusXAnimation = new DoubleAnimation();
radiusXAnimation.To = toWidth;
DoubleAnimation radiusYAnimation = new DoubleAnimation();
radiusYAnimation.To = toWidth;
ellipse.BeginAnimation(EllipseGeometry.RadiusXProperty, radiusXAnimation);
ellipse.BeginAnimation(EllipseGeometry.RadiusYProperty, radiusYAnimation);
}
context.Close();
_visuals.Add(visual);
}
public void UndoMove()
{
if (_visuals.Count > 1)
{
_visuals.RemoveAt(_visuals.Count - 1);
}
}
#endregion
#region PRIVATE METHODS
private DrawingVisual DrawBoardVisual()
{
DrawingVisual visual = new DrawingVisual();
DrawingContext context = visual.RenderOpen();
//draw bounding box with back color
context.DrawRectangle(_backBrush, null, new Rect(0, 0, this.Width, this.Height));
//translate origin
context.PushTransform(new TranslateTransform(this.Width / 2.0, this.Width / 2.0));
double diameter, decrement;
int hub, spoke, adjHub, adjSpoke;
double radius, radians;
//draw concentric circles
diameter = this.Width - Convert.ToInt32(0.1 * Convert.ToDouble(this.Width));
decrement = diameter / SpiralAI.HUBS;
for (hub = 0; hub < SpiralAI.HUBS; hub++)
{
diameter = diameter - (hub * decrement);
context.DrawEllipse(null, _boardPen, new Point(0, 0), diameter / 2.0, diameter / 2.0);
}
//draw grid lines
for (radians = 0; radians < (2 * Math.PI); radians += (Math.PI / 6))
{
context.DrawLine(_boardPen, new Point(0, 0), new Point(Convert.ToInt32(Math.Cos(radians) * (this.Width / 2))
, Convert.ToInt32(Math.Sin(radians) * (this.Width / 2))));
}
context.Close();
//record possible occupier positions
diameter = this.Width - Convert.ToInt32(0.1 * Convert.ToDouble(this.Width));
_positions = new Point[SpiralAI.HUBS, SpiralAI.SPOKES];
for (hub = 0; hub < SpiralAI.HUBS; hub++)
{
for (spoke = 0; spoke < SpiralAI.SPOKES; spoke++)
{
//adjust row and column
adjHub = hub + 1;
adjSpoke = spoke + 3;
//calculate radius and angle
radius = Convert.ToDouble((diameter - ((SpiralAI.HUBS - adjHub) * decrement))) / 2;
radians = (Math.PI / 6) * adjSpoke;
//calculate x and y
_positions[hub, spoke].X = Convert.ToInt32(Math.Sin(radians) * radius);
_positions[hub, spoke].Y = Convert.ToInt32(Math.Cos(radians) * radius);
}
}
return visual;
}
#endregion
}
}