Task: 15 puzzle game



C#: using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

public class FifteenPuzzle
{
    const int GridSize = 4; 
    const int BlockCount = 16;

    static readonly Random R = new Random();

    private List<Button> Puzzles = new List<Button>();
    private int Moves = 0;
    private DateTime Start;

    public class Puzzle
    {
        private int mOrderedNumer;

        public int CurrentNumber;

        public int X;
        public int Y;

        public int InvX
        {
            get { return (GridSize - 1) - X; }
        }
        public int InvY
        {
            get { return (GridSize - 1) - Y; }
        }

        public Puzzle(int OrderedNumer)
        {
            mOrderedNumer = OrderedNumer;

            CurrentNumber = OrderedNumer;

            X = OrderedNumer % GridSize;
            Y = OrderedNumer / GridSize;
        }
        public Puzzle(int OrderedNumer, int CurrentNumber)
            : this(OrderedNumer)
        {
            this.CurrentNumber = CurrentNumber;
        }

        public bool IsEmptyPuzzle
        {
            get { return CurrentNumber >= (BlockCount - 1); }
        }
        public bool IsTruePlace
        {
            get { return (CurrentNumber == mOrderedNumer); }
        }
        public bool NearestWith(Puzzle OtherPz)
        {
            int dx = (X - OtherPz.X);
            int dy = (Y - OtherPz.Y);

            if ((dx == 0) && (dy <= 1) && (dy >= -1)) return true;
            if ((dy == 0) && (dx <= 1) && (dx >= -1)) return true;

            return false;
        }

        public override string ToString()
        {
            return (CurrentNumber + 1).ToString();
        }
    }

    public static void Main(string[] args)
    {
        FifteenPuzzle Game = new FifteenPuzzle();
        Application.Run(Game.CreateForm());
    }

    private Form CreateForm()
    {
        int ButtonSize = 50;
        int ButtonMargin = 3;
        int FormEdge = 9;

        Font ButtonFont = new Font("Arial", 15.75F, FontStyle.Regular);

        Button StartButton = new Button();
        StartButton.Location = new Point(FormEdge, (GridSize * (ButtonMargin + ButtonSize)) + FormEdge);
        StartButton.Size = new Size(86, 23);
        StartButton.Font = new Font("Arial", 9.75F, FontStyle.Regular);
        StartButton.Text = "New Game";
        StartButton.UseVisualStyleBackColor = true;
        StartButton.TabStop = false;

        StartButton.Click += new EventHandler(NewGame);

        int FormWidth = (GridSize * ButtonSize) + ((GridSize - 1) * ButtonMargin) + (FormEdge * 2);
        int FormHeigth = FormWidth + StartButton.Height;

        Form Form = new Form();
        Form.Text = "Fifteen";
        Form.ClientSize = new Size(FormWidth, FormHeigth);
        Form.FormBorderStyle = FormBorderStyle.FixedSingle;
        Form.MaximizeBox = false;
        Form.SuspendLayout();

        for (int i = 0; i < BlockCount; i++)
        {
            Button Bt = new Button();
            Puzzle Pz = new Puzzle(i);

            int PosX = FormEdge + (Pz.X) * (ButtonSize + ButtonMargin);
            int PosY = FormEdge + (Pz.Y) * (ButtonSize + ButtonMargin);
            Bt.Location = new Point(PosX, PosY);

            Bt.Size = new Size(ButtonSize, ButtonSize);
            Bt.Font = ButtonFont;

            Bt.Text = Pz.ToString();
            Bt.Tag = Pz;
            Bt.UseVisualStyleBackColor = true;
            Bt.TabStop = false;

            Bt.Enabled = false;
            if (Pz.IsEmptyPuzzle) Bt.Visible = false;

            Bt.Click += new EventHandler(MovePuzzle);

            Puzzles.Add(Bt);
            Form.Controls.Add(Bt);
        }

        Form.Controls.Add(StartButton);
        Form.ResumeLayout();

        return Form;
    }

    private void NewGame(object Sender, EventArgs E)
    {
        do
        {
            for (int i = 0; i < Puzzles.Count; i++)
            {
                Button Bt1 = Puzzles[R.Next(i, Puzzles.Count)];
                Button Bt2 = Puzzles[i];
                Swap(Bt1, Bt2);
            }
        }
        while (!IsSolvable());

        for (int i = 0; i < Puzzles.Count; i++)
        {
            Puzzles[i].Enabled = true;
        }

        Moves = 0;
        Start = DateTime.Now;
    }

    private void MovePuzzle(object Sender, EventArgs E)
    {
        Button Bt1 = (Button)Sender;
        Puzzle Pz1 = (Puzzle)Bt1.Tag;

        Button Bt2 = Puzzles.Find(Bt => ((Puzzle)Bt.Tag).IsEmptyPuzzle);
        Puzzle Pz2 = (Puzzle)Bt2.Tag;

        if (Pz1.NearestWith(Pz2))
        {
            Swap(Bt1, Bt2);
            Moves++;
        }

        CheckWin();
    }

    private void CheckWin()
    {
        Button WrongPuzzle = Puzzles.Find(Bt => !((Puzzle)Bt.Tag).IsTruePlace);
        bool UWin = (WrongPuzzle == null);

        if (UWin)
        {
            for (int i = 0; i < Puzzles.Count; i++)
            {
                Puzzles[i].Enabled = false;
            }

            TimeSpan Elapsed = DateTime.Now - Start;
            Elapsed = TimeSpan.FromSeconds(Math.Round(Elapsed.TotalSeconds, 0));
            MessageBox.Show(String.Format("Solved in {0} moves. Time: {1}", Moves, Elapsed));
        }
    }

    private void Swap(Button Bt1, Button Bt2)
    {
        if (Bt1 == Bt2) return;

        Puzzle Pz1 = (Puzzle)Bt1.Tag;
        Puzzle Pz2 = (Puzzle)Bt2.Tag;

        int g = Pz1.CurrentNumber;
        Pz1.CurrentNumber = Pz2.CurrentNumber;
        Pz2.CurrentNumber = g;

        Bt1.Visible = true;
        Bt1.Text = Pz1.ToString();
        if (Pz1.IsEmptyPuzzle) Bt1.Visible = false;

        Bt2.Visible = true;
        Bt2.Text = Pz2.ToString();
        if (Pz2.IsEmptyPuzzle) Bt2.Visible = false;
    }

    private bool IsSolvable()
    {
        
        

        int InvCount = 0;
        for (int i = 0; i < Puzzles.Count - 1; i++)
        {
            for (int j = i + 1; j < Puzzles.Count; j++)
            {
                Puzzle Pz1 = (Puzzle)Puzzles[i].Tag;
                if (Pz1.IsEmptyPuzzle) continue;

                Puzzle Pz2 = (Puzzle)Puzzles[j].Tag;
                if (Pz2.IsEmptyPuzzle) continue;

                if (Pz1.CurrentNumber > Pz2.CurrentNumber) InvCount++;
            }
        }

        Button EmptyBt = Puzzles.Find(Bt => ((Puzzle)Bt.Tag).IsEmptyPuzzle);
        Puzzle EmptyPz = (Puzzle)EmptyBt.Tag;

        bool Result = false;
        if ((EmptyPz.InvY + 1) % 2 == 0) 
        {
            
            if (InvCount % 2 != 0) Result = true;
        }
        else 
        {
            
            if (InvCount % 2 == 0) Result = true;
        }
        return Result;
    }
}



Java: package fifteenpuzzle;

import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;

class FifteenPuzzle extends JPanel {

    private final int side = 4;
    private final int numTiles = side * side - 1;

    private final Random rand = new Random();
    private final int[] tiles = new int[numTiles + 1];
    private final int tileSize;
    private int blankPos;
    private final int margin;
    private final int gridSize;
    private boolean gameOver;

    private FifteenPuzzle() {
        final int dim = 640;

        margin = 80;
        tileSize = (dim - 2 * margin) / side;
        gridSize = tileSize * side;

        setPreferredSize(new Dimension(dim, dim + margin));
        setBackground(Color.WHITE);
        setForeground(new Color(0x6495ED)); 
        setFont(new Font("SansSerif", Font.BOLD, 60));

        gameOver = true;

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (gameOver) {
                    newGame();

                } else {

                    int ex = e.getX() - margin;
                    int ey = e.getY() - margin;

                    if (ex < 0 || ex > gridSize || ey < 0 || ey > gridSize) {
                        return;
                    }

                    int c1 = ex / tileSize;
                    int r1 = ey / tileSize;
                    int c2 = blankPos % side;
                    int r2 = blankPos / side;

                    int clickPos = r1 * side + c1;

                    int dir = 0;
                    if (c1 == c2 && Math.abs(r1 - r2) > 0) {
                        dir = (r1 - r2) > 0 ? 4 : -4;
                        
                    } else if (r1 == r2 && Math.abs(c1 - c2) > 0) {
                        dir = (c1 - c2) > 0 ? 1 : -1;
                    }

                    if (dir != 0) {
                        do {
                            int newBlankPos = blankPos + dir;
                            tiles[blankPos] = tiles[newBlankPos];
                            blankPos = newBlankPos;
                        } while (blankPos != clickPos);
                        tiles[blankPos] = 0;
                    }
                    
                    gameOver = isSolved();
                }
                repaint();
            }
        });

        newGame();
    }

    private void newGame() {
        do {
            reset();
            shuffle();
        } while (!isSolvable());
        gameOver = false;
    }

    private void reset() {
        for (int i = 0; i < tiles.length; i++) {
            tiles[i] = (i + 1) % tiles.length;
        }
        blankPos = tiles.length - 1;
    }

    private void shuffle() {
        
        
        int n = numTiles;
        while (n > 1) {
            int r = rand.nextInt(n--);
            int tmp = tiles[r];
            tiles[r] = tiles[n];
            tiles[n] = tmp;
        }
    }

    
    private boolean isSolvable() {
        int countInversions = 0;
        for (int i = 0; i < numTiles; i++) {
            for (int j = 0; j < i; j++) {
                if (tiles[j] > tiles[i]) {
                    countInversions++;
                }
            }
        }
        return countInversions % 2 == 0;
    }

    private boolean isSolved() {
        if (tiles[tiles.length - 1] != 0) {
            return false;
        }
        for (int i = numTiles - 1; i >= 0; i--) {
            if (tiles[i] != i + 1) {
                return false;
            }
        }
        return true;
    }

    private void drawGrid(Graphics2D g) {
        for (int i = 0; i < tiles.length; i++) {
            int r = i / side;
            int c = i % side;
            int x = margin + c * tileSize;
            int y = margin + r * tileSize;

            if (tiles[i] == 0) {
                if (gameOver) {
                    g.setColor(Color.GREEN);
                    drawCenteredString(g, "\u2713", x, y);
                }
                continue;
            }

            g.setColor(getForeground());
            g.fillRoundRect(x, y, tileSize, tileSize, 25, 25);
            g.setColor(Color.blue.darker());
            g.drawRoundRect(x, y, tileSize, tileSize, 25, 25);
            g.setColor(Color.WHITE);

            drawCenteredString(g, String.valueOf(tiles[i]), x, y);
        }
    }

    private void drawStartMessage(Graphics2D g) {
        if (gameOver) {
            g.setFont(getFont().deriveFont(Font.BOLD, 18));
            g.setColor(getForeground());
            String s = "click to start a new game";
            int x = (getWidth() - g.getFontMetrics().stringWidth(s)) / 2;
            int y = getHeight() - margin;
            g.drawString(s, x, y);
        }
    }

    private void drawCenteredString(Graphics2D g, String s, int x, int y) {
        FontMetrics fm = g.getFontMetrics();
        int asc = fm.getAscent();
        int des = fm.getDescent();

        x = x + (tileSize - fm.stringWidth(s)) / 2;
        y = y + (asc + (tileSize - (asc + des)) / 2);

        g.drawString(s, x, y);
    }

    @Override
    public void paintComponent(Graphics gg) {
        super.paintComponent(gg);
        Graphics2D g = (Graphics2D) gg;
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        drawGrid(g);
        drawStartMessage(g);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setTitle("Fifteen Puzzle");
            f.setResizable(false);
            f.add(new FifteenPuzzle(), BorderLayout.CENTER);
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        });
    }
}