首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >MonoGame中的一款Tic-Tac-Toe游戏

MonoGame中的一款Tic-Tac-Toe游戏
EN

Code Review用户
提问于 2018-04-29 15:49:29
回答 1查看 1.2K关注 0票数 12

我最近开始学习MonoGame和C#,所以为了对两者都更加自信,并做一些不那么简单的事情,我构建了一个Naughts和十字架游戏。我使用7.1版本的C# (这里和那里的值元组.)

游戏内容如下:

  • 玩Tic Toe (AI是无与伦比的)
  • 为玩家和电脑记分
  • 就目前而言,这是一种无穷无尽的匹配。

整个项目本身可以在github上找到。这是游戏的截图。

我有几个问题。整件事看上去太大,太臃肿了,尽管我确实尽量不重复太多。有什么惯用的方法来缩小它的尺寸吗?

关于评论--我的大部分功能都很短,考虑到我糟糕的语言技巧,大部分代码仍然是不言而喻的。但是,我确实尝试为所有函数提供XML文档--这是否被认为是良好的实践?还是应该只为我预期在将来使用的功能而做?

关于访问级别-什么时候我应该使类公开,以及应该在什么地方将它的可见性保留为默认状态?如果我做得对--公共类将在项目之外可用--然而,将公共类和非公共类混合在一起有时不会编译。

静态函数/类-我应该拥有它们吗?

属性与setters/getter,属性和属性--何时使用?当我让对象公开一个对象时,我尝试将它公开为一个属性--这样做正确吗?

一般来说,我想知道通常情况下,如何在保持功能的同时简化/重组/改进这些代码。

这是代码本身。假设所有类都位于名称空间TicTacToe中,并在每个文件的开头有以下行:

代码语言:javascript
运行
复制
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

我的游戏分为以下几类:

TicTacToeGame.cs (取代MonoGame模板中的Game1 --注释是由模板自动生成的)--我保留了它们,因为我现在没有什么可添加的,而且我已经将它主要用作包装类)。

代码语言:javascript
运行
复制
/// 
/// A class to play tic tac toe
/// 
public class TicTacToeGame : Game
{
    GraphicsDeviceManager graphics;
    public SpriteBatch SpriteBatch;
    GameManager gameManager;
    int windowWidth = 1280;
    int windowHeight = 780;

    public TicTacToeGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
        graphics.PreferredBackBufferWidth = windowWidth;
        graphics.PreferredBackBufferHeight = windowHeight;
    }

    /// 
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// 
    protected override void Initialize()
    {
        this.IsMouseVisible = true;
        base.Initialize();
    }

    /// 
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// 
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        SpriteBatch = new SpriteBatch(GraphicsDevice);
        gameManager = new GameManager(this);

        // TODO: use this.Content to load your game content here
    }

    /// 
    /// UnloadContent will be called once per game and is the place to unload
    /// game-specific content.
    /// 
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }

    /// 
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// 
    /// Provides a snapshot of timing values.
    protected override void Update(GameTime gameTime)
    {
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
            Exit();

        if (Keyboard.GetState().IsKeyDown(Keys.F10))
        {
            if (!graphics.IsFullScreen)
                graphics.IsFullScreen = true;
            else
                graphics.IsFullScreen = false;
            graphics.ApplyChanges();
        }

        gameManager.Update();


        base.Update(gameTime);
    }

    /// 
    /// This is called when the game should draw itself.
    /// 
    /// Provides a snapshot of timing values.
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        // TODO: Add your drawing code here
        SpriteBatch.Begin();

        gameManager.Draw();

        SpriteBatch.End();

        base.Draw(gameTime);
    }
}

GameManager.cs -负责连接游戏部件

代码语言:javascript
运行
复制
/// 
/// An object repsonsible for controlling the game
/// 
public class GameManager
{
    public TicTacToeGame TheGame { get; private set; }
    public GameLogic Logic { get; private set; }
    public GameIO IO { get; private set; }
    public GameBoard Board { get; private set; }
    private IPlayer player1;
    private IPlayer player2;

    /// 
    /// Allocates memory for object - used to avoid null reference errors
    /// while initializing the fields of the the object
    /// 
    private GameManager()
    {
        TheGame = null;
        Logic = null;
        IO = null;
        Board = null;
    }



    /// 
    /// The game to which the given Manager belongs to.
    /// 
    /// 
    public GameManager(TicTacToeGame ticTacToeGame) : this()
    {
        this.TheGame = ticTacToeGame;
        Board = new GameBoard();
        Logic = new GameLogic(this);
        IO = new GameIO(this);
        player1 = new HumanPlayer("Player", CrossesOrNoughts.Crosses, this, new ScoreCalculator(10, 1, -10));
        player2 = new ComputerPlayer("Computer", CrossesOrNoughts.Naughts, this, new ScoreCalculator(10, 0, -10));
        Logic.AddPlayers(player1, player2);
    }

    public void Reset()
    {
        Board = new GameBoard();
        Logic = new GameLogic(this);

        CrossesOrNoughts tempSymbol = player1.Symbol();
        player1.SetSymbol(player2.Symbol());
        player2.SetSymbol(tempSymbol);

        IPlayer tempPlayer = player1;
        player1 = player2;
        player2 = tempPlayer;

        Logic.AddPlayers(player1, player2);
    }

    /// 
    /// Update the game state
    /// 
    public void Update()
    {
        IO.Update();
    }

    /// 
    /// Display the board on the screen.
    /// 
    public void Draw()
    {
        IO.Draw();
    }

}

GameIO.cs -负责与游戏的交互-即图形输出和用户输入

代码语言:javascript
运行
复制
/// 
/// The class repsonsible for the graphics part of the game
/// 
public class GameIO
{
    GameManager gameManager;
    TicTacToeGame TheGame => gameManager.TheGame;
    GameLogic Logic => gameManager.Logic;
    GameBoard Board => gameManager.Board;
    Vector2 TopLeft => WindowSize * 0.05f;    // top left position of the board
    Vector2 SquareSize => WindowSize / 5;
    Vector2 BoardSize => SquareSize * 3f;
    Vector2 WindowSize => new Vector2(TheGame.GraphicsDevice.Viewport.Width, TheGame.GraphicsDevice.Viewport.Height);
    Texture2D background;                                // background texture
    Texture2D tableBorders;                              // borders between the squares
    Texture2D xImage;                                    // Crosses image
    Texture2D oImage;                                    // Naughts imaage
    Texture2D horizontalLine;                            // Horizontal line image
    Texture2D verticalLine;                              // vertical line image    
    Texture2D westEastDiagonal;                          // an image of diagonal from topleft to buttom right
    Texture2D eastWestDiagonal;                          // an image of diagonal from topright to butttom left
    GameMessage gameMessage;

    private GameIO() { }

    public GameIO(GameManager gameManager)
    {
        this.gameManager = gameManager;
        background = TheGame.Content.Load("Background");
        tableBorders = TheGame.Content.Load("TableBorders");
        xImage = TheGame.Content.Load("X");
        oImage = TheGame.Content.Load("O");
        horizontalLine = TheGame.Content.Load("HorizontalLine");
        verticalLine = TheGame.Content.Load("VerticalLine");
        westEastDiagonal = TheGame.Content.Load("WestEastDiagonal");
        eastWestDiagonal = TheGame.Content.Load("EastWestDiagonal");
        gameMessage = new GameMessage(gameManager);
    }


    /// 
    /// Draws a square image on the screen
    /// 
    /// Texture name
    /// Upper border of the image position
    /// Left border of the image
    /// Height of the image
    /// Widht of the image
    void DrawSquare(Texture2D image, float topPosition, float leftPosition, float width, float height)
    {
        Rectangle destination = new Rectangle((int)topPosition, (int)leftPosition, (int)width, (int)height);
        TheGame.SpriteBatch.Draw(image, destination, Color.White);
    }

    /// 
    /// Draws the back ground of the table
    /// 
    void DrawBackground()
    {
        DrawSquare(background, TopLeft.X, TopLeft.Y, BoardSize.X, BoardSize.Y);
        DrawSquare(tableBorders, TopLeft.X, TopLeft.Y, BoardSize.X, BoardSize.Y);
    }

    /// 
    /// Fills the squares in the game table
    /// 
    void DrawSquares()
    {
        for (int i = 0; i < 3; ++i)
            for (int j = 0; j < 3; ++j)
            {
                Texture2D filling;
                if (Board[i, j] == CrossesOrNoughts.Crosses)
                    filling = TheGame.Content.Load("X");
                else if (Board[i, j] == CrossesOrNoughts.Naughts)
                    filling = TheGame.Content.Load("O");
                else filling = null;

                if (filling != null)
                    DrawSquare(filling, TopLeft.X + i * SquareSize.X, TopLeft.Y + j * SquareSize.Y,
                        SquareSize.X, SquareSize.Y);
            }
    }

    /// 
    /// Marks with a line the rows that are all either noughts or crosses
    /// 
    void MarkRows()
    {
        for (int i = 0; i < 3; ++i)
        {
            if (Board.IsRowTaken(i))
            {
                DrawSquare(horizontalLine, TopLeft.X, TopLeft.Y + SquareSize.Y * i, BoardSize.X, SquareSize.Y);
            }
        }
    }

    /// 
    /// Marks the collumns that are all either noughts or crosses
    /// 
    void MarkColumns()
    {
        for (int i = 0; i < 3; ++i)
        {
            if (Board.IsColumnTaken(i))
            {
                DrawSquare(verticalLine, TopLeft.X + SquareSize.X * i, TopLeft.Y, SquareSize.X, BoardSize.Y);
            }
        }
    }

    /// 
    /// Marks the main if it contains all noughts or crosses
    /// 
    void MarkDiagonals()
    {
        if (Board.IsMainDiagonalTaken())
            DrawSquare(westEastDiagonal, TopLeft.X, TopLeft.Y, BoardSize.X, BoardSize.Y);
        if (Board.IsSecondaryDiagonalTaken())
            DrawSquare(eastWestDiagonal, TopLeft.X, TopLeft.Y, BoardSize.X, BoardSize.Y);
    }


    /// 
    /// Draws the game board
    /// 
    public void Draw()
    {
        DrawBackground();
        DrawSquares();
        MarkRows();
        MarkColumns();
        MarkDiagonals();
        PrintScores();

        if (Logic.State == GameLogic.GameState.Over)
        {
            DeclareWinner();
            RestartMessage();
        }
    }

    /// 
    /// Translates 2 dimensional vector to position on the board
    /// 
    /// 
    /// 
    public (int row, int column) PositionOnBoard(Vector2 clickPosition)
    {
        return ((int)((clickPosition.X - TopLeft.X) / SquareSize.X),
                (int)((clickPosition.Y - TopLeft.Y) / SquareSize.Y));
    }

    /// 
    /// Processes mouse input from the user
    /// 
    public void ProcessMouseInput()
    {
        MouseState mouseState = Mouse.GetState();

        if (mouseState.LeftButton == ButtonState.Pressed)
        {
            (int row, int column) = PositionOnBoard(new Vector2(mouseState.X, mouseState.Y));
            Logic.Update(row, column);
        }
    }

    /// 
    /// Processes move that was entered as a pair of numbers
    /// 
    /// Row number
    /// Column number
    public void ProcessDigitalInput(int row, int column)
    {
        Logic.Update(row, column);
    }

    /// 
    /// Get input from player and update the state of the game
    /// 
    public void Update()
    {
        if (Logic.State == GameLogic.GameState.Continue) Logic.CurrentPlayer.MakeMove();
        if (Logic.State == GameLogic.GameState.Over)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Space))
                gameManager.Reset();
        }
    }

    /// 
    /// Print player scores
    /// 
    private void PrintScores()
    {
        gameMessage.PrintMessageAt(new Vector2(TopLeft.X, TopLeft.Y + BoardSize.Y + 20),
                                   $"{Logic.player1.Name()}: {Logic.player1.Score()}");
        gameMessage.PrintMessageAt(new Vector2(TopLeft.X, TopLeft.Y + BoardSize.Y + 70),
                                   $"{Logic.player2.Name()}: {Logic.player2.Score()}");
    }

    private void DeclareWinner()
    {
        gameMessage.PrintMessageAt(new Vector2(TopLeft.X, TopLeft.Y + BoardSize.Y + 120),
                                  $"The winner is {Logic.Winner}");
    }

    private void RestartMessage()
    {
        gameMessage.PrintMessageAt(new Vector2(TopLeft.X, TopLeft.Y + BoardSize.Y + 170),
                                   "Press space to continue");
    }

}

GameLogic.cs -负责处理游戏状态-确定胜利者和处理用户输入。

代码语言:javascript
运行
复制
/// 
///  A class respsonsible to the logic of the game. Used to determine winner and control the turns.
/// 
public class GameLogic
{
    /// 
    /// The state of the game - whether it is over or not.
    /// 
    public enum GameState { Continue, Over};
    private GameManager gameManager;
    GameBoard board => gameManager.Board;
    public IPlayer player1 { get; private set; }
    public IPlayer player2 { get; private set; }
    public IPlayer CurrentPlayer { get;  private set; }
    public CrossesOrNoughts Winner { get; private set; }
    public GameState State { get; private set; }

    /// 
    /// Creates a new game logic object and associates it with the gameManager object
    /// 
    /// Game manager object to associate with
    public GameLogic(GameManager gameManager)
    {
        this.gameManager = gameManager;
        this.State = GameState.Continue;
        player1 = null;
        player2 = null;
        CurrentPlayer = player1;
    }

    /// 
    /// Adds a player to the game
    /// 
    /// 
    /// 
    public void AddPlayers(IPlayer player1, IPlayer player2)
    {
        if (this.player1 == null) this.player1 = player1;
        if (this.player2 == null) this.player2 = player2;
        CurrentPlayer = player1;
    }

    /// 
    /// Determines result of the game state determined by internal board object
    /// 
    /// Whehter the game is over and the winner symbol
    private (GameState, CrossesOrNoughts) DetermineResult() => DetermineResult(this.board);

    /// 
    /// Calculates the state and the result of the game at the moment represented by the board.
    /// 
    /// 
    /// Wheher the game is over and who is the winner in case it i. I
    /// f it is not over - Niether is retunred.
    public static (GameState state, CrossesOrNoughts winner) DetermineResult(GameBoard board)
    {
        // go over rows colums and diagonals to che k whether the game is over and we have a winner.
        // After that - check if the board is full
        for(int i = 0; i < 3; ++i)
        {
            if (board.IsRowTaken(i))
                return (GameState.Over, board[0, i]);
            if (board.IsColumnTaken(i))
                return (GameState.Over, board[i, 0]);
        }

        if (board.IsMainDiagonalTaken())
            return (GameState.Over, board[0, 0]);
        if (board.IsSecondaryDiagonalTaken())
            return (GameState.Over, board[2, 0]);
        if (board.IsFull())
            return (GameState.Over, CrossesOrNoughts.Neither);

        return (GameState.Continue, CrossesOrNoughts.Neither);
    }

    /// 
    /// Change the player
    /// 
    void UpdatePlayer()
    {
        CurrentPlayer = (CurrentPlayer == player1) ? player2 : player1;
    }

    /// 
    /// Checks whether position is legal or if it is taken and puts appropriate player sign on it if the game is not over.
    /// After performing player move, updates the player if it the game is not over.
    /// 
    /// 
    /// 
    public void Update(int row, int column)
    {
        if (board.ShouldUpdate(row, column) && State == GameState.Continue)
        {
            board[row, column] = CurrentPlayer.Symbol();
            (State, Winner) = DetermineResult();

            if (State == GameState.Continue) UpdatePlayer();
            else
            {
                player1.UpdateScore(Winner);
                player2.UpdateScore(Winner);
            }
        }
    }

    /// 
    /// Calculates the symbol used by opponent player
    /// 
    /// Returns the symbol used by the opponent
    /// 
    public static CrossesOrNoughts OpponentSymbol(CrossesOrNoughts playerSymbol)
    {
        if (playerSymbol == CrossesOrNoughts.Crosses) return CrossesOrNoughts.Naughts;
        if (playerSymbol == CrossesOrNoughts.Naughts) return CrossesOrNoughts.Crosses;
        else return CrossesOrNoughts.Neither;
    }
}

Board.cs -表示游戏中的棋盘,处理“几何”和“物理”部分--即我们可以在棋盘上放一块,还是有同类型的线条或对角线,等等.

代码语言:javascript
运行
复制
/// 
/// Board to represent board in Noughts and crosses game
/// 
public class GameBoard
{    
    private CrossesOrNoughts[,]  entries;

    /// 
    /// Checks whether the position is within bounds
    /// 
    /// Position row
    /// Position column
    /// True if position is within bounds, false otherwise
    public bool IsValidPosition(int row, int column) => !(row < 0 || row >= 3 || column < 0 || column >= 3);
    public bool IsFree(int row, int column) => !(entries[row, column] == CrossesOrNoughts.Neither);
    public bool ShouldUpdate(int row, int column) => 
        IsValidPosition(row, column) && (entries[row, column] == CrossesOrNoughts.Neither);

    /// 
    /// The construtor - accepts no arguments and creates 3 on 3 empty board
    /// 
    public GameBoard()
    {
        entries = new CrossesOrNoughts[3, 3];
    }

    /// 
    /// Indexer - returns the square at the given position
    /// 
    /// Position row
    /// Position column
    /// Position entry
    public CrossesOrNoughts this[int row, int column]
    {
        get
        {
            if (IsValidPosition(row, column))
                return entries[row, column];
            else throw new IndexOutOfRangeException();
        }
        set
        {
            if (IsValidPosition(row, column))
                entries[row, column] = value;
            else throw new IndexOutOfRangeException();
        }
    }

    /// 
    /// Returns whether the entries in the array are same are either noughts or crosses
    /// 
    /// The array os crosses or nouhgts
    /// True if they are all same or false otherwise
    private bool HaveSameSign(params CrossesOrNoughts[] crossesOrNoughts)
    {
        for (int i = 0; i < crossesOrNoughts.Length - 1; ++i)
        {
            if (crossesOrNoughts[i] == CrossesOrNoughts.Neither) return false;
            if (crossesOrNoughts[i] != crossesOrNoughts[i + 1]) return false;
        }
        return true;
    }

    /// 
    /// Returns the entries in the given row.
    /// 
    /// Row numbers
    /// The row entries in array form
    private CrossesOrNoughts[] TableRow(int row)
    {
        CrossesOrNoughts[] result = new CrossesOrNoughts[entries.GetLength(1)];
        for(int i = 0; i < result.Length; ++i)
        {
            result[i] = entries[i, row];
        }

        return result;
    }

    /// 
    /// Returns the entries in the given column
    /// 
    /// Column number
    /// The column entries in array form
    private CrossesOrNoughts[] TableColumn(int column)
    {
        CrossesOrNoughts[] result = new CrossesOrNoughts[entries.GetLength(0)];

        for(int i = 0; i < result.Length; ++i)
        {
            result[i] = entries[column, i];
        }

        return result;
    }

    /// 
    /// Returns the entries in diagonal from top left corner to buttom right corner
    /// 
    /// Entries of the main diagonal in array form
    private CrossesOrNoughts[] MainDiagonal()
    {
        CrossesOrNoughts[] result = new CrossesOrNoughts[entries.GetLength(0)];
        for (int i = 0; i < result.Length; ++i)
            result[i] = entries[i, i];

        return result;
    }

    /// 
    /// Return  the entries in the diagonal from buttom left to upper right corner
    /// 
    /// The entries of the secondary diagonal in array form
    private CrossesOrNoughts[] SecondaryDiagonal()
    {
        CrossesOrNoughts[] result = new CrossesOrNoughts[entries.GetLength(0)];
        for (int i = 0; i < result.Length; ++i)
            result[i] = entries[result.Length - 1 - i, i];

        return result;
    }

    /// 
    /// Checks whether the board is full
    /// 
    /// 
    public bool IsFull()
    {
        for(int i = 0; i < 3; ++i)
        {
            for(int j = 0; j < 3; ++j)
            {
                if (entries[i, j] == CrossesOrNoughts.Neither) return false;
            }
        }

        return true;
    }

    /// 
    /// Checks whether the given row is full and contains same signs
    /// 
    /// 
    /// True if the column entries are either all noughts or all rosses, false otherwise
    public bool IsRowTaken(int row) => HaveSameSign(TableRow(row));

    /// 
    /// Checks whether the given column is marked and contains same signs
    /// 
    /// 
    /// True if the column entries are either all noughts or all crosses, false otherwise
    public bool IsColumnTaken(int column) => HaveSameSign(TableColumn(column));

    /// 
    /// Checks whether the main diagonal is marked and contains same signs
    /// 
    /// True if all the entries in the main diagonal are either all noughts or all crosses, false otherwise
    public bool IsMainDiagonalTaken() => HaveSameSign(MainDiagonal());

    /// 
    /// Checks whther the secondary diagonal is marked and contains same signs
    /// 
    /// True if all the entries in the main diagonal are either all noughts or all crosses, false otherwise
    public bool IsSecondaryDiagonalTaken() => HaveSameSign(SecondaryDiagonal());
}

代表游戏玩家的IPlayer界面

代码语言:javascript
运行
复制
/// 
/// An interface representing the player in the game.
/// 
public interface IPlayer
{
    CrossesOrNoughts Symbol();
    void SetSymbol(CrossesOrNoughts symbol);
    void MakeMove();
    string Name();
    int Score();
    void UpdateScore(CrossesOrNoughts crossesOrNoughts);
}

该接口由两个类HumanPlayer和ComputerPlayer实现。

HumanPlayer.cs

代码语言:javascript
运行
复制
/// 
/// A class to represent human controlled player in the game
/// 
public class HumanPlayer : IPlayer
{
    private string name;
    private CrossesOrNoughts symbol;
    private GameManager gameManager;
    private int score;
    private ScoreCalculator scoreCalculator;

    /// 
    /// Creats an instance of player
    /// 
    /// Player's name
    /// What player puts on the board - crosses or nauughts
    /// Interface to the game
    public HumanPlayer(String name, CrossesOrNoughts symbol, GameManager gameManager, ScoreCalculator scoreCalculator) 
    {
        this.name = name;
        this.symbol = symbol;
        this.gameManager = gameManager;
        this.score = 0;
        this.scoreCalculator = scoreCalculator;
    }

    /// 
    /// make a Move in the game
    /// 
    public void MakeMove() => gameManager.IO.ProcessMouseInput();

    /// 
    /// The symbol used by player
    /// 
    /// Returns whether the player puts crosses or naughts
    public CrossesOrNoughts Symbol() => symbol;

    /// 
    ///  Player's name
    /// 
    /// Player's name
    public string Name() => name;

    /// 
    /// Score of the player
    /// 
    /// The score of the player
    public int Score() => score;

    public void SetSymbol(CrossesOrNoughts symbol)
    {
        this.symbol = symbol;
    }

    /// 
    /// Update the player's score
    /// 
    /// Current winner of the game
    public void UpdateScore(CrossesOrNoughts winner)
    {
        if (winner == symbol) score += scoreCalculator.WinScore;
        if (winner == GameLogic.OpponentSymbol(symbol)) score += scoreCalculator.LoseScore;
        else score += scoreCalculator.DrawScore;
    }

}

ComputerPlayer.cs

代码语言:javascript
运行
复制
/// 
/// Represents computer controlled player - designed to always pick the best move
/// 
public class ComputerPlayer : IPlayer
{
    private string name;
    private CrossesOrNoughts symbol;
    private GameManager gameManager;
    private int score;
    private ScoreCalculator scoreCalculator;

    public ComputerPlayer(string name, CrossesOrNoughts symbol, GameManager gameManager, ScoreCalculator scoreCalculator)
    {
        this.name = name;
        this.symbol = symbol;
        this.gameManager = gameManager;
        this.score = 0;
        this.scoreCalculator = scoreCalculator;
    }

    /// 
    /// Symbol used by the player  - crosses or noughts
    /// 
    /// The symbol used by the player
    public CrossesOrNoughts Symbol() => symbol;
    public string Name() => name;

    /// 
    /// Calculates the best possible move and passes it to the IO
    /// 
    public void MakeMove()
    {
        MoveAnalysis move = MoveCalculator.BestMove(symbol, gameManager.Board);
        gameManager.IO.ProcessDigitalInput(move.Row, move.Column);
    }

    /// 
    /// The score of the player
    /// 
    /// 
    public int Score() => score;

    public void SetSymbol(CrossesOrNoughts symbol)
    {
        this.symbol = symbol;
    }

    /// 
    /// Update the player's score
    /// 
    /// Current winner of the game
    public void UpdateScore(CrossesOrNoughts winner)
    {
        if (winner == symbol) score += scoreCalculator.WinScore;
        if (winner == GameLogic.OpponentSymbol(symbol)) score += scoreCalculator.LoseScore;
        else score += scoreCalculator.DrawScore;
    }

}

helper类MoveCalculator用于ComputerPlayer,它允许使用helper类MoveAnalysis计算最佳移动,以跟踪移动统计信息。

代码语言:javascript
运行
复制
/// 
/// Statistaics for the move - position and who's the winner if we make it
/// 
class MoveAnalysis
{
    public int Row { get; set; }
    public int Column { get; set; }
    public CrossesOrNoughts Winner { get; set; }

    public MoveAnalysis(int row, int column, CrossesOrNoughts winner)
    {
        Row = row;
        Column = column;
        Winner = winner;
    }
}

/// 
/// Static class used to calculate the optimal move
/// 
static class MoveCalculator
{
    private static Random random = new Random();
    /// 
    /// Calculate the result to which leads outting playerSymbol at gven position on a board
    /// assuming that both players play optimally. 
    /// 
    /// Row position
    /// Column position
    /// The game board
    /// Symbol - either naughts or crosses.
    /// 
    private static CrossesOrNoughts EvaluateMove(int row, int column, GameBoard board, CrossesOrNoughts playerSymbol)
    {
        // Sanity check - checks whether the the position is legal
        if (playerSymbol == CrossesOrNoughts.Neither || !board.IsValidPosition(row, column))
            throw new ArgumentOutOfRangeException("Player can be either Crosses or Naughts.");
        if (board[row, column] != CrossesOrNoughts.Neither)
            throw new IndexOutOfRangeException("Square already occupied.");

        /* Calculates the score recursively. 
         * We put the current player's sign on the board and check the result.
         * If the game is over we return the result of the game.
         * Otherwise, we go over all available positions and pick return the best score 
         * achivable by the opponent
         */
        board[row, column] = playerSymbol;
        (GameLogic.GameState state, CrossesOrNoughts winner) = GameLogic.DetermineResult(board);

        if (state == GameLogic.GameState.Over)
        {
            board[row, column] = CrossesOrNoughts.Neither;
            return winner;
        }

        CrossesOrNoughts bestOponentResult = playerSymbol;
        CrossesOrNoughts opponentSymbol = GameLogic.OpponentSymbol(playerSymbol);

        for (int i = 0; i < 3; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                if (board[i, j] == CrossesOrNoughts.Neither)
                {
                    CrossesOrNoughts result = EvaluateMove(i, j, board, opponentSymbol);
                    if (result == opponentSymbol)
                        bestOponentResult = opponentSymbol;
                    else if (result == CrossesOrNoughts.Neither && bestOponentResult == playerSymbol)
                        bestOponentResult = CrossesOrNoughts.Neither;
                }
            }
        }

        board[row, column] = CrossesOrNoughts.Neither;
        return bestOponentResult;
    }

    /// 
    /// Calculates the best move that can be maid by the player
    /// 
    /// Players symbol - either crosses or noughtss
    /// the game board
    /// Best move that player can make
    public static MoveAnalysis BestMove(CrossesOrNoughts playerSymbol, GameBoard board)
    {
        // list of possible moves
        List moves = new List();

        // go over all empty positions and them as possible moves
        for (int i = 0; i < 3; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                if (board[i, j] == CrossesOrNoughts.Neither)
                {
                    CrossesOrNoughts winner = EvaluateMove(i, j, board, playerSymbol);
                    moves.Add(new MoveAnalysis(i, j, winner));
                }
            }
        }

        // determine the best possible move and result
        MoveAnalysis bestMove = moves[0];

        for (int i = 1; i < moves.Count; ++i)
        {
            if (moves[i].Winner == playerSymbol)
            {
                bestMove = moves[i];
            }

            else if (moves[i].Winner == CrossesOrNoughts.Neither && bestMove.Winner == GameLogic.OpponentSymbol(playerSymbol))
            {
                bestMove = moves[i];
            }
        }

        // randomize - make a list of best moves and chose the best one
        List bestMoves = new List();
        for(int i = 0; i < moves.Count; ++i)
        {
            if (moves[i].Winner == bestMove.Winner)
                bestMoves.Add(moves[i]);
        }

        return bestMoves[random.Next(bestMoves.Count)];
    }
}




/// 
/// Statistaics for the move - position and who's the winner if we make it
/// 
class MoveAnalysis
{
    public int Row { get; set; }
    public int Column { get; set; }
    public CrossesOrNoughts Winner { get; set; }

    public MoveAnalysis(int row, int column, CrossesOrNoughts winner)
    {
        Row = row;
        Column = column;
        Winner = winner;
    }
}

/// 
/// Static class used to calculate the optimal move
/// 
static class MoveCalculator
{
    private static Random random = new Random();
    /// 
    /// Calculate the result to which leads outting playerSymbol at gven position on a board
    /// assuming that both players play optimally. 
    /// 
    /// Row position
    /// Column position
    /// The game board
    /// Symbol - either naughts or crosses.
    /// 
    private static CrossesOrNoughts EvaluateMove(int row, int column, GameBoard board, CrossesOrNoughts playerSymbol)
    {
        // Sanity check - checks whether the the position is legal
        if (playerSymbol == CrossesOrNoughts.Neither || !board.IsValidPosition(row, column))
            throw new ArgumentOutOfRangeException("Player can be either Crosses or Naughts.");
        if (board[row, column] != CrossesOrNoughts.Neither)
            throw new IndexOutOfRangeException("Square already occupied.");

        /* Calculates the score recursively. 
         * We put the current player's sign on the board and check the result.
         * If the game is over we return the result of the game.
         * Otherwise, we go over all available positions and pick return the best score 
         * achivable by the opponent
         */
        board[row, column] = playerSymbol;
        (GameLogic.GameState state, CrossesOrNoughts winner) = GameLogic.DetermineResult(board);

        if (state == GameLogic.GameState.Over)
        {
            board[row, column] = CrossesOrNoughts.Neither;
            return winner;
        }

        CrossesOrNoughts bestOponentResult = playerSymbol;
        CrossesOrNoughts opponentSymbol = GameLogic.OpponentSymbol(playerSymbol);

        for (int i = 0; i < 3; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                if (board[i, j] == CrossesOrNoughts.Neither)
                {
                    CrossesOrNoughts result = EvaluateMove(i, j, board, opponentSymbol);
                    if (result == opponentSymbol)
                        bestOponentResult = opponentSymbol;
                    else if (result == CrossesOrNoughts.Neither && bestOponentResult == playerSymbol)
                        bestOponentResult = CrossesOrNoughts.Neither;
                }
            }
        }

        board[row, column] = CrossesOrNoughts.Neither;
        return bestOponentResult;
    }

    /// 
    /// Calculates the best move that can be maid by the player
    /// 
    /// Players symbol - either crosses or noughtss
    /// the game board
    /// Best move that player can make
    public static MoveAnalysis BestMove(CrossesOrNoughts playerSymbol, GameBoard board)
    {
        // list of possible moves
        List moves = new List();

        // go over all empty positions and them as possible moves
        for (int i = 0; i < 3; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                if (board[i, j] == CrossesOrNoughts.Neither)
                {
                    CrossesOrNoughts winner = EvaluateMove(i, j, board, playerSymbol);
                    moves.Add(new MoveAnalysis(i, j, winner));
                }
            }
        }

        // determine the best possible move and result
        MoveAnalysis bestMove = moves[0];

        for (int i = 1; i < moves.Count; ++i)
        {
            if (moves[i].Winner == playerSymbol)
            {
                bestMove = moves[i];
            }

            else if (moves[i].Winner == CrossesOrNoughts.Neither && bestMove.Winner == GameLogic.OpponentSymbol(playerSymbol))
            {
                bestMove = moves[i];
            }
        }

        // randomize - make a list of best moves and chose the best one
        List bestMoves = new List();
        for(int i = 0; i < moves.Count; ++i)
        {
            if (moves[i].Winner == bestMove.Winner)
                bestMoves.Add(moves[i]);
        }

        return bestMoves[random.Next(bestMoves.Count)];
    }
}

一个用来帮助计算游戏ScoreCalculator.cs中分数的类

代码语言:javascript
运行
复制
/// 
/// A class to help to determine the score obtained by the player as a result of the game
/// 
public class ScoreCalculator
{
    public int WinScore { get; private set; }
    public int DrawScore { get; private set; }
    public int LoseScore { get; private set; }

    /// 
    /// Constructor
    /// 
    /// Score for the win
    /// Score for the draw
    /// Score for the loss
    public ScoreCalculator(int win, int draw, int lose)
    {
        WinScore = win;
        DrawScore = draw;
        LoseScore = lose;
    }
}
EN

回答 1

Code Review用户

发布于 2018-05-01 06:54:07

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/193211

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档