//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
//
//This is a bosque test/benchmark for a tic-tac-toe program.
//
namespace NSMain;
entity Board {
    const playerX: String[PlayerMark] = 'x'#PlayerMark;
    const playerO: String[PlayerMark] = 'o'#PlayerMark;
    const allCellPositions: List[[Int, Int]] = List[[Int, Int]]@{
        @[ 0, 0 ], @[ 1, 0 ], @[ 2, 0 ],
        @[ 0, 1 ], @[ 1, 1 ], @[ 2, 1 ],
        @[ 0, 2 ], @[ 1, 2 ], @[ 2, 2 ]
    };
    const winPositionOptions: List[List[[Int, Int]]] = List[List[[Int, Int]]]@{
        List[[Int, Int]]@{ @[ 0, 0 ], @[ 0, 1 ], @[ 0, 2 ] },
        List[[Int, Int]]@{ @[ 0, 1 ], @[ 1, 1 ], @[ 2, 1 ] },
        List[[Int, Int]]@{ @[ 0, 2 ], @[ 1, 2 ], @[ 2, 2 ] },
        List[[Int, Int]]@{ @[ 0, 0 ], @[ 1, 0 ], @[ 2, 0 ] },
        List[[Int, Int]]@{ @[ 1, 0 ], @[ 1, 1 ], @[ 1, 2 ] },
        List[[Int, Int]]@{ @[ 2, 0 ], @[ 2, 1 ], @[ 2, 2 ] },
        List[[Int, Int]]@{ @[ 0, 0 ], @[ 1, 1 ], @[ 2, 2 ] },
        List[[Int, Int]]@{ @[ 0, 2 ], @[ 1, 1 ], @[ 2, 0 ] }
    };
    //Board is a list of marks, indexed by x,y coords from upper left 0 based
    field cells: List[String[PlayerMark]?];
    factory static createInitialBoard(): { cells: List[String[PlayerMark]?] } {
        return @{ cells=List[String[PlayerMark]?]::createOfSize(9, none) };
    }
    method getOpenCells(): List[[Int, Int]] {
        return Board::allCellPositions->filter(fn(pos: [Int, Int]): Bool => {
            return !this->isCellOccupied(pos[0], pos[1]);
        });
    }
    method getCellContents(x: Int, y: Int): String[PlayerMark]? 
        requires 0 <= x && x < 3 && 0 <= y && y < 3;
    {
        return this.cells->at(x + y * 3);
    }
    method isCellOccupied(x: Int, y: Int): Bool {
        return this->getCellContents(x, y) != none;
    }
    method isCellOccupiedWith(x: Int, y: Int, mark: String[PlayerMark]): Bool 
        requires mark == Board::playerX || mark == Board::playerO; 
    {
        return this->getCellContents(x, y) == mark;
    }
    method markCellWith(x: Int, y: Int, mark: String[PlayerMark]): Board 
        requires mark == Board::playerX || mark == Board::playerO;
        requires 0 <= x && x < 3 && 0 <= y && y < 3;
        requires !this->isCellOccupied(x, y);
    {
        return this<~(cells=this.cells->set(x + y * 3, mark));
    }
    hidden method checkSingleWinOption(opt: List[[Int, Int]], mark: String[PlayerMark]): Bool {
        return opt->all(fn(entry: [Int, Int]): Bool => this->isCellOccupiedWith(entry[0], entry[1], mark));
    }
    hidden method checkSingleWinner(mark: String[PlayerMark]): Bool {
        return Board::winPositionOptions->any(fn(opt: List[[Int, Int]]): Bool => this->checkSingleWinOption(opt, mark));
    }
    method checkForWinner(): String[PlayerMark]? {
        if(this->checkSingleWinner(Board::playerX)) {
            return Board::playerX;
        }
        elif(this->checkSingleWinner(Board::playerO)) {
            return Board::playerO;
        }
        else {
            return none;
        }
    }
}
entity Game {
    field winner: String[PlayerMark]? = none;
    field board: Board = Board@createInitialBoard();
    method hasWinner(): Bool {
        return this.winner != none;
    }
    method getWinner(): String[PlayerMark] 
        requires this->hasWinner();
    {
        return this.winner->as[String[PlayerMark]]();
    }
    method makeAutoMove(mark: String[PlayerMark], rnd: Int): Game
        requires !this->hasWinner();
    {
        var! nboard: Board;
        if(!this.board->isCellOccupied(1, 1)) {
            nboard = this.board->markCellWith(1, 1, mark);
        }
        else {
            var opts = this.board->getOpenCells();
            var tup = opts->uniform(rnd);
            nboard = this.board->markCellWith(...tup, mark);
        }
        return this<~( board=nboard, winner=nboard->checkForWinner() );
    }
    method makeExplicitMove(x: Int, y: Int, mark: String[PlayerMark]): Game 
        requires !this.board->isCellOccupied(x, y);
    {
        var nboard = this.board->markCellWith(x, y, mark);
        return this<~( board=nboard, winner=nboard->checkForWinner() );
    }
}
entity PlayerMark provides Parsable {
    field mark: String;
    override static tryParse(str: String): PlayerMark | None {
        return (str == "x" || str == "o") ? PlayerMark@{ mark=str } : none;
    }
}
entrypoint function main(): Game {
    var! game = Game@{};
    game = game->makeAutoMove(Board::playerX, 0);
    game = game->makeAutoMove(Board::playerO, 1);
    game = game->makeAutoMove(Board::playerX, 2);
    game = game->makeExplicitMove(2, 0, Board::playerO);
    game = game->makeExplicitMove(2, 1, Board::playerX);
    return game;
}