// MINIMAX FUNCTION
        public Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> > Minimax(GameBoard <counters> board, counters counter, int ply, Tuple <int, int> positions, bool mmax, GameBoard <int> scoreBoard, ref int cont, int alpha, int beta)
        {
            // decs
            counters us       = Flip(counter);
            int      ourindex = 1;
            List <Tuple <int, int> > availableMoves = getAvailableMoves(board, positions);
            // create new list of Tuple<int,int>
            List <Tuple <int, Tuple <int, int> > > result_list = new List <Tuple <int, Tuple <int, int> > >();
            int bestScore         = mmax ? -1001 : 1001;
            int score             = Consts.MIN_SCORE; // current score of move
            Tuple <int, int> Move = new Tuple <int, int>(0, 0);

            Tuple <int, int> bestMove = new Tuple <int, int>(1, 1); // best move with score// THRESHOLD <=============
                                                                    // add assertion here
                                                                    // decs for random move
            Random           rnd       = new Random();
            int              randMoveX = rnd.Next(1, 7);            // creates a number between 1 and 7
            int              randMoveY = rnd.Next(1, 7);            // creates a number between 1 and 7
            Tuple <int, int> randMove  = new Tuple <int, int>(randMoveX, randMoveY);

            // check win
            if (availableMoves.Count == 0)
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(10, positions, board, scoreBoard));
            }
            else if (Win(board, counter))
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(1000, positions, board, scoreBoard));
            }
            else if (Win(board, this.otherCounter))
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(-1000, positions, board, scoreBoard));
            }

            if (FindTwoInARow(board, counter) && Two(board, counter) && board[Move.Item1, Move.Item2] == counters.EMPTY)
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(100, positions, board, scoreBoard));
            }
            else if (FindTwoInARow(board, this.otherCounter) && Two(board, this.otherCounter) && board[Move.Item1, Move.Item2] == counters.EMPTY)
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(-100, positions, board, scoreBoard));
            }
            else if (FindOneInARow(board, ourindex, this.otherCounter) && One(board, counter) && board[Move.Item1, Move.Item2] == counters.EMPTY)
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(100, positions, board, scoreBoard));
            }
            else if (FindOneInARow(board, ourindex, this.otherCounter) && One(board, this.otherCounter) && board[Move.Item1, Move.Item2] == counters.EMPTY)
            {
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(-100, positions, board, scoreBoard));
            }

            // CHECK DEPTH
            else if (ply > maxPly)
            {
                score = EvalCurrentBoard(board, scoreBoard, ourindex, us); // call stat evaluation func - takes board and player and gives score to that player
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(score, positions, board, scoreBoard));
            }
            // Console.WriteLine("HWL: Is this line reached?");
            // Console.ReadLine(); // HWL: even with a ReadLine here, the program runs to the end
            // place random move
            if (board.IsEmpty()) // if board is empty then play random move
            {
                // decide scoring for random move
                score = EvalForWin(board, ourindex, us);    // 1 for win, 0 for unknown
                // assign two score
                if (FindTwoInARow(board, us))               // player win?
                {
                    score = 100;                            // twoinrow confirmed
                }
                if (FindTwoInARow(board, us + 1))           // twoinrow opponent?
                {
                    score = -100;                           // twoinrow confirmed
                }
                if (FindOneInARow(board, ourindex, us))     // oneinrow?
                {
                    score = 10;                             // oneinrow confirmed
                }
                if (FindOneInARow(board, ourindex, us + 1)) // oneinarow opponent?
                {
                    score = -10;                            // oneinrow confirmed
                }
                // MakeRandomMove(board, counter, ply, positions, !mmax, scoreBoard, ref cont);

                //  Console.ReadLine();
                return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(score, positions, board, scoreBoard));
            }
            GameBoard <counters> copy;

            // make copy original board
            copy = board.Clone();     // make copy board
                                      // copy.DisplayBoard(); // display copy board
                                      // cell priority - favour centre and corners
            for (int i = 0; i < availableMoves.Count; i++)
            {
                Move = availableMoves[i];               // current move
                                                        // cell priority - favour centre and corners
                                                        // HWL: where do you actual place the piece for the position in Move? you don't do this here, just pass Move to the call of Minimax below; in the recursive call you then overwrite the input argument with a random move (see comment at start of Minimax; so you are actually not considering Move at all!
                                                        // HWL: try placing the piece here, and below just use the score
                copy[Move.Item1, Move.Item2] = counter; // place counter
                                                        // GameBoard board0 = MakeMove(board, move); // copies board - parallel ready

                // ************************************************************************************************
                // ************************************************************************************************
                // ************************************** MAIN MINIMAX WORK ***************************************
                // ************************************************************************************************
                // ************************************************************************************************

                // list defined in Minimax declarations
                Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> > result = Minimax(copy, Flip(counter), ply + 1, Move, !mmax, scoreBoard, ref cont, alpha, beta); /* swap player */ // RECURSIVE call

                // trying to prevent preventing cell overwrite
                copy[Move.Item1, Move.Item2] = counters.EMPTY; /*  counter; */ // HWL: remove counter that was tried in this iteration
                                                                               // GameBoard board0 = MakeMove(board, move); // copies board - parallel ready

                score     = -result.Item1;                                     // assign score
                positions = result.Item2;                                      // present position (x,y)

                // list of all result - list of possible result Tuple<int, int>
                result_list.Add(new Tuple <int, Tuple <int, int> >(score, positions));

                // assign score to correct cell in score
                scoreBoard[result.Item2.Item1, result.Item2.Item2] = score;
                // HWL: summarise the result of having tried Move, print the assoc scoreboard and check that the matching move is the one for the highest score on the board

                /*    Console.WriteLine(mmax.ToString() +
                 * " **HWL (ply={0}) Trying Move ({4},{5}) gives score {1} and position ({2},{3})  [[so far bestScore={6}, bestMove=({7},{8})",
                 *    ply, score, result.Item2.Item1, result.Item2.Item2, Move.Item1, Move.Item2,
                 *    bestScore, bestMove.Item1, bestMove.Item2);
                 */
                //   scoreBoard.DisplayBoard();
                if (score == Consts.MIN_SCORE || score == Consts.MAX_SCORE)
                {
                    /*   Console.WriteLine("**HWL CLAIM: putting piece {0} at position ({1},{2}) gives 3-in-a-row:",
                     *       counter, Move.Item1, Move.Item2);
                     */
                }

                // ************************************************************************************************
                // ************************************************************************************************
                // ******************************** END OF MAIN MINIMAX WORK **************************************
                // ************************************************************************************************
                // ************************************************************************************************

                // ************************************************************************************************
                // ************************* WHEN TO MAXIMISE, WHEN TO MINIMISE ***********************************
                // ******************************* WITH ALPHA-BETA PRUNING ****************************************
                // ************************************************************************************************
                // ************************************************************************************************

                // if maximising
                if (/* true HWL || */ mmax)
                {
                    alpha = score;
                    if (score > bestScore)
                    {
                        bestMove  = Move;
                        bestScore = score;
                    }
                    if (alpha > bestScore)
                    {
                        bestMove  = Move;
                        bestScore = alpha;
                    }
                }
                // if minimising
                else
                {
                    if (bestScore > score)
                    {
                        bestMove  = Move;
                        bestScore = score;
                    }
                    if (beta <= alpha)
                    {
                        bestScore = alpha;
                    }

                    return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(bestScore, positions, board, scoreBoard));
                }

                // ************************************************************************************************
                // ************************************************************************************************
                // ********************* END OF WHEN TO MAXIMISE, WHEN TO MINIMISE ********************************
                // ******************************* WITH ALPHA-BETA PRUNING ****************************************
                // ************************************************************************************************



                cont++;                                                                                                                // increment positions visited
            }
            return(new Tuple <int, Tuple <int, int>, GameBoard <counters>, GameBoard <int> >(bestScore, bestMove, board, scoreBoard)); // return
        }