3
\$\begingroup\$

I've created a simple score game. The goal is improving my knowledge about OOP.

This is my Main method:

using System;

namespace Snake
{
    class Program
    {
        static void Main()
        {
            while (true)
            {
                Process.UI();
            }
        }
    }
}

It's very simple and just calls a method named UI.

This is my Snake method (I named it Snake because it's like a snake game):

using System;
using System.Collections.Generic;
using System.Threading;

namespace Snake
{
    class Snake
    {
        static int headOfSnake;
        public static int Score { get; set; }

        public static void GenericRandomCircle()
        {
            Random test = new Random();
            int index = test.Next(Process.ListOfDots.Count);
            Process.ListOfDots[index] = '■';
        }

        public static void PositionOfSnake()
        {
            Random test = new Random();
            headOfSnake = test.Next(Process.ListOfDots.Count);
            Process.ListOfDots[headOfSnake] = '▒';
        }

        public static void Move(string move, int Step)
        {
            switch (move)
            {
                case "w":
                    ChangeSnakePosition(Step, -20);
                    break;

                case "s":
                    ChangeSnakePosition(Step, 20);
                    break;

                case "a":
                    ChangeSnakePosition(Step, -1);
                    break;

                case "d":
                    ChangeSnakePosition(Step, 1);
                    break;

                default:
                    break;
            }
        }

        static void ChangeSnakePosition(int Step, int WhereToWhere)
        {
            try
            {
                int Counter = headOfSnake;
                for (int i = 0; i < Step; i++)
                {
                    Thread.Sleep(200);
                    if (Process.ListOfDots[Counter + WhereToWhere] == '■')
                    {
                        Score++;
                        GenericRandomCircle();
                    }
                    Process.ListOfDots[Counter] = '∙';
                    Counter = Counter + WhereToWhere;
                    Process.ListOfDots[Counter] = '▒';
                    RefreshGameBoard();
                }
                headOfSnake = Counter;
            }
            catch
            {
                Console.ForegroundColor = ConsoleColor.Red;
                RefreshGameBoard();
                Console.WriteLine("YOU LOSE!!! START GAME AGAIN");
                Environment.Exit(0);
            }
        }
        static void RefreshGameBoard()
        {
            Process.GameBoard();
        }
    }
}

And this is the last class called Process to process the game:

using System;
using System.Collections.Generic;

namespace Snake
{
    class Process
    {
        static List<char> _listOfDots = new List<char>();
        static bool isGenerate;
        static string userMove;
        static int userStep;

        public static List<char> ListOfDots
        {
            get { return _listOfDots; }
            set { _listOfDots = value; }
        }

        public static void GameBoard()
        {
            Console.Clear();
            Console.WriteLine("╔═════════════════════════════════════════╗");
            for (int i = 0; i < ListOfDots.Count; i++)
            {
                if (i % 20 == 0 && i == 0)
                {
                    Console.Write("║ {0} ", ListOfDots[i]);
                    continue;
                }
                else if (i % 20 == 0)
                {
                    Console.Write("║\n║ {0} ", ListOfDots[i]);
                    continue;
                }

                Console.Write("{0} ", ListOfDots[i]);

                if (i == ListOfDots.Count - 1)
                {
                    Console.Write("║");
                }
            }
            Console.WriteLine();
            Console.Write("╚═════════════════════════════════════════╝");
            Console.WriteLine();
        }
        public static void ShowGuide()
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Your Score: {0}", Snake.Score);
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("up: W \t down: S \t right: D \t left: A");
        }

        public static void UI()
        {
            if (!isGenerate)
            {
                FirstGenerate();
            }
            MoveChecks("Enter W S D A", "Put numbers");
            Snake.Move(userMove, userStep);
        }

        static void MoveChecks(string wasdMessage, string stepMessage)
        {
            while (true)
            {
                GameBoard();
                ShowGuide();
                userMove = GetString("Enter W S D A to move: ");
                if (userMove != "w" && userMove != "a" && userMove != "s" && userMove != "d")
                {
                    GetError(String.Format("▌ {0} ▐", wasdMessage));
                    Console.ReadLine();
                    continue;
                }

                try
                {
                    userStep = Convert.ToInt32(GetString("How Many Step: "));
                    break;
                }
                catch
                {
                    GetError(String.Format("▌ {0} ▐", stepMessage));
                    Console.ReadLine();
                    continue;
                }
            }
        }

        static void FirstGenerate()
        {
            while (true)
            {
                string userInput = GetString("Please Enter a number can be divided by 100 to play the game (less than 400) :");
                if (ListOfDots.Count > 0)
                {
                    GetError("You Can't Insert to the BoardGame.");
                    return;
                }
                try
                {
                    if (Convert.ToInt32(userInput) % 100 == 0 && Convert.ToInt32(userInput) <= 400)
                    {
                        for (int i = 0; i < Convert.ToInt32(userInput); i++)
                        {
                            ListOfDots.Add('∙');
                        }

                        Snake.GenericRandomCircle();
                        Snake.PositionOfSnake();
                        GameBoard();
                        Snake.Score = 0;
                        isGenerate = true;
                        break;
                    }
                    else
                    {
                        GetError("Please Enter a number divided by 100 and less than 400.");
                    }
                }
                catch
                {
                    Console.WriteLine("Please enter a number");
                }
            }
        }

        // STATIC METHODS - Once Write * EveryWhere Use.
        public static string GetString(string message)
        {
            Console.ForegroundColor = ConsoleColor.Cyan;
            Console.Write(message.ToLower());
            Console.ForegroundColor = ConsoleColor.White;
            return Console.ReadLine();
        }

        public static void GetError(string message)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(message);
            Console.ForegroundColor = ConsoleColor.White;
        }
    }
}

How do you think this program can be made more object-oriented and expanded? Please tell me the problems of this program so that I can improve my knowledge. Thanks.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ I was busy writing an answer and realized that I was drifting far from the topic. I decided to rather post my solution as a new question. No offence meant and I liked the way you formatted the output =) \$\endgroup\$
    – jdt
    Oct 31 '21 at 20:00
  • \$\begingroup\$ @upkajdt No problem my friend. I will learn a lot from your code. \$\endgroup\$ Nov 1 '21 at 8:43
  • \$\begingroup\$ Cool! When you create games it is always good to split your concerns into GameState, Rendering, HandleUserInput, UpdateState, and StateEvaluation. A good exercise would be to take my code and see if you can add internal walls or a computer-based snake opponent. Next step, console-based PacMan / Astroids =). Please let me know if you have any questions about my code. \$\endgroup\$
    – jdt
    Nov 1 '21 at 12:01
3
\$\begingroup\$

Few tips:

  1. Process name is conflicting with System.Diagnostics.Process the conflict may appear in the further development. Avoid using .NET class names. For example Game instead of Process
  2. Give methods names which state what the method do e.g. Game.Run() instead of Game.UI() or DrawGameBoard instead of GameBoard.
  3. Same for variable neames Random rnd instead of Random test.
  4. To introduce OOP make everything non-static, then create objects using new keyword.
  5. A lot of public methods which isn't used outside of the class, make it private.
  6. Infinite, never ending loop while(true). I suggest to find a way to exit the app.

I'll show some simple example.

Here's implementation via static.

public class Parent
{
    public static Run()
    {
        Child.Go();
    }

    public static DoCommonJob()
    {
    }
}

public class Child()
{
    public static Go()
    {
        Parent.DoCommonJob();
    }
}

class Program
{
    static void Main()
    {
        Parent.Run();
    }
}

That's how it looks now.

The following example avoids static starting using objects.

public class Parent
{
    private Child child;

    public Parent()
    {
        child = new Child(this);
    }

    public Run()
    {
        child.Go();
    }

    public DoCommonJob()
    {
    }
}

public class Child()
{
    private Parent parent;

    public Child(Parent parent)
    {
        this.parent = parent;
    }

    public Go()
    {
        parent.DoCommonJob();
    }
}

class Program
{
    static void Main()
    {
        Parent parent = new Parent();
        parent.Run();
    }
}

But here's obvious not good solution, cyclic dependency: Parent depends on Child, while Child depends on Parent. It commonly acceptable but can cause problems in future development.

This can be solved by introducing 3rd class where's common method located.

public class Common
{
    public DoCommonJob()
    {
    }
}

public class Parent
{
    private Child child;
    private Common common;

    public Parent(Common common)
    {
        this.common = new Common();
        this.child = new Child(common);
    }

    public Run()
    {
        child.Go();
    }
}

public class Child()
{
    private Common common;

    public Child(Common common)
    {
        this.common = common;
    }

    public Go()
    {
        common.DoCommonJob();
    }
}

class Program
{
    static void Main()
    {
        Parent parent = new Parent();
        parent.Run();
    }
}

Both Parent and Child depends on Common while there's no cyclic dependency. Now it looks fine.

Note: new keyword creates new instance of the class every time you call it.

For example, this one won't work as expected according to current logic

while(true)
{
    Game game = new Game();
    game.Run();
}

But this one will

Game game = new Game();
while(true)
{
    game.Run();
}

Feel the difference.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you very much for taking the time to analyze my code. It is very valuable to me. A question about Part 4: So when do we need to use static methods and members of the class? \$\endgroup\$ Nov 1 '21 at 8:44
  • 1
    \$\begingroup\$ @CarlJohnson for example Singleton pattern implementation SomeClass.Instance, named constructors pattern SomeClass.Create(), and a lot more useful patterns. \$\endgroup\$
    – aepot
    Nov 1 '21 at 10:32
  • 1
    \$\begingroup\$ @CarlJohnson, Static variables/methods belong to the class definition itself. A good example of this is DateTime.Now. Non-static variables/methods belong to the objects created from a class definition for instance var date = new DateTime(); date.AddDays(5); \$\endgroup\$
    – jdt
    Nov 1 '21 at 12:08
2
\$\begingroup\$

in the method ChangeSnakePosition(int Step, int WhereToWhere) the variable WhereToWhere should not be int, instead you can create enum called Direction

public enum Direction
{
    up,
    down,
    left,
    right,
}

then the method signature will be

ChangeSnakePosition(int Step, Direction direction)

this will help in better reading in calling

ChangeSnakePosition(Step, -20);

instead of a crapy number, it will become a readable value

ChangeSnakePosition(Step, Direction.up);

beside @aepot mentions that you don't have to use static everywhere, your methods should be return what it tries to calculate, for example in Process class the following method

void MoveChecks(string wasdMessage, string stepMessage)

should return a tuple with direction and steps as following

(Direction dir,int step) MoveChecks(string wasdMessage, string stepMessage)
// you should rename this method to be getDirectionFromUser instead of indescribable MoveChecks

Then you can safely delete unnecessary property int userStep and string userMove. The calling will be as following

var result = MoveChecks("Enter W S D A", "Put numbers");
Snake.Move(result.dir, result.step);  // to do: don't use statics 
\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much for your reply. It's very valuable to me. now I have a clear idea of ​​where I can use enum. \$\endgroup\$ Nov 1 '21 at 16:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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