9
\$\begingroup\$

I am a beginner coder and was wondering how to improve my c# console code. It makes a tic-tac-toe game.

    using System;

namespace TicTacToe
{
    class Program
    {
        static string[] options = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; //stores the variables to change
        static bool Playing = true; //stops the game once someone wins
        static int turn = 0;

        static void Main(string[] args)
        {
            Intro();
            Board();

            while (Playing) //is to stop the game once someone wins
            {
                if (turn%2 == 0)
                {
                    Console.WriteLine("Player 1's turn");
                }
                else 
                {
                    Console.WriteLine("Player 2's turn");
                }
                
                int playerInput1;
                Console.WriteLine("type in your responce player");
                bool torf = int.TryParse(Console.ReadLine(), out playerInput1);
                playerInput1--;
                // makes sure the input is put in a valid value

                if (torf && playerInput1 < 9 && playerInput1 > -1) //makes sure again the input is valid and continues
                {
                    if (options[playerInput1] == "x" || options[playerInput1] == "o")
                    {
                        Console.WriteLine("Stop stealing othe people's place");
                    }
                    else
                    {
                        if (turn%2 == 0)
                        {
                            options[playerInput1] = "x";
                            turn++;

                            Board();
                            WinCondition();
                            Tie();
                        }
                        else
                        {
                            options[playerInput1] = "o";
                            turn++;

                            Board();
                            WinCondition();
                            Tie();
                        }
                        /*int playerInput2;
                        Console.WriteLine("o");
                        bool torf2 = int.TryParse(Console.ReadLine(), out playerInput2);
                        playerInput2--;

                        if (torf2 && playerInput2 < 9 && playerInput2 > -1)
                        {
                            if (options[playerInput2] == "x" || options[playerInput2] == "o")
                            {
                                Console.WriteLine("Stop stealing other people's space");
                            }
                            else
                            {
                                options[playerInput2] = "o";

                                Board();
                                WinCondition();

                                torf2 = false;
                                torf = false;
                            }
                        }*/
                    }
                }
                else
                {
                    Console.WriteLine("Please input a valid expression");
                }
            }
        }
        public static void Board() // makes the board
        {
            Console.Clear();

            Console.WriteLine("     |     |      ");

            Console.WriteLine($"  {options[0]}  |  {options[1]}  |  {options[2]}");

            Console.WriteLine("_____|_____|_____ ");

            Console.WriteLine("     |     |      ");

            Console.WriteLine($"  {options[3]}  |  {options[4]}  |  {options[5]}");

            Console.WriteLine("_____|_____|_____ ");

            Console.WriteLine("     |     |      ");

            Console.WriteLine($"  {options[6]}  |  {options[7]}  |  {options[8]}");

            Console.WriteLine("     |     |      ");
        }
        
        public static void WinCondition()
        {
            if (options[0] == options[1] && options[1] == options[2])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            else if (options[3] == options[4] && options[4] == options[5])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            else if (options[6] == options[7] && options[7] == options[8])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            // checks for horizontal wins
            else if (options[0] == options[3] && options[3] == options[6])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            else if (options[1] == options[4] && options[4] == options[7])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            else if (options[2] == options[5] && options[5] == options[8])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            // checks for vertical wins
            else if (options[0] == options[4] && options[4] == options[8])
            {
                Playing = false;
                if(turn % 2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            else if (options[2] == options[4] && options[4] == options[6])
            {
                Playing = false;
                if (turn%2 == 0)
                {
                    Console.WriteLine("Congrats on winning player 1, better luck next time player 2");
                }
                else
                {
                    Console.WriteLine("Congrats on winning player 2, better luck next time player 1");
                }
            }
            // checks for diagonal wins
        }

        public static void Intro()
        {
            Console.WriteLine("Welcome to\n");
            Console.WriteLine(@"____________             ________                   ________           ");
            Console.WriteLine(@"___  __/__(_)______      ___  __/_____ _______      ___  __/__________ ");
            Console.WriteLine(@"__  /  __  /_  ___/________  /  _  __ `/  ___/________  /  _  __ \  _ \");
            Console.WriteLine(@"_  /   _  / / /__ _/_____/  /   / /_/ // /__ _/_____/  /   / /_/ /  __/");
            Console.WriteLine(@"/_/    /_/  \___/        /_/    \__,_/ \___/        /_/    \____/\___/ ");
            Console.WriteLine("\n1.The game is played on a grid that's 3 squares by 3 squares.\n\n2.Player 1 is \"X\" and Player 2 is \"O\". Players take turns putting their marks in empty squares.\n\n3.The first player to get 3 of her marks in a row(horizontally, vertically or diagonally) is the winner.\n\n4.When all 9 squares are full, the game is over. If no player has 3 marks in a row, the game ends in a tie.\n\n5.You can put x or o in by typing the number you want to put it at");
            Console.ReadKey(false);
            Console.Clear();
        }

        public static void Tie()
        {
            if (options[0] != "1" && options[1]!= "2" && options[2] != "3" && options[3] != "4" && options[4] != "5" && options[5] != "6" && options[6] != "7" && options[7] != "8" && options[8] != "9")
            {
                Console.WriteLine("The game is a tie");
                Playing = false;
            }
        }
    }
}
\$\endgroup\$
14
\$\begingroup\$

Choosing good identifiers

  • When I see a name like options I think of something a player can choose from like "single player" (i.e., play against the computer) or "multiplayer". A better name would be board because that's what it is.

  • torf? Does it mean "true or false"? Every Boolean is true or false. This does not reflect the meaning it has in this context. Better: isValidInt. But I would inline this variable (see later).

  • Board(). A board is a thing, but here it stands for an action. Better PrintBoard(). Same for PrintIntro().

  • The field Playing starts with an upper case and is therefore said to be PascalCase. This is reserved for type names, method names and property names. Use camelCase for fields, parameters and variables. See C# Coding Standards and Naming Conventions for a full list.

Logic and structure can be simplified and clarified.

  • At many places you calculate turn % 2 == 0. Instead, I suggest directly calculating the player number. We would declare static int player = 0; and a method getting the next player as well as a method switching between players

    private static int NextPlayer()
    {
        return 1 - player;
    }
    
    private static void SwitchPlayer()
    {
        player = NextPlayer();
    }
    
  • This leads to another simplification. The first if-else statement can be replaced by (I am using string interpolation here)

    Console.WriteLine($"Player {player + 1}'s turn"); // Display player as 1-based number.
    
  • I would inline the variable you called torf and declare playerInput in a out variable declaration like this.

    if (Int32.TryParse(Console.ReadLine(), out int playerInput) && 
        playerInput is >= 1 and <= 9)
    {
        playerInput--; // Make it a 0-based index.
        ...
    }
    

    I used pattern matching to test if the value is in a valid range. But you can replace it by a classic Boolean expression if you prefer or if you are using a pre C# 9.0 version.

  • You differentiate two cases doing the same with a small exception (shown with the new identifiers):

    if (player == 0) {
        board[playerInput] = "x";
        SwitchPlayer();
    
        PrintBoard();
        WinCondition();
        Tie();
    } else {
        board[playerInput] = "o";
        SwitchPlayer();
    
        PrintBoard();
        WinCondition();
        Tie();
    }
    

    This can easily be simplified by declaring a new field playerMark as array. Since we're at it, we can do the same for player names. This saves us the base-0 to base-1 conversion of player numbers for display and could easily be extended to store real names.

    static readonly char[] playerMark = { 'x', 'o' };
    static readonly string[] playerName = { "1", "2" };
    

    It becomes

    board[playerInput] = playerMark[player];
    PrintBoard();
    WinCondition();
    Tie();
    SwitchPlayer(); // Doing this after printing winner!
    

    Even without this new static field, you could move all but the first line out of the if-else statement, since they are exactly the same in both cases:

    if (player == 0) {
        board[playerInput] = 'x';
    } else {
        board[playerInput] = 'o';
    }
    PrintBoard();
    WinCondition();
    Tie();
    SwitchPlayer();
    
  • The WinCondition method tests conditions and prints to the console. And it does so by repeating the same print statements over and over. Better separate the two concerns.

    private static bool IsWinCondition()
    {
        return
            board[0] == board[1] && board[1] == board[2] ||
            board[3] == board[4] && board[4] == board[5] ||
            board[6] == board[7] && board[7] == board[8] ||
            board[0] == board[3] && board[3] == board[6] ||
            board[1] == board[4] && board[4] == board[7] ||
            board[2] == board[5] && board[5] == board[8] ||
            board[0] == board[4] && board[4] == board[8] ||
            board[2] == board[4] && board[4] == board[6];
        }
    }
    

    and call it with (yet another simplification here)

    if (IsWinCondition()) {
        playing = false;
        Console.WriteLine(
          $"Congrats player {playerName[player]}, better luck next time player {playerName[NextPlayer()]}");
    }
    
  • Same issue as above with Tie. Also, it does not test whether we have a tie but only if the board is full. We only have a tie if we do not have a win situation at the same time. Therefore, I changed the method to (requires a using System.Linq; before the namespace)

    private static bool IsBoardFull()
    {
        return board.All(square => square > '9'); // Because 'x' and 'o' are greater
    }
    

    To make this work I changed the type of board and playerMark to char[]. char is considered to be a numeric type in C# and can be compared like numbers. (You must change the corresponding double quotes to single quotes.)

    See: Enumerable.All extension method.

    The conditions then become

    if (IsWinCondition()) {
        playing = false;
        Console.WriteLine(
            $"Congrats player {playerName[player]}, better luck next time player {playerName[NextPlayer()]}");
    } else if (IsBoardFull()) {
        playing = false;
        Console.WriteLine("The game is a tie");
    }
    
  • Now playing can be made a local variable since it is not used or set in other methods

    bool playing = true; //stops the game once someone wins
    while (playing) //is to stop the game once someone wins
    {
        ...
    }
    

The full solution (without the enclosing namespace and class):

static char[] board = { '1', '2', '3', '4', '5', '6', '7', '8', '9' }; //stores the variables to change
static readonly char[] playerMark = { 'x', 'o' };
static readonly string[] playerName = { "1", "2" };
static int player = 0;

public static void Main()
{
    PrintIntro();
    PrintBoard();
    bool playing = true; // Stops the game once someone wins
    while (playing)
    {
        Console.WriteLine($"Player {playerName[player]}'s turn");

        Console.WriteLine("Please, type in your response: ");
        if (int.TryParse(Console.ReadLine(), out int playerInput) && playerInput is >= 1 and <= 9) {
            playerInput--; // Make it a 0-based index.
            if (board[playerInput] is 'x' or 'o') {
                Console.WriteLine("Stop stealing other people's place");
            } else {
                board[playerInput] = playerMark[player];
                PrintBoard();
                if (IsWinCondition()) {
                    playing = false;
                    Console.WriteLine(
                        $"Congrats player {playerName[player]}, better luck next time player {playerName[NextPlayer()]}");
                } else if (IsBoardFull()) {
                    playing = false;
                    Console.WriteLine("The game is a tie");
                }
                SwitchPlayer();
            }
        } else {
            Console.WriteLine("Please input a valid expression");
        }
    }
}

private static int NextPlayer()
{
    return 1 - player;
}

private static void SwitchPlayer()
{
    player = NextPlayer();
}

private static bool IsBoardFull()
{
    return board.All(square => square > '9');
}

private static bool IsWinCondition()
{
    return
        board[0] == board[1] && board[1] == board[2] ||
        board[3] == board[4] && board[4] == board[5] ||
        board[6] == board[7] && board[7] == board[8] ||
        board[0] == board[3] && board[3] == board[6] ||
        board[1] == board[4] && board[4] == board[7] ||
        board[2] == board[5] && board[5] == board[8] ||
        board[0] == board[4] && board[4] == board[8] ||
        board[2] == board[4] && board[4] == board[6];
}

private static void PrintBoard() // makes the board
{
    Console.Clear();

    Console.WriteLine("     |     |      ");
    Console.WriteLine($"  {board[0]}  |  {board[1]}  |  {board[2]}");
    Console.WriteLine("_____|_____|_____ ");
    Console.WriteLine("     |     |      ");
    Console.WriteLine($"  {board[3]}  |  {board[4]}  |  {board[5]}");
    Console.WriteLine("_____|_____|_____ ");
    Console.WriteLine("     |     |      ");
    Console.WriteLine($"  {board[6]}  |  {board[7]}  |  {board[8]}");
    Console.WriteLine("     |     |      ");
}

private static void PrintIntro()
{
    // ...
}

Additional thought

You are storing the game board in a one-dimensional array. There exist variations of Tic-tac-toe having more rows and columns. Using a 2-d array would make the game more extensible, because this would allow testing win conditions and printing the board by simply iterating rows and columns. Now we use a manually composed win condition. This is okay as the board is very small. But doing this on lager boards would be both tedious and error prone.

\$\endgroup\$
5
  • \$\begingroup\$ My solution calls SwitchPlayer at exactly one place and produces alternately the player numbers 0 and 1 as expected. (player + 1) % 2 (or player = 1 - player) is necessary: when player is 0, it produces 1 and when player is 1 it produces 0. So, I don't know what you mean by "not usable" and "tweaked everywhere". Also, there is no "off by one" dilemma in the logic. Only for display one is added. But you are right that for display we could use a data based approach; however, I would simply use an array: string playerName = { "one", "two" }. Get name with playerName[player]. \$\endgroup\$ – Olivier Jacot-Descombes Feb 27 at 13:57
  • \$\begingroup\$ We would print the player numbers with Console.WriteLine($"Congrats player {playerName[player]}, better luck next time player {playerName[1 - player]}");. (We still have to use the 1 - player trick to get the other player.) \$\endgroup\$ – Olivier Jacot-Descombes Feb 27 at 14:00
  • \$\begingroup\$ 1 - player is necessary : Player is a simple variable but how it must be tweaked all over the place is a code smell. Make a Player class so a client has methods like nextPlayer, currentPlayer, otherPlayer. Small code exercises like TTT don't "motivate" using "tiny, pointless" classes. It may be tiny, but far from pointless, As code grows, complexity easily gets out of control where there is nothing to build upon, so to speak; without encapsulation and abstraction that starts at the core. \$\endgroup\$ – radarbob Mar 1 at 16:52
  • \$\begingroup\$ Yes, we could also create a Game class, a Board class, use interfaces etc. I suggested already a lot of improvements. I fear that further refactorings might be overwhelming for a beginner. Maybe classes (except static ones) and objects were not yet the subject of his lessons. \$\endgroup\$ – Olivier Jacot-Descombes Mar 1 at 17:53
  • 1
    \$\begingroup\$ I grok the concern for over-enthusiasm in answers. Certainly your answer reflects this concern with player code tweaks complementing the measured (C#) language feature and program flow changes. I want to convey the impetus for thinking about a player class in context . I mean to observe, not critique. I am commenting rather than answering the OP because I can see the implementation being too disruptive. \$\endgroup\$ – radarbob Mar 3 at 3:30
7
\$\begingroup\$

In this review let me try to focus on the longest part of your code: WinCondition.

  • Try to separate condition checks from user interaction, do not mix them
  • Try to use better naming, like: CheckVictory, HasAWinner, HasGameEnded, etc.
  • Try to separate data and logic during condition checks

When they are not separated

var conditions = new List<bool>
{
    // checks for horizontal wins
    options[0] == options[1] && options[1] == options[2],
    options[3] == options[4] && options[4] == options[5],
    options[6] == options[7] && options[7] == options[8],
    // checks for vertical wins
    options[0] == options[3] && options[3] == options[6],
    options[1] == options[4] && options[4] == options[7],
    options[2] == options[5] && options[5] == options[8],
    // checks for diagonal wins
    options[0] == options[4] && options[4] == options[8],
    options[2] == options[4] && options[4] == options[6]

};

return conditions.FirstOrDefault(condition => condition));

When they are separated

//Data
var horizontalMarks = new List<(int, int, int)>
{
    (0, 1, 2),
    (3, 4, 5),
    (6, 7, 8)
};

var verticalMarks = new List<(int, int, int)>
{
    (0, 3, 6),
    (1, 4, 7),
    (2, 5, 8)
};

var diagonalMarks = new List<(int, int, int)>
{
    (0, 4, 8),
    (2, 4, 6)
};
var marks = horizontalMarks.Union(verticalMarks).Union(diagonalMarks);

//Logic
bool hasAWinner = false;
foreach (var (first, second, third) in marks)
{
    //If all data in this three positions are the same then we have found a winner
    if (new[] {options[first], options[second], options[third]}.Distinct().Count() != 1) continue;
    hasAWinner = true;
    break;
}

return hasAWinner;

If you wish you can introduce a helper class instead of relying on ValueTuples.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ That should be (0, 3, 6) on the first line after var verticalMarks, but I doubt that this is easier to read/maintain regardless. If you're doing this because it's more general (and e.g. can be extended to an NxN board), one can directly search on the rows, columns, and diagonals using standard for loops. For a new programmer, it might even be useful practice with the modulo and integer division operations. \$\endgroup\$ – Yonatan N Feb 24 at 23:19
  • \$\begingroup\$ @YonatanN Thanks I've fixed that line. Please feel free to post an answer with your proposed solution. \$\endgroup\$ – Peter Csala Feb 25 at 7:07
2
\$\begingroup\$

Olivier Jacot-Descombes already gave some really good feedback. I want to go on from his solution. You're only using static methods, which is basically procedural programing. C# has a strong support from object-oriented programming, and I want to show you, how you might introduce it to your program.

In OOP, you store data that belongs together in an object. You don't give direct access to this data, but rather have some methods that help you to manipulate the objects state.

In your case, it is quite natural to introduce an object that represents the game board. I also introduce an enum to mark the state of each field instead of storing it in strings. The class for your object might look like this:

public enum FieldValue
{
   Unmarked,
   MarkedByPlayer1,
   MarkedByPlayer2
}

public class Gameboard
{
   private FieldValue[] fields = new FieldValue[9];

   public FieldValue GetFieldValue(int col, int row)
   {
      return fields[col*3 + row];
   }

   public void SetFieldValue(int col, int row, FieldValue value)
   {
      fields[col*3 + row] = value;
   }

   public override string ToString()
   {
      StringBuilder sb = new StringBuilder();
      sb.AppendLine("     |     |      ");
      sb.AppendLine($"  {FieldValueToString(fields[0],0)}  |  {FieldValueToString(fields[1],1)}  |  {FieldValueToString(fields[2],2)}");
      sb.AppendLine("_____|_____|_____ ");
      sb.AppendLine("     |     |      ");
      sb.AppendLine($"  {FieldValueToString(fields[3],3)}  |  {FieldValueToString(fields[4],4)}  |  {FieldValueToString(fields[5],5)}");
      sb.AppendLine("_____|_____|_____ ");
      sb.AppendLine("     |     |      ");
      sb.AppendLine($"  {FieldValueToString(fields[6],6)}  |  {FieldValueToString(fields[7],7)}  |  {FieldValueToString(fields[8],8)}");
      sb.AppendLine("     |     |      ");
      return sb.ToString();
   }

   private string FieldValueToString(FieldValue fieldValue, int index)
   {
      return fieldValue switch
      {
          FieldValue.Unmarked        => index.ToString(),
          FieldValue.MarkedByPlayer1 => "X",
          FieldValue.MarkedByPlayer2 => "O"
          _ => ""
      };
   }

   public void Clear() // you can use this method if you want to use your GameBoard object for several games
   {
      for(int i = 0; i < fields.Length; i++)
      {
          fields[i] = FieldStatus.Unmarked;
      }
   }

   public bool IsBoardFull()
   {
        return fields.All(x => x != FieldValue.Unmarked);
   }

   public bool IsWinner(int player)
   {
      FieldValue winnerValue = player == 1 ? FieldValue.MarkedByPlayer1 : FieldValue.MarkedByPlayer2;
      List<(int,int,int)> colsAndRows = new List<(int,int,int)>();
      colsAndRows.Add((0,1,2));
      colsAndRows.Add((3,4,5));
      colsAndRows.Add((6,7,8));
      colsAndRows.Add((0,3,6));
      colsAndRows.Add((1,4,7));
      colsAndRows.Add((2,5,8));
      colsAndRows.Add((0,4,8));
      colsAndRows.Add((2,4,6));
      foreach(var colOrRow in colsAndRows)
      {
        if(fields[colOrRow.Item1] == winnerValue &&
           fields[colOrRow.Item2] == winnerValue &&
           fields[colOrRow.Item3] == winnerValue)
        {
           return true;
        }
      }
      return false;
   }
}

You can use this class by adding

private static GameBoard gameBoard = new GameBoard();

in your Program class an call its method. For example, the PrintBoard() method would become:

private static void PrintBoard() // makes the board
{
    Console.Clear();
    Console.WriteLine(gameBoard.ToString());
}

All of this results in a separation of concerns: You have your Program class, which reads input from the user, displays information on the screen and manages the player's turns. You also have the GameBoard class, which stores the data, and delivers information about the game's state: Did a player win? Is another turn possible? You could even go on and put the logic, which player has the next turn into a separate class. Quite important is also, that this class has no reference to the Console class. That means you have a better separation between logic and UI: If you decide at some point, you don't want to have this class in a console, but a winforms or WPF application, you don't have to touch this class.

Notice also that from outside of the class, one doesn't know that the information is stored in a one-dimensional array. From the outside, we only see the public methods, but not the fields, which are marked as private. If you want to change this to a two-dimensional array, you only have to modify your GameBoard class and can leave all the other classes unchanged. I just show you some of your methods how they might look with a two-dimensional array:

public class Gameboard
{
   private const int size = 3;
   private FieldValue[,] fields = new FieldValue[size,size];

   public FieldValue GetFieldValue(int col, int row)
   {
      return fields[col,row];
   }

   public void SetFieldValue(int col, int row, FieldValue value)
   {
      fields[col,row] = value;
   }

   public bool IsWinner(int player)
   {
      FieldValue winnerValue = player == 1 ? FieldValue.MarkedByPlayer1 : FieldValue.MarkedByPlayer2;
      
      for(int i = 0; i < size; i++)
      {
         // check columns
         if(gameBoard[i,0] == winnerValue && gameBoard[i,1] == winnerValue && gameBoard[i,2] == winnerValue)
         {
            return true;
         }

         // check rows
         if(gameBoard[0,i] == winnerValue && gameBoard[1,i] == winnerValue && gameBoard[2,i] == winnerValue)
         {
            return true;
         }
      }

      // check diagonals
      if(gameBoard[0,0] == winnerValue && gameBoard[1,1] == winnerValue && gameBoard[2,2] == winnerValue)
      {
         return true;
      }

      if(gameBoard[size,size] == winnerValue && gameBoard[size-1,size-1] == winnerValue && gameBoard[size-2,size-2] == winnerValue)
      {
           return true;
      }
      return false;
   }

   // implementation of ToString(), Clear(),  and IsBoardFull() is left as an exercise to the reader
}

From that, it is only a small step to a tic tac toe game with different game size (e.g. 5x5 fields)

\$\endgroup\$

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.