我正在制作一款用OpenGL渲染的国际象棋游戏。
我不想找人告诉我所有的答案,我想自己找出代码,但我真正需要的是为我指出正确的概念。在这一点上,我不确定从哪里开始。以下是我的想法:
枚举TurnState
,具有以下值:
playerOneTurn
playerTwoTurn
Stopped
枚举GameState
,具有以下值:
playerOneCheck
playerTwoCheck
playerOnecCheckMate
PlayerTwoCheckMate
InitializingGame
Tie
NormalPlay
一个抽象类Player
和一个子类Computer
。
一个类,ChessGame
,具有以下字段:
Player p1, p2
TurnState turnState
GameState gameState
一个类,Move
,具有以下字段:
*Piece
Location origin
Location destination
一个类,Location
,具有以下字段:
row
col
*ChessBoard
一个类ChessBoard
,有一个方法isValid
,它接受一个Move
并检查移动是否有效。
一个抽象类ChessPieces
,具有以下方法:
GetValue() // returns an int value of the piece (for scoring)
GetPosition() // returns the current position of a piece
getIsSelected() // returns a boolean, true if selected, false if unselected
move() // moves the piece in a way dependent upon what piece
以及以下子类:
Pawn
Rook
Queen
King
Knight
关于国际象棋的AI部分:
为了获得国际象棋AI或任何类型的回合制游戏AI,你需要计算游戏在一个给定回合中的"值"(这很重要)(也就是说,你给每一块分配一个值,并将玩家1和玩家2的值相加,然后你做score=玩家1score-玩家2score,所以负值对玩家2有利,正值对玩家1有利,这只是一个基本的例子,不是很有效,但这是解释游戏的"价值"是)。
在你计算出之后,你需要能够计算出玩家在特定棋盘配置下的每一个可能的移动。
有了它,你将能够构建一个决策树,其中你将把游戏的当前状态作为根节点。树的下一个"级别"将代表从当前状态(依此类推)可以达到的每一种可能状态。重要的是要注意,若你们考虑玩家1在树的级别上的可能移动,你们会考虑玩家下一个级别上的两个可能移动。
接下来要做的是:
假设玩家1要移动,他会在树上观察直到深度5(对于一场国际象棋游戏,你永远不会在整棵树上观察)。因此,他将选择一个为他优化的移动,这意味着:在每个级别上,他都会考虑自己的最佳移动或玩家2的最佳移动(因此他将处理最坏的情况),因此他将移动树的下一级别中值最高的节点。
要计算节点的值,请执行以下操作:注意:考虑到根节点的深度为0,对于player1,每个奇数深度节点都需要为maxValue,对于player 2,每个偶数深度节点都必须为minValue。
您将把树扩展到您定义的最大深度,对于最大深度中的节点,您只需计算板的值(我在回答的开头提到过),对于上面的节点,我们将执行以下操作:
偶数节点的值:所有子节点之间的minValue奇数节点的值:所有子节点之间的最大值
因此,基本上,您将根据更深节点的值进行回归,以找到节点的值。
好吧,这是最基本的想法,从中你可以研究一些其他的东西,如果你想的话,你可以PM我,我已经在这种搜索上做了一些工作,我只是在这里描述了最基本的思想,对于一个高效的代码,你需要很多优化技术。
希望它对有一点帮助
首先:将两者分离:AI和GUI/OpenGL。在国际象棋中,GUI和AI(计算机国际象棋术语中的"引擎")在两个不同的进程中使用预定义的协议进行通信是很正常的。这方面最流行的两个协议是UCI和WinBoard。
对于国际象棋引擎部分,你基本上需要三件事:
- 董事会/职位代表
- 叶节点评估函数
- 一种搜索算法
我建议你阅读:
- 国际象棋程序WIKI
- TalkChess电脑象棋论坛
- 研究一个开源的计算机象棋引擎,比如Stockfish、Crafty或Fruit
这可能不是直接回答你的问题(实际上你的问题是什么?),但你提到你想要指向正确概念的指针。
oysteijo是对的,其中一个非常重要的概念是将程序的各个部分相互分离。
对于像国际象棋这样的东西,存在着许多有效而优雅的国际象棋游戏状态表示。我想说MVC(模型、视图、控制器)设计模式在国际象棋游戏中运行得很好。
希望这会有一些意义,如果没有,我建议你多读一些MVC。
您的模型将主要涉及存储游戏状态表示的数据结构,这就是棋盘。一件作品只能出现在64个地点中的一个,作品的类型、数量以及每件作品的作用都有限制。模特将负责处理这些东西。给模型提供确定任何给定动作合法性的逻辑(即游戏的属性,不一定涉及游戏的任何给定实例的状态)也是有意义的。
视图是所有与演示文稿相关的代码所在的位置。OpenGL在这里所做的一切,以及"调试"例程,它可能(例如)将棋盘的ASCII表示打印到控制台。
控制器可能具有一些与用户接口以处理输入的功能。控制器是操作模型("将E5移动到D3":控制器中的一个函数可能会调用model.moveKnight('D3')
)和视图("在光荣的3D中绘制板":控制器可能会调用类似于openGLView.draw(model)
的操作)的代码部分
MVC帮助实现的主要目标之一是执行不同任务的代码部分的独立性。如果你的人工智能发生了一些变化,导致渲染算法出现问题,这将是一个令人沮丧和困难的境地。一个经验丰富的程序员会竭尽全力确保这种情况不会发生。
在这一点上,你可能想知道你的人工智能代码在哪里合适。好吧,这真的取决于你。运用你最好的判断力。它可能是控制器的一部分。就我个人而言,我希望它是一个完整的另一个控制器(chessAIController
)来实现AI算法,但将所有这些都包含在主控制器中同样容易。
关键是,实际上如何组织代码并不重要,只要它是以某种逻辑方式完成的。MVC之所以如此广泛,是因为这三个组件通常存在于大多数软件中,并且通常将它们分开是有意义的。注意,他们实际上并没有真正分开。。。控制器经常直接操纵视图和模型。诸如不允许视图操作任何内容之类的限制有助于代码保持清晰易懂。
当你在编程项目中没有结构或组织时,几乎不可能避免拥有庞大的例程来完成所有的事情,因为代码中实际上只有一个地方可以用来构建功能。这必然会产生大量错综复杂的意大利面条代码,无论多么高级的语言都无法将您从中拯救出来。这创建的代码非常糟糕,因为没有人能理解它,甚至你在编写它的两周后也无法理解它。