
#define CSHARP2PLUS

namespace AddisonWesley.Michaelis.EssentialCSharp.Chapter03.TicTacToe
{
    using System;

#pragma warning disable 1030 // Wyłącza ostrzeżenia zdefiniowane przez użytkownika.

    // Klasa TicTacToe umożliwia dwóm osobom
    // grę w kółko i krzyżyk.
    public class TicTacToeGame      // Deklaracja klasy TicTacToeGame.
    {
        public static void Main()  // Deklaracja punktu wejścia do programu.
        {
            // Przechowuje ruchy wykonane przez każdego z graczy.
            int[] playerPositions = { 0, 0 };

            // Początkowo bieżący gracz (reprezentuje go zmienna currentPlayer) to gracz 1.
            int currentPlayer = 1;

            // Zwycięzca.
            int winner = 0;

            string input = null;


            // Wyświetlanie planszy i 
            // prośby o wykonanie następnego posunięcia 
            // przez bieżącego gracza.
            for (int turn = 1; turn <= 10; ++turn)
            {
                DisplayBoard(playerPositions);

                #region Wykrywanie końca gry
                if (EndGame(winner, turn, input))
                {
                    break;
                }
                #endregion Wykrywanie końca gry

                input = NextMove(playerPositions, currentPlayer);

                winner = DetermineWinner(playerPositions);

                // Zmiana bieżącego gracza.
                currentPlayer = (currentPlayer == 2) ? 1 : 2;
            }
        }

        private static string NextMove(int[] playerPositions,
                       int currentPlayer)
        {
            string input;

            // Wielokrotne wyświetlanie prośby o podanie ruchu
            // (do czasu wykonania poprawnego posunięcia).
            bool validMove;
            do
            {
                // Wyświetlanie prośby o wykonanie posunięcia przez bieżącego gracza.
                System.Console.Write($"\nGracz {currentPlayer} – wprowadź ruch:");
                input = System.Console.ReadLine();
                validMove = ValidateAndMove(playerPositions,
                              currentPlayer, input);
            } while(!validMove);

            return input;
        }

        static bool EndGame(int winner, int turn, string input)
        {
            bool endGame = false;
            if(winner > 0)
            {
                System.Console.WriteLine($"\nGracz {winner} wygrał!!!");
                endGame = true;
            }
            else if(turn == 10)
            {
                // Po dziesiątym wyświetleniu planszy 
                // program kończy grę, zamiast ponownie 
                // wyświetlać prośbę o podanie ruchu.
                System.Console.WriteLine("\nGra zakończona remisem!");
                endGame = true;
            }
            else if(input == "" || input == "quit")
            {
                // Sprawdzanie, czy gracz zakończył grę przez wciśnięcie
                // klawisza Enter bez wpisanych znaków lub w wyniku wprowadzenia instrukcji "quit".
                System.Console.WriteLine("Gracz zakończył rozgrywkę");
                endGame = true;
            }
            return endGame;
        }

        static int DetermineWinner(int[] playerPositions)
        {
            int winner = 0;

            // Określanie, czy ktoś zwyciężył.
            int[] winningMasks = {
          7, 56, 448, 73, 146, 292, 84, 273};

            foreach(int mask in winningMasks)
            {
                if((mask & playerPositions[0]) == mask)
                {
                    winner = 1;
                    break;
                }
                else if((mask & playerPositions[1]) == mask)
                {
                    winner = 2;
                    break;
                }
            }
            return winner;
        }

        static bool ValidateAndMove(
          int[] playerPositions, int currentPlayer, string input)
        {
            bool valid = false;

            // Sprawdzanie danych wprowadzonych przez gracza.
            switch (input)
            {
                case "1":
                case "2":
                case "3":
                case "4":
                case "5":
                case "6":
                case "7":
                case "8":
                case "9":
#warning  "Dozwolone jest wielokrotne wprowadzenie tego samego ruchu."
                    int shifter;  // Liczba pozycji, o jakie trzeba się 
                    // przesunąć, by ustawić bit.
                    int position;  // Ustawiany bit.

                    // Instrukcja int.Parse() przekształca zmienną input na liczbę całkowitą.
                    // Wyrażenie "int.Parse(input) – 1" zastosowano, ponieważ 
                    // tablice są indeksowane od zera.
                    shifter = int.Parse(input) - 1;

                    // Przesunięcie maski 00000000000000000000000000000001
                    // o wartość shifter.
                    position = 1 << shifter;

                    // Wykonanie operacji OR na komórkach bieżącego gracza
                    // w celu dodania nowego ruchu.
                    // Ponieważ currentPlayer to 1 lub 2, należy
                    // odjąć 1, aby zastosować zmienną currentPlayer jako
                    // indeks tablicy (indeksowanej od zera).
                    playerPositions[currentPlayer - 1] |= position;

                    valid = true;
                    break;

                case "":
                case "quit":
                    valid = true;
                    break;

                default:
                    // Jeśli żadna z wcześniejszych instrukcji case nie
                    // została uruchomiona, oznacza to, że gracz wprowadził błędny tekst.
                    System.Console.WriteLine(
                        "\nBŁĄD: Wprowadź wartość od 1 do 9. "
                        + "Wciśnij ENTER, aby zakończyć.");
                    break;
            }

            return valid;
        }

        static void DisplayBoard(int[] playerPositions)
        {
            // Ta tablica reprezentuje kreski między komórkami
            // z poszczególnych wierszy.
            string[] borders = {
  "|", "|", "\n---+---+---\n", "|", "|",
  "\n---+---+---\n", "|", "|", ""
  };

            // Wyświetlanie aktualnego stanu planszy.
            int border = 0;  // Ustawianie pierwszej kreski (borders[0] = "|").

#if CSHARP2PLUS
            System.Console.Clear();
#endif

            for(int position = 1;
                 position <= 256;
                 position <<= 1, border++)
            {
                char token = CalculateToken(
                    playerPositions, position);

                // Wyświetlanie wartości komórki i linii, która
                // powinna pojawiać się po danej komórce.
                System.Console.Write($" {token} {borders[border]}");
            }
        }

        static char CalculateToken(
            int[] playerPositions, int position)
        {
            // Inicjowanie tablicy symbolami 'X' i 'O'.
            char[] players = { 'X', 'O' };

            char token;
            // Jeśli gracz zaznaczył pole o danej pozycji, 
            // należy ustawić zmienną token na symbol powiązany z danym graczem.
            if ((position & playerPositions[0]) == position)
            {
                // Gracz 1 zaznaczył dane pole.
                token = players[0];
            }
            else if((position & playerPositions[1]) == position)
            {
                // Gracz 2 zaznaczył dane pole.
                token = players[1];
            }
            else
            {
                // Dane pole jest puste.
                token = ' ';
            }
            return token;
        }

#line 113 "TicTacToe.cs"
        // Miejsce na wygenerowany kod.
#line default
    }
}