5
\$\begingroup\$

I am completely new to object oriented programing (OOP). I have also never worked with C# before. The exercise from my book had some requirements that should be followed to complete the exercise.

Use of at least two classes (not counting the main class), one of which should be a Tile class, representing the tiles

No public fields with the exception of the field representing the value of Tiles.

The value field of Tiles must be readonly

This are the problems I am currently facing.

  • I dont know what the tile class should do or how it should be implemented.
  • I am almost 100% sure I am not following the guidelines (tried my best)
  • I think my shuffle method and detection if the puzzle is solvable is wrong

Program.cs

  class Program
    {
        static void Main(string[] args)
        {
            Board board = new Board();
            board.StartGame();

        }
    }

Tile.cs

class Tile
{
    readonly public int Value;
}

Board.cs

class Board
    {
        private int[,] Arr2d { get; set; }
        private int Size { get; set; }

        public void StartGame()
        {
            Size = 3;
            Arr2d = new int[Size, Size];


            FillTiles();
            ShuffleTiles();


            if (IsSolvable() == true)
            {
                PrintArr();
            }
            else
            {
                StartGame();
            }

           SwapTiles();
            Console.WriteLine("Finished the game");
        }

        private void FillTiles()
        {
            int k = 0;

            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    Arr2d[i, j] = k++;
                }
            }
        }

        private void ShuffleTiles()
        {
            Random random = new Random();
            int n = Arr2d.GetLength(1);
         

           for (int i = Arr2d.Length - 1; i > 0; i--)
            {
                int i0 = i / n;
                int i1 = i % n;
                int j = random.Next(i + 1);
                int j0 = j / n;
                int j1 = j % n;
                int temp = Arr2d[i0, i1];
                Arr2d[i0, i1] = Arr2d[j0, j1];
                Arr2d[j0, j1] = temp;
            }
        }

        private int GetInvCount()
        {
            //List<int> flattedList = new List<int> { 1, 20, 6, 4, 5 };
            var flattedList = Arr2d.OfType<int>().ToList();
            int inv_count = 0;

            for (int i = 0; i < flattedList.Count - 1; i++) {
                for (int j = i + 1; j < flattedList.Count; j++)
                {
                    if (flattedList[i] > flattedList[j])
                    {
                        inv_count++;
                    }
                }   
            }
            return inv_count+1;
        }

        private int GetRowNumberFromBelow(int emptyTilePosition)
        {
            var row = emptyTilePosition / Size;

            return Size - row;
        }

        private bool IsSolvable()
        {

            int numberOfInversions = GetInvCount();

            if (Size % 2 != 0)
            {
                return (numberOfInversions % 2 == 0);
            }
                int pos = GetRowNumberFromBelow(IndexZeroPos());
                if (pos % 2 != 0)
                {
                    return (numberOfInversions % 2 == 0);
                }
                else
                {
                    return (numberOfInversions % 2 != 0);
                }

        }

        private void PrintArr()
        {
            for (int i = 0; i < Size; i++)
            {
                for (int j = 0; j < Size; j++)
                {
                    Console.Write(Arr2d[i, j] + " ");
                }

                Console.WriteLine();
            }

        }

        private bool ArrSorted()
        {
            var flattedList = Arr2d.OfType<int>().ToList();
            var result = flattedList.OrderByDescending(x => x !=0).ThenBy(x => x).SequenceEqual(flattedList);
            return result;
        }

        private int IndexZeroPos()
        {
            var flattedList = Arr2d.OfType<int>().ToList();
            int i = flattedList.IndexOf(0);
            return i;
        }

        private void SwapTiles()
        {
            int y = IndexZeroPos() % Size;
            int x = IndexZeroPos() / Size;
            var isSorted = ArrSorted();

            while (isSorted != true)
            {
                ConsoleKeyInfo info = Console.ReadKey();
                Console.WriteLine();

                if (info.Key == ConsoleKey.RightArrow)
                {
                    if (y >= (Size - 1))
                    {
                        continue;
                    }
                    int temp = Arr2d[x, y];
                    Arr2d[x, y] = Arr2d[x, (y + 1)];
                    Arr2d[x, (y + 1)] = temp;
                    if (isSorted != ArrSorted())
                    {
                        break;
                    }
                    else
                    {
                        y = IndexZeroPos() % Size;
                        x = IndexZeroPos() / Size;
                        PrintArr();
                    }
                }

                if (info.Key == ConsoleKey.LeftArrow)
                {
                    if (y <= 0)
                    {
                        continue;
                    }
                    int temp = Arr2d[x, y];
                    Arr2d[x, y] = Arr2d[x, (y - 1)];
                    Arr2d[x, (y - 1)] = temp;

                    if (isSorted != ArrSorted())
                    {
                        break;
                    }
                    else
                    {
                        y = IndexZeroPos() % Size;
                        x = IndexZeroPos() / Size;
                        PrintArr();
                    }
                }

                if (info.Key == ConsoleKey.UpArrow)
                {
                    if (x <= 0)
                    {
                        continue;
                    }

                    int temp = Arr2d[x, y];
                    Arr2d[x, y] = Arr2d[(x - 1), y];
                    Arr2d[(x - 1), y] = temp;
               
                    if (isSorted != ArrSorted())
                    {
                        break;
                    }
                    else
                    {
                        y = IndexZeroPos() % Size;
                        x = IndexZeroPos() / Size;
                        PrintArr();
                    }
                }

                if (info.Key == ConsoleKey.DownArrow)
                {
                    if (x >= (Size - 1))
                    {
                        continue;
                    }

                    int temp = Arr2d[x, y];
                    Arr2d[x, y] = Arr2d[(x + 1), y];
                    Arr2d[(x + 1), y] = temp;
                   

                    if (isSorted != ArrSorted())
                    {
                        break;
                    }
                    else
                    {
                        y = IndexZeroPos() % Size;
                        x = IndexZeroPos() / Size;
                        PrintArr();
                    }
                }

            }
        }
    }

As this is my first small project I feel like any feedback will do me good and I would really appreciate any feedback of any sort to become better.

The code above works, as in the user can move and it will complete when the puzzle is in this state 1 2 3 4 5 6 7 8 0. I have tested some puzzles that where generated with Puzzle solver From the testing it seems a lot of the puzzle states are not solvable which shouldn't be the case. My method IsSolvable seem flawed.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ Welcome to Code Review! Can you confirm that the code is complete and that it functions correctly? If so, I recommend that you edit to add a summary of the testing (ideally as reproducible unit-test code). If it's not working, it isn't ready for review (see help center) and the question may be deleted. \$\endgroup\$ Commented Dec 9, 2022 at 16:16
  • \$\begingroup\$ @TobySpeight is this better or am I still missing something ? \$\endgroup\$
    – Darke
    Commented Dec 9, 2022 at 16:25
  • \$\begingroup\$ I was just picking up on your line, "I think my shuffle method and detection if the puzzle is solvable is wrong", and wasn't sure if that meant it has known bugs. If it works to your satisfaction, then it's fine here. :) \$\endgroup\$ Commented Dec 9, 2022 at 16:29
  • 1
    \$\begingroup\$ About half of sliding-block puzzles are unsolvable, as there's a parity to the arrangement. If you can rearrange by an even number of swaps, it's solvable, but if the number of swaps is odd, then not. (I haven't actually read the code; C# isn't one of my languages). \$\endgroup\$ Commented Dec 9, 2022 at 16:31
  • 2
    \$\begingroup\$ Please do not edit the question, especially the code, after an answer has been posted. Changing the question may cause answer invalidation. Everyone needs to be able to see what the reviewer was referring to. What to do after the question has been answered. \$\endgroup\$
    – pacmaninbw
    Commented Dec 10, 2022 at 13:32

3 Answers 3

6
\$\begingroup\$

I think you're real close to the ask of the problem. I have two main bits for you to change up:

  • Your Tile class has no way of setting the tile value. Change so that it does. I think immutability is useful here, so note the fact that the property is get-only and the class is sealed (to be honest, I'd implement it as a struct rather than a class, but that would not honor the letter of your requirements):
public sealed class Tile : IComparable<Tile>
{
    public Tile(int value) => this.Value = value;

    public int Value { get; }

    public int CompareTo(Tile? other) => other?.Value.CompareTo(this.Value) ?? -1;

    public override string ToString() => this.Value.ToString();
}
  • Change the type of Arr2d from int[,] to Tile[,] (you'll also have to change various int declarations in the rest of the class to Tile). Also change the name of the member variable from Arr2d to Tiles and change the visibility as such: public Tile[,] Tiles { get; private set; }

Now there are a few other issues with the code to be sure, and I think it may be broke enough to violate the rule of only working code be posted. But I think you can find those through debugging.

\$\endgroup\$
1
  • \$\begingroup\$ Would you be kind and look at how I implemented your suggestions in my self answer? What I did works, but dont know if that what you wanted me to do especially FillTilesmethod \$\endgroup\$
    – Darke
    Commented Dec 11, 2022 at 12:58
4
\$\begingroup\$

I have a few scattered observations.

Your Tile.Value is a field rather than a property. If using a public access modifier, you should favor using a property rather than a field. Example:

public int Value { get; }

I tend to think of a tile on a physical board as being more than a integer value. I would think it would have a location (X, Y) or maybe (Row, Column), along with a Value, and possible other properties depending upon the game. Think of Minesweeper where a tile can have a bomb (one property HasBomb) or has not been revealed (property IsRevealed).

I think your Board class does too much game mechanics. I would think the Board class should have the responsibility of defining the board and tiles on the board, but not do anything regarding game play. Game mechanics would be the responsibilty of a different class. Consider the physical board is used for Checkers as Chess, but both games have different mechanics.

\$\endgroup\$
1
\$\begingroup\$

Update based on Jesse C. Slicer feedback:

  • Arr2d = new int[Size, Size]; changed to Tiles = new Tile [Size,Size];
  • int temp = Arr2d[i0, i1]; changed to Tile temp = Tiles[i0, i1];
  • flattedList = Arr2d.OfType<int>().ToList(); modified to flattedList = Tiles.OfType<Tile>().ToList();

Board.cs

class Board
{
    public Tile[,] Tiles { get; private set; }
    private int Size { get; set; }

    public void StartGame()
    {
        Size = 3;
        Tiles = new Tile [Size,Size];

        FillTiles();
        ShuffleTiles();

        if (IsSolvable() == true)
        {
            PrintArr();
        }
        else
        {
            StartGame();
        }

       SwapTiles();
        Console.WriteLine("Finished the game");
    }

    private void FillTiles()
    {
        Tile k = new Tile(0);
        int temp = 0;

        for (int i = 0; i < Size; i++)
        {
            for (int j = 0; j < Size; j++)
            {

                Tiles[i, j] = k;
                temp++;
                k = new Tile(temp);

            }
        }
    }

    private void ShuffleTiles()
    {
        Random random = new Random();
        int n = Tiles.GetLength(1);
     

       for (int i = Tiles.Length - 1; i > 0; i--)
        {
            int i0 = i / n;
            int i1 = i % n;
            int j = random.Next(i + 1);
            int j0 = j / n;
            int j1 = j % n;
            Tile temp = Tiles[i0, i1];
            Tiles[i0, i1] = Tiles[j0, j1];
            Tiles[j0, j1] = temp;
        }
    }

    private int GetInvCount()
    {
        //List<int> flattedList = new List<int> { 1, 20, 6, 4, 5 };
        var flattedList = Tiles.OfType<Tile>().ToList();
        int inv_count = 0;

        for (int i = 0; i < flattedList.Count - 1; i++) {
            for (int j = i + 1; j < flattedList.Count; j++)
            {
                if (flattedList[i].Value > flattedList[j].Value)
                {
                    inv_count++;
                }
            }   
        }
        return inv_count+1;
    }

    private int GetRowNumberFromBelow(int emptyTilePosition)
    {
        var row = emptyTilePosition / Size;

        return Size - row;
    }

    private bool IsSolvable()
    {

        int numberOfInversions = GetInvCount();

        if (Size % 2 != 0)
        {
            return (numberOfInversions % 2 == 0);
        }
            int pos = GetRowNumberFromBelow(IndexZeroPos());
            if (pos % 2 != 0)
            {
                return (numberOfInversions % 2 == 0);
            }
            else
            {
                return (numberOfInversions % 2 != 0);
            }

    }

    private void PrintArr()
    {
        for (int i = 0; i < Size; i++)
        {
            for (int j = 0; j < Size; j++)
            {
                Console.Write(Tiles[i, j] + " ");
            }

            Console.WriteLine();
        }

    }

    private bool ArrSorted()
    {
        var flattedList = Tiles.OfType<Tile>().ToList();
        var result = flattedList.OrderByDescending(x => x.Value !=0).ThenBy(x => x).SequenceEqual(flattedList);
        return result;
    }

    private int IndexZeroPos()
    {
        var flattedList = Tiles.OfType<Tile>().ToList();
        int index = flattedList.FindIndex(x => x.Value ==0);
        return index;
    }

    private void SwapTiles()
    {
        int y = IndexZeroPos() % Size;
        int x = IndexZeroPos() / Size;
        var isSorted = ArrSorted();

        while (isSorted != true)
        {
            ConsoleKeyInfo info = Console.ReadKey();
            Console.WriteLine();

            if (info.Key == ConsoleKey.RightArrow)
            {
                if (y >= (Size - 1))
                {
                    continue;
                }
                Tile temp = Tiles[x, y];
                Tiles[x, y] = Tiles[x, (y + 1)];
                Tiles[x, (y + 1)] = temp;
                if (isSorted != ArrSorted())
                {
                    break;
                }
                else
                {
                    y = IndexZeroPos() % Size;
                    x = IndexZeroPos() / Size;
                    PrintArr();
                }
            }

            if (info.Key == ConsoleKey.LeftArrow)
            {
                if (y <= 0)
                {
                    continue;
                }
                Tile temp = Tiles[x, y];
                Tiles[x, y] = Tiles[x, (y - 1)];
                Tiles[x, (y - 1)] = temp;

                if (isSorted != ArrSorted())
                {
                    break;
                }
                else
                {
                    y = IndexZeroPos() % Size;
                    x = IndexZeroPos() / Size;
                    PrintArr();
                }
            }

            if (info.Key == ConsoleKey.UpArrow)
            {
                if (x <= 0)
                {
                    continue;
                }

                Tile temp = Tiles[x, y];
                Tiles[x, y] = Tiles[(x - 1), y];
                Tiles[(x - 1), y] = temp;
           
                if (isSorted != ArrSorted())
                {
                    break;
                }
                else
                {
                    y = IndexZeroPos() % Size;
                    x = IndexZeroPos() / Size;
                    PrintArr();
                }
            }

            if (info.Key == ConsoleKey.DownArrow)
            {
                if (x >= (Size - 1))
                {
                    continue;
                }

                Tile temp = Tiles[x, y];
                Tiles[x, y] = Tiles[(x + 1), y];
                Tiles[(x + 1), y] = temp;
               

                if (isSorted != ArrSorted())
                {
                    break;
                }
                else
                {
                    y = IndexZeroPos() % Size;
                    x = IndexZeroPos() / Size;
                    PrintArr();
                }
            }

        }
    }
}

Still not happy about how I modify FillTiles method it looks wrong to call new Tile for every iteration, but that what I came up with

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.