using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace PocketLoop
{
public partial class EditorForm : Form
{
private CBoard m_board = null;
private int m_boardOffsetX = 5;
private int m_boardOffsetY = 5;
private CNumber m_selectedNumber = null;
private Random m_rnd = new Random();
private bool m_startedDrawing = false;
private int m_selectedDotX = -1;
private int m_selectedDotY = -1;
private int m_gridSizeX = 5;
private int m_gridSizeY = 5;
private int m_cellSize = 44;
// For painting.
private Bitmap m_backBuffer = null;
private Bitmap m_dotValidImage = null;
private Bitmap m_dotInvalidImage = null;
private Bitmap m_horizConnectorImage = null;
private Bitmap m_vertConnectorImage = null;
private Bitmap[,] m_numberImage = null;
private const double NUMBERREGION_CELLSIZE_PERCENT = 0.75; // Size of number region as a percentage of the cell size.
private const int MIN_MOVE_THRESHOLD = 4;
private const int MIN_DOT_CLICK_DISTANCE = 2;
public EditorForm()
{
InitializeComponent();
m_backBuffer = new Bitmap(ClientSize.Width, ClientSize.Height);
Graphics.FromImage(m_backBuffer).Clear(Color.White);
}
private void DrawSingleDot(Graphics gfx, int x, int y, bool isValid)
{
if (m_dotValidImage == null)
{
m_dotValidImage = new Bitmap(4, 4);
Graphics.FromImage(m_dotValidImage).FillRectangle(new SolidBrush(Color.Gray), 1, 1, 2, 2);
m_dotInvalidImage = new Bitmap(4, 4);
Graphics.FromImage(m_dotInvalidImage).FillRectangle(new SolidBrush(Color.Red), 0, 0, 4, 4);
}
gfx.DrawImage((isValid ? m_dotValidImage : m_dotInvalidImage),
m_boardOffsetX + x * m_cellSize - 1, m_boardOffsetY + y * m_cellSize - 1);
}
private void DrawDots()
{
for (int i = 0; i < m_board.MaxX; i++)
{
for (int j = 0; j < m_board.MaxY; j++)
{
DrawSingleDot(Graphics.FromImage(m_backBuffer), i, j, m_board.Dot[i,j].Valid);
}
}
}
private void DrawHorizConnector(Graphics gfx, int x, int y, CConnector c)
{
if (m_horizConnectorImage == null)
{
m_horizConnectorImage = new Bitmap(m_cellSize - 4, 2);
Graphics.FromImage(m_horizConnectorImage).FillRectangle(new SolidBrush(Color.Black), 0, 0, m_cellSize - 4, 2);
}
if (c.GetStatus() == ConnectorStatus.Set)
{
gfx.DrawImage(m_horizConnectorImage, m_boardOffsetX + x * m_cellSize + 3, m_boardOffsetY + y * m_cellSize);
}
}
private void DrawVertConnector(Graphics gfx, int x, int y, CConnector c)
{
if (m_vertConnectorImage == null)
{
m_vertConnectorImage = new Bitmap(2, m_cellSize - 4);
Graphics.FromImage(m_vertConnectorImage).FillRectangle(new SolidBrush(Color.Black), 0, 0, 2, m_cellSize - 4);
}
if (c.GetStatus() == ConnectorStatus.Set)
{
gfx.DrawImage(m_vertConnectorImage, m_boardOffsetX + x * m_cellSize, m_boardOffsetY + y * m_cellSize + 3);
}
}
private void DrawNumber(Graphics gfx, int x, int y, CNumber n)
{
if (m_numberImage == null)
{
m_numberImage = new Bitmap[4, 2];
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 2; j++)
{
m_numberImage[i, j] = new Bitmap(m_cellSize - 4, m_cellSize - 4);
Graphics g = Graphics.FromImage(m_numberImage[i, j]);
g.Clear(Color.White);
TextCenter(i.ToString(), g, new Font(FontFamily.GenericSansSerif, (m_cellSize) / 2 / (AutoScaleDimensions.Width / 96), FontStyle.Bold),
(j == 1 ? new SolidBrush(Color.Black) : new SolidBrush(Color.Red)), (m_cellSize - 4) / 2, (m_cellSize - 4) / 2);
}
}
}
if (n.Number != -1)
{
gfx.DrawImage(m_numberImage[n.Number, (n.Valid ? 1 : 0)],
x * m_cellSize + m_boardOffsetX + 3, y * m_cellSize + m_boardOffsetY + 2);
}
}
private void TextCenter(string str, Graphics grph, Font fnt, Brush brsh, float cx, float cy)
{
SizeF strParams = grph.MeasureString(str, fnt);
float x = cx - strParams.Width / 2;
float y = cy - strParams.Height / 2;
grph.DrawString(str, fnt, brsh, x, y);
}
private void GetSelectedConnector(int pixelX, int pixelY, out CConnector selectedConnector)
{
selectedConnector = null;
pixelX -= m_boardOffsetX;
pixelY -= m_boardOffsetY;
int sum = (pixelX + pixelY) / m_cellSize;
int diff = (pixelX - pixelY + (m_board.MaxX + m_board.MaxY) * m_cellSize) / m_cellSize - (m_board.MaxX + m_board.MaxY);
int x = (sum + diff + 1) / 2;
int y = (sum - diff > 0 ? (sum - diff) / 2 : (sum - diff - 1) / 2);
if (x >= 0 && x < m_board.MaxX && y >= 0 && y < m_board.MaxY)
{
if (((sum + diff) % 2) == 0)
{
if (x < m_board.MaxX - 1)
{
selectedConnector = m_board.Dot[x, y].Right;
}
}
else
{
if (y < m_board.MaxY - 1)
{
selectedConnector = m_board.Dot[x, y].Down;
}
}
}
}
private bool DidClickNumber(int pixelX, int pixelY, out CNumber numberClicked)
{
numberClicked = null;
// Get point that user clicked.
pixelX -= m_boardOffsetX;
pixelY -= m_boardOffsetY;
// See if user clicked outside grid.
if ((pixelX < 0) || (pixelX > ((m_board.MaxX - 1) * m_cellSize)) || (pixelY < 0) || (pixelY > ((m_board.MaxY - 1) * m_cellSize)))
{
return (false);
}
// Finally check if user clicked the number region of the cell.
int cellPixelX = pixelX % m_cellSize;
int cellPixelY = pixelY % m_cellSize;
int boundary = (int)((m_cellSize * (1 - NUMBERREGION_CELLSIZE_PERCENT)) / 2);
if (((boundary < cellPixelX) && (cellPixelX < (m_cellSize - boundary)))
&& ((boundary < cellPixelY) && (cellPixelY < (m_cellSize - boundary))))
{
int numberCoordX = (int)(pixelX / m_cellSize);
int numberCoordY = (int)(pixelY / m_cellSize);
numberClicked = m_board.Number[numberCoordX, numberCoordY];
return (true);
}
return (false);
}
private void menuNew_Click(object sender, EventArgs e)
{
NewBoard(null);
foreach (CConnector connector in m_board.ConnectorList)
{
connector.SetStatus(ConnectorStatus.Unset);
}
panel1.Hide();
panel2.Hide();
DrawBoard(true);
}
private void NewBoard(CBoard board)
{
if (board == null)
{
m_board = new CBoard(m_gridSizeX + 1, m_gridSizeY + 1);
}
else
{
m_board = board;
}
m_cellSize = Math.Min((Width - 15) / (m_board.MaxX - 1), (Height - 15) / (m_board.MaxY - 1));
m_boardOffsetX = (this.Width - (m_board.MaxX - 1) * m_cellSize) / 2;
m_boardOffsetY = (this.Height - (m_board.MaxY - 1) * m_cellSize) / 2;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(m_backBuffer, 0, 0);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (m_board == null) return;
if (!m_startedDrawing)
{
if (m_selectedNumber == null)
{
CConnector selectedConnector = null;
GetSelectedConnector(e.X, e.Y, out selectedConnector);
if (selectedConnector != null)
{
if (selectedConnector.GetStatus() == ConnectorStatus.Set)
{
selectedConnector.SetStatus(ConnectorStatus.Unset);
}
else if (selectedConnector.GetStatus() == ConnectorStatus.Unset)
{
selectedConnector.SetStatus(ConnectorStatus.Set);
}
DrawBoard(false);
}
}
}
else
{
m_startedDrawing = false;
}
}
private void DrawBoard(bool newBoard)
{
Graphics gfx = Graphics.FromImage(m_backBuffer);
gfx.Clear(Color.White);
DrawDots();
if (!newBoard)
{
for (int x = 0; x < m_board.MaxX; x++)
{
for (int y = 0; y < m_board.MaxY; y++)
{
if (x + 1 < m_board.MaxX)
{
DrawHorizConnector(gfx, x, y, m_board.Dot[x, y].Right);
}
if (y + 1 < m_board.MaxY)
{
DrawVertConnector(gfx, x, y, m_board.Dot[x, y].Down);
}
if (x + 1 < m_board.MaxX && y + 1 < m_board.MaxY)
{
DrawNumber(gfx, x, y, m_board.Number[x, y]);
}
}
}
}
Invalidate();
}
private void GetClosestDotCoordinates(int pixelX, int pixelY, out int coordX, out int coordY)
{
// Get point that user clicked. Don't allow points outside the grid.
pixelX -= m_boardOffsetX;
pixelY -= m_boardOffsetY;
pixelX = (pixelX < 0) ? 0 : pixelX;
pixelY = (pixelY < 0) ? 0 : pixelY;
pixelX = (pixelX > (m_cellSize * m_board.MaxX)) ? (m_cellSize * m_board.MaxX) : pixelX;
pixelY = (pixelY > (m_cellSize * m_board.MaxY)) ? (m_cellSize * m_board.MaxY) : pixelY;
// Get "coordinates" of the top left corner of the box the user clicked.
coordX = (int)(pixelX / m_cellSize);
coordY = (int)(pixelY / m_cellSize);
// Now figure out which corner of the box the user wants.
if ((pixelX % m_cellSize) >= (m_cellSize / 2))
{
// Want right half of box. Make sure we don't go outside the grid.
coordX = (coordX < (m_board.MaxX - 1)) ? (coordX + 1) : coordX;
}
if ((pixelY % m_cellSize) >= (m_cellSize / 2))
{
// Want bottom half of box. Make sure we don't go outside the grid.
coordY = (coordY < (m_board.MaxY - 1)) ? (coordY + 1) : coordY;
}
}
private int GetDistanceFromDot(int pixelX, int pixelY, int dotX, int dotY)
{
pixelX -= m_boardOffsetX;
pixelY -= m_boardOffsetY;
double distanceX = Math.Abs((dotX * m_cellSize) - pixelX);
double distanceY = Math.Abs((dotY * m_cellSize) - pixelY);
double distance = Math.Sqrt((distanceX * distanceX) + (distanceY * distanceY));
return ((int)distance);
}
protected override void OnMouseDown(MouseEventArgs e)
{
CNumber selectedNumber = null;
if (m_board == null) return;
if (DidClickNumber(e.X, e.Y, out selectedNumber))
{
m_selectedNumber = selectedNumber;
panel1.Show();
DrawBoard(false);
}
else
{
GetClosestDotCoordinates(e.X, e.Y, out m_selectedDotX, out m_selectedDotY);
if (GetDistanceFromDot(e.X, e.Y, m_selectedDotX, m_selectedDotY) > MIN_DOT_CLICK_DISTANCE)
{
m_selectedDotX = -1;
m_selectedDotY = -1;
}
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (m_board == null) return;
if (m_startedDrawing)
{
// Get connector to mark as set.
CConnector modifiedConnector = null;
int currCoordX = -1;
int currCoordY = -1;
GetClosestDotCoordinates(e.X, e.Y, out currCoordX, out currCoordY);
if ((currCoordX > m_selectedDotX) && (currCoordY == m_selectedDotY))
{
// Moved right.
modifiedConnector = m_board.Dot[m_selectedDotX, m_selectedDotY].Right;
}
else if ((currCoordX == m_selectedDotX) && (currCoordY > m_selectedDotY))
{
// Moved down.
modifiedConnector = m_board.Dot[m_selectedDotX, m_selectedDotY].Down;
}
else if ((currCoordX < m_selectedDotX) && (currCoordY == m_selectedDotY))
{
// Moved left.
modifiedConnector = m_board.Dot[m_selectedDotX, m_selectedDotY].Left;
}
else if ((currCoordX == m_selectedDotX) && (currCoordY < m_selectedDotY))
{
// Moved up.
modifiedConnector = m_board.Dot[m_selectedDotX, m_selectedDotY].Up;
}
else
{
return;
}
m_selectedDotX = currCoordX;
m_selectedDotY = currCoordY;
// Toggle between set and unset.
if (modifiedConnector.GetStatus() == ConnectorStatus.Set)
{
modifiedConnector.SetStatus(ConnectorStatus.Unset);
}
else if (modifiedConnector.GetStatus() == ConnectorStatus.Unset)
{
modifiedConnector.SetStatus(ConnectorStatus.Set);
}
DrawBoard(false);
}
else
{
if ((m_selectedDotX > 0) && (m_selectedDotY > 0))
{
// Get new distance.
if (GetDistanceFromDot(e.X, e.Y, m_selectedDotX, m_selectedDotY) > MIN_MOVE_THRESHOLD)
{
m_startedDrawing = true;
}
}
}
}
private void panel_Paint(object sender, PaintEventArgs e)
{
Panel panel = (Panel)sender;
e.Graphics.DrawRectangle(new Pen(SystemColors.WindowFrame), 0, 0, panel.Width - 1, panel.Height - 1);
}
private void numberButton_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
int number = -1;
if (button == numberButton0)
{
number = 0;
}
else if (button == numberButton1)
{
number = 1;
}
else if (button == numberButton2)
{
number = 2;
}
else if (button == numberButton3)
{
number = 3;
}
else if (button == numberButtonNone)
{
number = -1;
}
else
{
// Cancel.
panel1.Hide();
DrawBoard(false);
m_selectedNumber = null;
return;
}
Debug.Assert(m_selectedNumber != null);
m_selectedNumber.Number = number;
panel1.Hide();
DrawBoard(false);
m_selectedNumber = null;
}
private void menuValidate_Click(object sender, EventArgs e)
{
string msg = string.Empty;
string title = "Validate Result";
if (!m_board.IsSolved())
{
msg = "Board isn't valid";
}
else
{
msg = "Board is valid";
}
MessageBox.Show(msg, title);
}
private void Reduce()
{
List<MainForm.Tupple> positionList = new List<MainForm.Tupple>();
for (int x = 0; x < m_board.MaxX - 1; x++)
{
for (int y = 0; y < m_board.MaxY - 1; y++)
{
positionList.Insert(m_rnd.Next(positionList.Count), new MainForm.Tupple(x, y));
}
}
progressBar1.Value = 0;
progressBar1.Maximum = positionList.Count + m_board.ConnectorList.Length * 2;
// Set all connectors to unknown first.
waitLabel.Text = "Clearing Connectors";
waitLabel.Update();
foreach (CConnector c in m_board.ConnectorList)
{
c.SetStatus(ConnectorStatus.Unknown);
progressBar1.Value++;
}
// Now reduce.
waitLabel.Text = "Reducing Board";
waitLabel.Update();
int depth;
switch (m_board.MaxX - 1)
{
case 5:
depth = 5;
break;
case 6:
depth = 4;
break;
case 7:
depth = 3;
break;
default:
depth = 2;
break;
}
byte[] savedBoard = m_board.toBinary();
while (positionList.Count > 0)
{
m_board.Number[positionList[0].X, positionList[0].Y].Number = -1;
SimplifyResult r = m_board.Solve(depth, false);
if (r == SimplifyResult.Terminal)
{
m_board.LoadBinary(savedBoard, false);
m_board.Number[positionList[0].X, positionList[0].Y].Number = -1;
savedBoard = m_board.toBinary();
}
positionList.RemoveAt(0);
m_board.LoadBinary(savedBoard, false);
// Update progress bar.
progressBar1.Value++;
}
m_board = new CBoard(savedBoard, false);
foreach (CConnector c in m_board.ConnectorList)
{
c.SetStatus(ConnectorStatus.Unset);
progressBar1.Value++;
}
}
private void menuReduce_Click(object sender, EventArgs e)
{
panel2.Show();
DrawBoard(false);
Update();
Reduce();
panel2.Hide();
DrawBoard(false);
Update();
}
private void menuSave_Click(object sender, EventArgs e)
{
SaveFileDialog saveDlg = new SaveFileDialog();
saveDlg.Filter = "Pocketloop file|*.lop";
if (saveDlg.ShowDialog() == DialogResult.OK)
{
SaveBoardToFile(m_board, saveDlg.FileName);
}
}
private void menuOpen_Click(object sender, EventArgs e)
{
OpenFileDialog openDlg = new OpenFileDialog();
openDlg.Filter = "Pocketloop file|*.lop";
if (openDlg.ShowDialog() == DialogResult.OK)
{
CBoard board = LoadBoardFile(openDlg.FileName);
CheckCorrectGridSizeMenuItem(board.MaxX - 1, board.MaxY - 1);
NewBoard(board);
DrawBoard(false);
}
}
private void menuExit_Click(object sender, EventArgs e)
{
this.Close();
}
private void SaveBoardToFile(CBoard board, string filename)
{
FileStream fs = File.Open(filename, FileMode.Create, FileAccess.Write);
byte[] data = board.toBinary();
fs.Write(data, 0, data.Length);
fs.Close();
}
private CBoard LoadBoardFile(string filename)
{
if (File.Exists(filename))
{
FileStream fs = File.OpenRead(filename);
byte[] data = new byte[fs.Length];
fs.Read(data, 0, (int)fs.Length);
fs.Close();
return new CBoard(data, false);
}
else
{
return (null);
}
}
private void menu5x5_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(5, 5);
}
private void CheckCorrectGridSizeMenuItem(int x, int y)
{
m_gridSizeX = x;
m_gridSizeY = y;
menu10x10.Checked = (m_gridSizeX == 10 && m_gridSizeY == 10);
menu10x7.Checked = (m_gridSizeX == 10 && m_gridSizeY == 7);
menu10x5.Checked = (m_gridSizeX == 10 && m_gridSizeY == 5);
menu9x9.Checked = (m_gridSizeX == 9 && m_gridSizeY == 9);
menu9x6.Checked = (m_gridSizeX == 9 && m_gridSizeY == 6);
menu8x8.Checked = (m_gridSizeX == 8 && m_gridSizeY == 8);
menu8x5.Checked = (m_gridSizeX == 8 && m_gridSizeY == 5);
menu7x7.Checked = (m_gridSizeX == 7 && m_gridSizeY == 7);
menu6x6.Checked = (m_gridSizeX == 6 && m_gridSizeY == 6);
menu5x5.Checked = (m_gridSizeX == 5 && m_gridSizeY == 5);
}
private void menu6x6_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(6, 6);
}
private void menu7x7_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(7, 7);
}
private void menu8x5_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(8, 5);
}
private void menu8x8_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(8, 8);
}
private void menu9x6_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(9, 6);
}
private void menu9x9_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(9, 9);
}
private void menu10x7_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(10, 7);
}
private void menu10x10_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(10, 10);
}
private void menu10x5_Click(object sender, EventArgs e)
{
CheckCorrectGridSizeMenuItem(10, 5);
}
}
}