private void MainForm_MouseUp(object sender, MouseEventArgs e) { if (_canMovePiece) { _previousMouseX = e.X; _previousMouseY = e.Y; #region Draw the "dropped" moving cluster at its final position into back buffer using (Graphics gfx = Graphics.FromImage(_backBuffer)) { gfx.ResetClip(); gfx.SetClip(_currentCluster.MovableFigure); gfx.DrawImageUnscaled(_currentCluster.Picture, _currentCluster.BoardLocation); } #endregion #region Sync the board, the back buffer, and the display using (Graphics gfx = Graphics.FromImage(_board)) { gfx.DrawImageUnscaled(_backBuffer, 0, 0); } using (Graphics gfx = this.CreateGraphics()) { gfx.DrawImageUnscaled(_backBuffer, 0, 0); } #endregion #region Snapping and combining adjacent pieces Matrix matrix = new Matrix(); // Adjacent clusters to be combined with the current cluster List <int> adjacentClusterIDs = new List <int>(); // For each piece in the current cluster, check if there is any adjacent piece to snap for (int i = 0; i < _currentCluster.Pieces.Count; i++) { Piece currentPiece = _currentCluster.Pieces[i]; foreach (int pieceID in currentPiece.AdjacentPieceIDs) { Piece adjacentPiece = GetPieceByID(pieceID); if (adjacentPiece != null && (adjacentPiece.ClusterID != currentPiece.ClusterID)) { #region Make sure the adjacent piece is located at the correct "side" of the current piece Rectangle adjacentPieceMovableFigureBoardLocation = Rectangle.Truncate(adjacentPiece.MovableFigure.GetBounds()); Rectangle currentPieceMovableFigureBoardLocation = Rectangle.Truncate(currentPiece.MovableFigure.GetBounds()); // Math.Sign(.) returns a number indicating the sign of value. // -1 value is less than zero. 0 value is equal to zero. 1 value is greater than zero. // Tolerance value is set to 2 pixels. if (Math.Abs(currentPiece.SourcePictureLocation.X - adjacentPiece.SourcePictureLocation.X) <= 2) { int figureYDifferenceSign = Math.Sign(currentPieceMovableFigureBoardLocation.Y - adjacentPieceMovableFigureBoardLocation.Y); int sourcePictureYDifferenceSign = Math.Sign(currentPiece.SourcePictureLocation.Y - adjacentPiece.SourcePictureLocation.Y); // Adjacent piece is on the wrong side of the current piece. Do not snap to the current piece. // For example, adjacent piece should be located below the current piece instead of being // located above it. if (figureYDifferenceSign != sourcePictureYDifferenceSign) { continue; } } else if (Math.Abs(currentPiece.SourcePictureLocation.Y - adjacentPiece.SourcePictureLocation.Y) <= 2) { int figureXDifferenceSign = Math.Sign(currentPieceMovableFigureBoardLocation.X - adjacentPieceMovableFigureBoardLocation.X); int sourceImageXDifferenceSign = Math.Sign(currentPiece.SourcePictureLocation.X - adjacentPiece.SourcePictureLocation.X); // Adjacent piece is on the wrong side of the current piece. Do not snap to the current piece. // For example, adjacent piece should be located at the right side of the current piece instead // of being located at the left side. if (figureXDifferenceSign != sourceImageXDifferenceSign) { continue; } } #endregion #region Determine if the adjacent piece should be snapped to the current cluster // ================================================= // If the dimensions of the rectangle bounds of the merged path // almost equals the "unioned" rectangles of the two pieces' source image // dimensions, they can be snapped together. // ================================================= GraphicsPath combinedMovableFigure = new GraphicsPath(); combinedMovableFigure.AddPath(adjacentPiece.MovableFigure, false); combinedMovableFigure.AddPath(currentPiece.MovableFigure, false); Rectangle combinedMovableFigureBoardLocation = Rectangle.Truncate(combinedMovableFigure.GetBounds()); // The combined rectangle based on the source picture dimensions of the two pieces Rectangle combinedSourcePictureLocation = Rectangle.Union(adjacentPiece.SourcePictureLocation, currentPiece.SourcePictureLocation); if (Math.Abs(combinedMovableFigureBoardLocation.Width - combinedSourcePictureLocation.Width) <= GameSettings.SNAP_TOLERANCE && Math.Abs(combinedMovableFigureBoardLocation.Height - combinedSourcePictureLocation.Height) <= GameSettings.SNAP_TOLERANCE) { PieceCluster adjacentPieceCluster = GetPieceClusterByID(adjacentPiece.ClusterID); adjacentClusterIDs.Add(adjacentPieceCluster.ID); // Update the ClusterID for the pieces in adjacent cluster foreach (Piece piece in adjacentPieceCluster.Pieces) { piece.ClusterID = currentPiece.ClusterID; } } #endregion } } } if (adjacentClusterIDs.Count > 0) { #region Remove the adjacent cluster from the list after combining with the current cluster foreach (int clusterID in adjacentClusterIDs) { PieceCluster adjacentCluster = GetPieceClusterByID(clusterID); foreach (Piece piece in adjacentCluster.Pieces) { _currentCluster.Pieces.Add(piece); } RemovePieceGroupByID(clusterID); } #endregion GraphicsPath combinedStaticFigure = new GraphicsPath(); Rectangle combinedBoardLocation = _currentCluster.BoardLocation; Rectangle combinedSourcePictureLocation = _currentCluster.SourcePictureLocation; foreach (Piece piece in _currentCluster.Pieces) { combinedStaticFigure.AddPath(piece.StaticFigure, false); combinedBoardLocation = Rectangle.Union(combinedBoardLocation, piece.BoardLocation); combinedSourcePictureLocation = Rectangle.Union(combinedSourcePictureLocation, piece.SourcePictureLocation); } _currentCluster.BoardLocation = new Rectangle(combinedBoardLocation.X, combinedBoardLocation.Y, combinedSourcePictureLocation.Width, combinedSourcePictureLocation.Height); _currentCluster.SourcePictureLocation = combinedSourcePictureLocation; _currentCluster.Width = combinedSourcePictureLocation.Width; _currentCluster.Height = combinedSourcePictureLocation.Height; _currentCluster.StaticFigure = (GraphicsPath)combinedStaticFigure.Clone(); _currentCluster.MovableFigure = (GraphicsPath)combinedStaticFigure.Clone(); Rectangle combinedStaticFigureLocation = Rectangle.Truncate(combinedStaticFigure.GetBounds()); // Translate the movable figure to the origin first and... matrix.Reset(); matrix.Translate(0 - combinedStaticFigureLocation.X, 0 - combinedStaticFigureLocation.Y); _currentCluster.MovableFigure.Transform(matrix); // ...then translate to the new board location matrix.Reset(); matrix.Translate(combinedBoardLocation.X, combinedBoardLocation.Y); _currentCluster.MovableFigure.Transform(matrix); #region Construct cluster picture // Translate the figure to the origin to draw the picture matrix.Reset(); matrix.Translate(0 - combinedStaticFigureLocation.X, 0 - combinedStaticFigureLocation.Y); GraphicsPath translatedCombinedStaticFigure = (GraphicsPath)combinedStaticFigure.Clone(); translatedCombinedStaticFigure.Transform(matrix); Bitmap clusterPicture = new Bitmap(combinedSourcePictureLocation.Width, combinedSourcePictureLocation.Height); using (Graphics gfx = Graphics.FromImage(clusterPicture)) { gfx.FillRectangle(Brushes.White, 0, 0, clusterPicture.Width, clusterPicture.Height); gfx.ResetClip(); gfx.SetClip(translatedCombinedStaticFigure); gfx.DrawImage(_sourcePicture, new Rectangle(0, 0, clusterPicture.Width, clusterPicture.Height), combinedStaticFigureLocation, GraphicsUnit.Pixel); if (GameSettings.DRAW_PIECE_OUTLINE) { Pen outlinePen = new Pen(Color.Black) { Width = GameSettings.PIECE_OUTLINE_WIDTH, Alignment = PenAlignment.Inset }; gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.DrawPath(outlinePen, translatedCombinedStaticFigure); } } Bitmap modifiedClusterPicture = (Bitmap)clusterPicture.Clone(); ImageUtilities.EdgeDetectHorizontal(modifiedClusterPicture); ImageUtilities.EdgeDetectVertical(modifiedClusterPicture); clusterPicture = ImageUtilities.AlphaBlendMatrix(modifiedClusterPicture, clusterPicture, 255);//200); #endregion _currentCluster.Picture = (Bitmap)clusterPicture.Clone(); // Update the piece's movable figure and board location foreach (Piece piece in _currentCluster.Pieces) { int offsetX = piece.SourcePictureLocation.X - combinedSourcePictureLocation.X; int offsetY = piece.SourcePictureLocation.Y - combinedSourcePictureLocation.Y; int newLocationX = combinedBoardLocation.X + offsetX; int newLocationY = combinedBoardLocation.Y + offsetY; piece.BoardLocation = new Rectangle(newLocationX, newLocationY, piece.Width, piece.Height); Rectangle movableFigureBoardLocation = Rectangle.Truncate(piece.MovableFigure.GetBounds()); // Translate the movable figure to the origin first and... matrix.Reset(); matrix.Translate(0 - movableFigureBoardLocation.X, 0 - movableFigureBoardLocation.Y); piece.MovableFigure.Transform(matrix); // ...then translate to the new board location matrix.Reset(); matrix.Translate(newLocationX, newLocationY); piece.MovableFigure.Transform(matrix); } #region Redraw #region Back buffer Rectangle areaToClear = new Rectangle(combinedBoardLocation.X, combinedBoardLocation.Y, combinedBoardLocation.Width + GameSettings.DROP_SHADOW_DEPTH, combinedBoardLocation.Height + GameSettings.DROP_SHADOW_DEPTH); using (Graphics gfx = Graphics.FromImage(_backBuffer)) { // Clear the area with the background picture first gfx.DrawImage(_background, areaToClear, areaToClear, GraphicsUnit.Pixel); #region Redraw the pieces Region regionToRedraw = new Region(areaToClear); foreach (PieceCluster cluster in _clusters) { Region clusterRegion = new Region(cluster.MovableFigure); clusterRegion.Intersect(regionToRedraw); if (!clusterRegion.IsEmpty(gfx)) { gfx.SetClip(clusterRegion, CombineMode.Replace); gfx.DrawImageUnscaled(cluster.Picture, cluster.BoardLocation); } } #endregion } #endregion #region Board using (Graphics gfx = Graphics.FromImage(_board)) { gfx.DrawImageUnscaled(_backBuffer, 0, 0); } #endregion #region Form using (Graphics gfx = this.CreateGraphics()) { gfx.DrawImageUnscaled(_backBuffer, 0, 0); } #endregion #endregion } #endregion _canMovePiece = false; _currentCluster = null; #region Victory announcement if (_clusters.Count == 1) { if (_victoryAnnounced == false) { _victoryAnnounced = true; //MessageBox.Show("The puzzle has been solved!", "Congratulations!", MessageBoxButtons.OK); } } #endregion } }
private ResponseMessage CreateJigsawPuzzle() { if (_sourcePicture == null) { return(new ResponseMessage { Message = "Please provide source picture." }); } #region Make sure the piece size is not too small int pieceWidth = _sourcePicture.Width / GameSettings.NUM_COLUMNS; int pieceHeight = _sourcePicture.Height / GameSettings.NUM_ROWS; if (pieceWidth < GameSettings.MIN_PIECE_WIDTH || pieceHeight < GameSettings.MIN_PIECE_HEIGHT) { return(new ResponseMessage { Message = "The picture is too small. Please select a bigger picture." }); } int lastColPieceWidth = pieceWidth + (_sourcePicture.Width % GameSettings.NUM_COLUMNS); int lastRowPieceHeight = pieceHeight + (_sourcePicture.Height % GameSettings.NUM_ROWS); #endregion #region Construct jigsaw pieces int lastRow = (GameSettings.NUM_ROWS - 1); int lastCol = (GameSettings.NUM_COLUMNS - 1); bool topCurveFlipVertical = false; bool bottomCurveFlipVertical = true; bool leftCurveFlipHorizontal = false; bool rightCurveFlipHorizontal = true; _currentCluster = null; _clusters = new List <PieceCluster>(); Matrix matrix = new Matrix(); Pen outlinePen = new Pen(Color.Black) { Width = GameSettings.PIECE_OUTLINE_WIDTH, Alignment = PenAlignment.Inset }; int pieceID = 0; for (int row = 0; row < GameSettings.NUM_ROWS; row++) { for (int col = 0; col < GameSettings.NUM_COLUMNS; col++) { GraphicsPath figure = new GraphicsPath(); int offsetX = (col * pieceWidth); int offsetY = (row * pieceHeight); #region Top if (row == 0) { int startX = offsetX; int startY = offsetY; int endX = offsetX + (col == lastCol ? lastColPieceWidth : pieceWidth);; int endY = offsetY; figure.AddLine(startX, startY, endX, endY); } else { int horizontalCurveLength = (col == lastCol ? lastColPieceWidth : pieceWidth); BezierCurve topCurve = BezierCurve.CreateHorizontal(horizontalCurveLength); if (topCurveFlipVertical) { topCurve.FlipVertical(); } topCurve.Translate(offsetX, offsetY); figure.AddBeziers(topCurve.Points); } #endregion #region Right if (col == lastCol) { int startX = offsetX + lastColPieceWidth; int startY = offsetY; int endX = offsetX + lastColPieceWidth; int endY = offsetY + (row == lastRow ? lastRowPieceHeight : pieceHeight); figure.AddLine(startX, startY, endX, endY); } else { int verticalCurveLength = (row == lastRow ? lastRowPieceHeight : pieceHeight); BezierCurve verticalCurve = BezierCurve.CreateVertical(verticalCurveLength); if (rightCurveFlipHorizontal) { verticalCurve.FlipHorizontal(); } verticalCurve.Translate(offsetX + pieceWidth, offsetY); figure.AddBeziers(verticalCurve.Points); } #endregion #region Bottom if (row == lastRow) { int startX = offsetX; int startY = offsetY + lastRowPieceHeight; int endX = offsetX + (col == lastCol ? lastColPieceWidth : pieceWidth); int endY = offsetY + lastRowPieceHeight; figure.AddLine(endX, endY, startX, startY); } else { int horizontalCurveLength = (col == lastCol ? lastColPieceWidth : pieceWidth); BezierCurve bottomCurve = BezierCurve.CreateHorizontal(horizontalCurveLength); bottomCurve.FlipHorizontal(); if (bottomCurveFlipVertical) { bottomCurve.FlipVertical(); } bottomCurve.Translate(offsetX + horizontalCurveLength, offsetY + pieceHeight); figure.AddBeziers(bottomCurve.Points); } #endregion #region Left if (col == 0) { int startX = offsetX; int startY = offsetY; int endX = offsetX; int endY = offsetY + (row == lastRow ? lastRowPieceHeight : pieceHeight); figure.AddLine(endX, endY, startX, startY); } else { int verticalCurveLength = (row == lastRow ? lastRowPieceHeight : pieceHeight); BezierCurve verticalCurve = BezierCurve.CreateVertical(verticalCurveLength); verticalCurve.FlipVertical(); if (leftCurveFlipHorizontal) { verticalCurve.FlipHorizontal(); } verticalCurve.Translate(offsetX, offsetY + verticalCurveLength); figure.AddBeziers(verticalCurve.Points); } #endregion #region Jigsaw information #region Determine adjacent piece IDs for the current piece List <Coordinate> adjacentCoords = new List <Coordinate> { new Coordinate(col, row - 1), new Coordinate(col + 1, row), new Coordinate(col, row + 1), new Coordinate(col - 1, row) }; List <int> adjacentPieceIDs = DetermineAdjacentPieceIDs(adjacentCoords, GameSettings.NUM_COLUMNS); #endregion #region Construct piece picture Rectangle figureLocation = Rectangle.Truncate(figure.GetBounds()); // Translate the figure to the origin to draw the picture for individual piece matrix.Reset(); matrix.Translate(0 - figureLocation.X, 0 - figureLocation.Y); GraphicsPath translatedFigure = (GraphicsPath)figure.Clone(); translatedFigure.Transform(matrix); Rectangle translatedFigureLocation = Rectangle.Truncate(translatedFigure.GetBounds()); Bitmap piecePicture = new Bitmap(figureLocation.Width, figureLocation.Height); using (Graphics gfx = Graphics.FromImage(piecePicture)) { gfx.FillRectangle(Brushes.White, 0, 0, piecePicture.Width, piecePicture.Height); gfx.ResetClip(); gfx.SetClip(translatedFigure); gfx.DrawImage(_sourcePicture, new Rectangle(0, 0, piecePicture.Width, piecePicture.Height), figureLocation, GraphicsUnit.Pixel); if (GameSettings.DRAW_PIECE_OUTLINE) { gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.DrawPath(outlinePen, translatedFigure); } } // Wanted to do the "bevel edge" effect but too complicated for me. Bitmap modifiedPiecePicture = (Bitmap)piecePicture.Clone(); ImageUtilities.EdgeDetectHorizontal(modifiedPiecePicture); ImageUtilities.EdgeDetectVertical(modifiedPiecePicture); piecePicture = ImageUtilities.AlphaBlendMatrix(modifiedPiecePicture, piecePicture, 255);//200); #endregion #region Piece and cluster information Piece piece = new Piece { ID = pieceID, ClusterID = pieceID, Width = figureLocation.Width, Height = figureLocation.Height, BoardLocation = translatedFigureLocation, SourcePictureLocation = figureLocation, MovableFigure = (GraphicsPath)translatedFigure.Clone(), StaticFigure = (GraphicsPath)figure.Clone(), Picture = (Bitmap)piecePicture.Clone(), AdjacentPieceIDs = adjacentPieceIDs }; PieceCluster cluster = new PieceCluster { ID = pieceID, Width = figureLocation.Width, Height = figureLocation.Height, BoardLocation = translatedFigureLocation, SourcePictureLocation = figureLocation, //SourcePictureLocation = new Rectangle(figureLocation.X, figureLocation.Y, figureLocation.Width, figureLocation.Height), MovableFigure = (GraphicsPath)translatedFigure.Clone(), StaticFigure = (GraphicsPath)figure.Clone(), Picture = (Bitmap)piecePicture.Clone(), Pieces = new List <Piece> { piece } }; #endregion _clusters.Add(cluster); #endregion // Flips vertically or horizontally topCurveFlipVertical = !topCurveFlipVertical; bottomCurveFlipVertical = !bottomCurveFlipVertical; leftCurveFlipHorizontal = !leftCurveFlipHorizontal; rightCurveFlipHorizontal = !rightCurveFlipHorizontal; pieceID++; } } #endregion #region Scramble jigsaw pieces Random random = new Random(); int boardWidth = this.ClientSize.Width; int boardHeight = this.ClientSize.Height; foreach (PieceCluster cluster in _clusters) { int locationX = random.Next(1, boardWidth); int locationY = random.Next((menuStrip1.Height + 1), boardHeight); #region Make sure the piece is within client rectangle bounds if ((locationX + cluster.Width) > boardWidth) { locationX = locationX - ((locationX + cluster.Width) - boardWidth); } if ((locationY + cluster.Height) > boardHeight) { locationY = locationY - ((locationY + cluster.Height) - boardHeight); } #endregion for (int index = 0; index < cluster.Pieces.Count; index++) { Piece piece = cluster.Pieces[index]; piece.BoardLocation = new Rectangle(locationX, locationY, piece.Width, piece.Height); matrix.Reset(); matrix.Translate(locationX, locationY); piece.MovableFigure.Transform(matrix); } // Move the figure for cluster piece cluster.BoardLocation = new Rectangle(locationX, locationY, cluster.Width, cluster.Height); matrix.Reset(); matrix.Translate(locationX, locationY); cluster.MovableFigure.Transform(matrix); } #endregion return(new ResponseMessage { Okay = true }); }
private void MainForm_MouseDown(object sender, MouseEventArgs e) { #region Determine which cluster is selected (mouse down) int selectedIndex = -1; for (int index = (_clusters.Count - 1); index >= 0; index--) { if (_clusters[index].MovableFigure.IsVisible(e.X, e.Y)) { selectedIndex = index; break; } } #endregion #region Bring up the selected cluster if (selectedIndex >= 0) { _currentCluster = _clusters[selectedIndex]; // Make the current cluster top-most and modify the list to reflect it _clusters.RemoveAt(selectedIndex); _clusters.Add(_currentCluster); #region Back buffer // ======================================================== // Clear the current moving piece's area with the background image and redraw the // pieces whose region intersect with the area. // ======================================================== using (Graphics gfx = Graphics.FromImage(_backBuffer)) { Rectangle currentClusterBoardLocation = _currentCluster.BoardLocation; // Clear the area with the background picture first gfx.DrawImage(_background, currentClusterBoardLocation, currentClusterBoardLocation, GraphicsUnit.Pixel); #region Redraw the pieces Region currentClusterBoardLocationRegion = new Region(_currentCluster.BoardLocation); foreach (PieceCluster cluster in _clusters) { if (cluster != _currentCluster) { Region clusterRegion = new Region(cluster.MovableFigure); clusterRegion.Intersect(currentClusterBoardLocationRegion); if (!clusterRegion.IsEmpty(gfx)) { gfx.SetClip(clusterRegion, CombineMode.Replace); gfx.DrawImageUnscaled(cluster.Picture, cluster.BoardLocation); } } } #endregion } #endregion #region Board Matrix matrix = new Matrix(); SolidBrush shadowBrush = new SolidBrush(GameSettings.DROP_SHADOW_COLOR); using (Graphics gfx = Graphics.FromImage(_board)) { #region Drop shadow // Simple drop shadow only for now. Alpha-blended drop shadow is too slow and jerky. matrix.Reset(); matrix.Translate(GameSettings.DROP_SHADOW_DEPTH, GameSettings.DROP_SHADOW_DEPTH); GraphicsPath shadowFigure = (GraphicsPath)_currentCluster.MovableFigure.Clone(); shadowFigure.Transform(matrix); gfx.ResetClip(); gfx.SetClip(shadowFigure); gfx.FillPath(shadowBrush, shadowFigure); #endregion #region Cluster picture gfx.ResetClip(); gfx.SetClip(_currentCluster.MovableFigure); gfx.DrawImageUnscaled(_currentCluster.Picture, _currentCluster.BoardLocation); #endregion } #endregion #region Form using (Graphics gfx = this.CreateGraphics()) { gfx.DrawImageUnscaled(_board, 0, 0); } #endregion _previousMouseX = e.X; _previousMouseY = e.Y; _canMovePiece = true; } #endregion }