2
\$\begingroup\$

I'm going through "C# Player's Guide" book and one of the optional tasks was to make simple TicTacToe program. Here is my implementation:

using System;
using System.Collections.Generic;

namespace Tic_Tac_Toe
{
    public class Program
    {
        private static Board currentBoard;
        private static Player currentPlayer;
        private static List<string> tempChain;
        static void Main(string[] args)
        {
            Console.WriteLine("Welcome to TicTacToe Autism Edition v0.0001");
            currentBoard = new Board();
            tempChain = new List<string>(3);
            NewGame();

        }

        private static void NewGame()
        {
            currentBoard.NewGame();
            currentPlayer = Shuffle();
            Console.WriteLine($"Board size is {currentBoard.Size}. Player {currentPlayer.Name} has the first turn:");
            drawCurrentBoard();
            processGame();
        }

        private static void processGame()
        {
            if (currentBoard.TurnCount >= 9)
            {
                endGameDraw();
            }
            Console.WriteLine($"{currentPlayer.Name}, enter the number from 1 to 9, each number indicates square on the board: ");
            int fieldNumber = processInput(Console.ReadLine());
            processMove(currentPlayer, fieldNumber);

        }

        private static void processMove(Player curPlayer, int move)
        {
            if (currentBoard.CurrentState[move - 1] == " ")
            {
                Console.WriteLine($"{currentPlayer.Name} takes square {move}");
                currentBoard.CurrentState[move - 1] = currentPlayer.Char;
                currentBoard.TurnCount++;
                currentPlayer.Turns++;
                currentPlayer.movesChain.Add(move);
                drawCurrentBoard();
                if (checkWin() == false)
                {
                    currentPlayer = nextPlayer();
                    processGame();
                }
                else
                {
                    endGameWin();
                }
            }
            else
            {
                Console.WriteLine($"Board square {move} is already occupied by \"{currentBoard.CurrentState[move - 1]}\"");
                processGame();
            }
        }

        private static int processInput(string input)
        {
            int number = -1;
            while (number == -1)
            {
                try
                {
                    number = Int32.Parse(input);
                    if (number >= 1 && number <= 9)
                    { return number; }
                    else
                    {
                        Console.WriteLine($"{input} is not a valid number in 1-9 range, try again: ");
                        number = -1;
                        input = Console.ReadLine();
                    }
                }
                catch (Exception)
                {
                    Console.WriteLine($"{input} is not a valid number in 1-9 range, try again: ");
                    number = -1;
                    input = Console.ReadLine();
                }
            }
            return number;
        }

        private static void processGameMode(string input)
        {
            while (input != "new" || input != "rematch")
            {
                if (input == "new")
                {
                    NewGame();
                    break;
                }
                else if (input == "rematch")
                {
                    Rematch();
                    break;
                }
                else
                {
                    Console.WriteLine("Unknown command, try again!");
                    input = Console.ReadLine();
                    continue;
                }
            }

        }

        private static void drawCurrentBoard()
        {
            var z = currentBoard.CurrentState;
            Console.Write($" {z[6]} | {z[7]} | {z[8]} \n");
            Console.Write($"---+---+---\n");
            Console.Write($" {z[3]} | {z[4]} | {z[5]} \n");
            Console.Write($"---+---+---\n");
            Console.Write($" {z[0]} | {z[1]} | {z[2]} \n");
        }

        private static Player nextPlayer()
        {
            if (currentPlayer == currentBoard.Player1) { return currentBoard.Player2; }
            else { return currentBoard.Player1; }
        }
        private static Player Shuffle()
        {
            Random randomID = new Random();
            int rpl = randomID.Next(1, 101);
            if (rpl %2 == 0) { return currentBoard.Player1;}
            else { return currentBoard.Player2;}
        }

        private static bool checkWin()
        {
            if (currentPlayer.Turns < 3 || currentPlayer.movesChain.Count < 3)
            { return false; }
            else
            {
                tempChain.Clear();
                for (int i = 1; i <= 3; i++)
                {
                    tempChain.Add(currentPlayer.movesChain[currentPlayer.movesChain.Count - i].ToString());
                }
                tempChain.Sort();
                var chain = string.Join<string>("", tempChain);
                if (chain == "147" || chain == "258" || chain == "369" || chain == "789" || chain == "456" || chain == "123" || chain == "357" || chain == "159")
                { return true; }
                else { return false; }
            }

        }

        private static void endGameWin()
        {
            currentBoard.TotalGames++;
            currentPlayer.Wins++;
            Console.WriteLine($"Congratulations, player {currentPlayer.Name} won!");
            Console.WriteLine($"Game stats: turns this round - {currentBoard.TurnCount} | total rounds - {currentBoard.TotalGames}");
            Console.WriteLine($"Player stats: {currentBoard.Player1.Name}\'s wins: {currentBoard.Player1.Wins} | {currentBoard.Player2.Name}\'s wins: {currentBoard.Player2.Wins}");
            Console.WriteLine("Type \"rematch\" for a rematch or \"new\" to start new game with new players");
            processGameMode(Console.ReadLine());
        }

        private static void endGameDraw()
        {
            currentBoard.TotalGames++;
            Console.WriteLine($"Board is full, none of the player have won.");
            Console.WriteLine("Type \"rematch\" for a rematch or \"new\" to start new game with new players");
            processGameMode(Console.ReadLine());
        }

        private static void Rematch()
        {
            Console.WriteLine("Starting new round");
            currentBoard.Rematch();
            drawCurrentBoard();
            processGame();
        }
    }

    public class Board
    {
        private Player player1;
        private Player player2;
        public int TurnCount { get; set; }
        public int TotalGames { get; set; }
        public int Size {get; private set; } = 3;
        public string[] CurrentState { get; set; }
        public Player Player1
        {
            get { return player1; }
            private set { player1 = value; }
        }
        public Player Player2
        {
            get { return player2; }
            private set { player2 = value; }
        }
        public Board()
        {
            this.TurnCount = 0;
            this.TotalGames = 0;
            this.CurrentState = new string[Size * Size];
            this.player1 = new Player();
            this.player2 = new Player();
            this.player1.Char = "x";
            this.player2.Char = "o";
        }

        private void fillBoard()
        {
            for (int i = 0; i < 9; i++) { CurrentState[i] = " "; }
        }
        public void Rematch()
        {
            this.TurnCount = 0;
            this.player1.Turns = 0;
            this.player2.Turns = 0;
            this.player2.movesChain.Clear();
            this.player2.movesChain.Clear();
            fillBoard();
        }

        public void NewGame()
        {
            this.TurnCount = 0;
            this.TotalGames = 0;
            fillBoard();
            this.player1.ResetPlayerData();
            this.player1.ResetPlayerData();
            Console.WriteLine("Enter Player1 name (1 char. or more): ");
            player1.setName(Console.ReadLine());
            Console.WriteLine($"Player 1 name is {player1.Name}");
            Console.WriteLine("Enter Player2 name (1 char. or more): ");
            player2.setName(Console.ReadLine());
            Console.WriteLine($"Player 2 name is {player2.Name}");
            Console.WriteLine($"New board prepared. Total games in session so far: {this.TotalGames}");

        }
    }

    public class Player
    {
        public string Name { get; set; }
        public int Turns { get; set; }
        public int Wins { get; set; }
        public string Char { get; set; }
        public List<int> movesChain { get; set; }

        public Player()
        {
            this.Name = "";
            this.Char = "";
            this.Wins = 0;
            this.Turns = 0;
            this.movesChain = new List<int>();
        }

        public void setName(string text)
        {
            while (text.Length < 1)
            {
                Console.WriteLine("Invalid Player name, try again: ");
                text = Console.ReadLine();
            }
            this.Name = text;
        }

        public void ResetPlayerData()
        {
            this.Name = "";
            this.Turns = 0;
            this.Wins = 0;
            this.movesChain.Clear();
        }
    }

}

Here is book's author implementation (cannot paste it here): http://starboundsoftware.com/books/c-sharp/try-it-out/tic-tac-toe

Question: How bad the stack memory gets with each new round in my version? Do I understand it right, that because most of my methods have no return value I basically never quit/release their stack layer and I literally stack each of them on top of each other in a closed loop?

For example:
- my game starts with Main() function
- goes into NewGame()
- from NewGame() goes into processGame()
- from processGame() goes into processMove
- from processMove() back into processGame

As I understand, each of these cycles are within each other rather than being juggled like if I was moving from a one room into to another room with items, thus stack grows each time, right?

Is implementation through while loop in a single method like in author's solution the best approach here? I couldnt figure out better way to do rematch/newgame.

\$\endgroup\$
5
  • \$\begingroup\$ You need to put the code in the question. Links are ok by only what's here gets peer reviewed. \$\endgroup\$
    – t3chb0t
    Commented Nov 24, 2017 at 17:03
  • \$\begingroup\$ Unfortunately, I cant put second example into the post body, simpy because it is a whole VS project and I'd need to ask permission to post it (there is a -99% chance the author will ever respond.) \$\endgroup\$
    – Digika
    Commented Nov 24, 2017 at 17:14
  • \$\begingroup\$ This is ok because someone else's code would be off-topic so you don't have to ask the author. You just need to add your own code which you did so now your question is fine. \$\endgroup\$
    – t3chb0t
    Commented Nov 24, 2017 at 17:18
  • \$\begingroup\$ Maybe you could explain in some more detail what you mean by Stack? Most people when they hear Stack they understand THE Stack class (including me) but you use somethig else that you call stack. Could you clarify this? \$\endgroup\$
    – t3chb0t
    Commented Nov 24, 2017 at 17:23
  • \$\begingroup\$ i mean stack memory \$\endgroup\$
    – Digika
    Commented Nov 24, 2017 at 17:35

1 Answer 1

1
\$\begingroup\$

You are indeed keeping everything in memroy because you logic is "recursive". Since you almost never return from any function before the game is over, everything is kept in memory. You should concider rethinking your program execution flow. Every step should happen once per turn.

A game is multiple turns. you should have severals loop to show that. At the end do I want to rematch ? You processGameMode loop shown that. That meens it should be on the main or at the very top of your logic.

NB : I will not use proper code here, just logic. If you learn you need logic and getting you hands dirty

Main()
{
    ... // init stuff     
    string[,] theGrid= new string[3,3];
    bool keepPlaying = true;
    While(keepPlaying)
    {
        keepPlaying = Play();
    }
    ... // end stuff
}

The play function could be like this :

private static bool Play()
{
    ... // init stuff - e.g The grid
    bool gameOver = false;        
    Draw();
    while (!gameOver)
    {
        PlayerOne();
        gameOver = checkWinner();
        PlayerTwo();            
        gameOver = checkWinner();
    }
    return Continue();
}

PlayerOne and PlayerTwo just manage input for earch player.

private static void PlayOne()
{
    string input = Console.ReadLine();
    int index = 0;
    while(!int.TryParse(input, out index))
    {
        ... // Print the error message and ask for input again.
    }
    ... // Update the grid
    Draw();
}

I don't put everything but you get the idea. What happen here is that at every loop on the Play function, all your variables get freed (It's the Garbadge collector task now)

And on every loop on the main, your state gets reset, and memory is clean.

\$\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.