C ++中的国际象棋控制台游戏(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/chess-console-game-in-cplusplus-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 15 分钟阅读 - 7218 个词 阅读量 0C ++中的国际象棋控制台游戏(译文)
原文地址:https://www.codeproject.com/Articles/1214018/Chess-Console-Game-in-Cplusplus
原文作者:Jerome Vonk
译文由本站 robot-v1.0 翻译
前言
Simple chess game, written in C++, that runs in a console. Made for didatic purposes and fun :)
简单的国际象棋游戏,用C ++编写,可在控制台中运行.出于教学目的和乐趣而制作:)
- 下载ChessConsole v1.1-37.1 KB(Download ChessConsole v1.1 - 37.1 KB)
如果你想(If you want to) 解压缩后验证可执行文件的哈希(verify the hash for the executable after unzipped) ,SHA-1为:(, the SHA-1 is:)
7B69131E72320DF6DEE49ED6E1CCBD6ACE95082F
.(.) (如果没有VS 2015可再发行组件,((If don’t have the VS 2015 Redistributables,) 请在这里找到(please find it here) ).().)
源代码(Source Code)
放心(Feel free) 在GitHub上分叉项目(to fork the project on GitHub) .(.)
屏幕截图(Screenshot)
介绍(Introduction)
互联网上有很多国际象棋游戏的实现方式,其中大多数功能都比该功能丰富.尽管如此,开发更简单,轻巧的软件,特别是针对教义用途的软件,也没有缺点.(There are lots of implementations of Chess' games available on the internet, most of them richer in features than this one. Nevertheless, there’s no demerit on developing a simpler, lightweight piece of software, specially aiming for didatic purposes.) 这个游戏是什么(或试图成为):(What this game is (or tries to be):)
-
轻巧.该应用程序的1.0版大小为155 KB.(Lightweight. The size of version 1.0 of the application is 155 KB.)
-
已实施(Implemented) 完全在控制台上(fully on console) 这个游戏是什么(What this game)没有/没有(is not/does not have):(:)
-
没有一个(Does not have a) 图形用户界面(GUI)
-
没有人工智能(AI)(Does not have artificial intelligence (AI))
背景(Background)
该游戏在控制台中运行,即,这意味着用户无法使用GUI.所有输入均来自键盘,为此,它使用(This game runs in a console, i.e., that means no GUI is available to the user. All the input is taken from the keyboard, and for that, it uses the) 坐标符号(Coordinate Notation) .(.) 白色部分用大写字母表示,黑色部分用小写字母表示.他们全都以名字的首字母表示,唯一的例外是骑士,骑士以(The white pieces are represented by capital letters and the black pieces are represented in lowercase letters. They are all represented by the first letter of their names, the only exception being the Knight, which is represented by an)ñ(N),离开(, leaving the)ķ(K)为国王):(for the king):) P(P)芒(awn) [R(R)ook(ook) ķ(K)ñ(n)权(ight) 乙(B)网店(ishop) 问(Q)女王(ueen) ķ(K)ing(ing) 我将尝试解释我在开发游戏时使用的一些概念,如果有任何不清楚的地方或者我错过了重点,请在讨论中让我知道.(I will try to explain some of the concepts I used when developing the game, if anything is not clear or if I missed an important point, please let me know in the discussion.)
画板(Drawing the Board)
我们可以使用ASCII字符(We can use the ASCII characters) 0xDB
和(and) 0xFF
分别绘制白色和黑色单元格.(to draw white and black cells, respectively.)
#define WHITE_SQUARE 0xDB
#define BLACK_SQUARE 0xFF
首先,我们必须决定我们要平方多大.说到高度,黑板上的一个正方形应该和一个字符一样大吗?还是两三个?(First, we have to decide how big we want the squares do be. Speaking about the height, should one square on the board be as big as one single character? Or maybe two or three?) 下图说明了我们具有的选项:(The following picture illustrates the options we have:)
我最终选择了第三个选项,这意味着一个正方形的高度等于三个字符.现在,我们面临另一个问题.提到的字符(0xDB和0xFF)不平方;它们实际上是矩形,一侧的面积是另一侧的两倍.这意味着,为了形成一个正方形,我们必须连续使用六个字符.(I ended up choosing the third option, which means the height of one square equals to three characters. Now, we face another problem. The mentioned characters (0xDB and 0xFF) are not squared; they are actually rectangular with one side being twice as big as the other. This means that, in order to form a square, we have to use six characters in a row.) 这些是画板的功能:(These are the functions that draw the board:)
void printBoard(Game& game)
{
cout << " A B C D E F G H\n\n";
for (int iLine = 7; iLine >= 0; iLine--)
{
if ( iLine%2 == 0)
{
// Line starting with BLACK
printLine(iLine, BLACK_SQUARE, WHITE_SQUARE, game);
}
else
{
// Line starting with WHITE
printLine(iLine, WHITE_SQUARE, BLACK_SQUARE, game);
}
}
}
void printLine(int iLine, int iColor1, int iColor2, Game& game)
{
// Define the CELL variable here.
// It represents how many horizontal characters will form one squarite
// The number of vertical characters will be CELL/2
// You can change it to alter the size of the board
// (an odd number will make the squares look rectangular)
int CELL = 6;
// Since the width of the characters BLACK and WHITE is half of the height,
// we need to use two characters in a row.
// So if we have CELL characters, we must have CELL/2 sublines
for (int subLine = 0; subLine < CELL/2; subLine++)
{
// A sub-line is consisted of 8 cells, but we can group it
// in 4 iPairs of black&white
for (int iPair = 0; iPair < 4; iPair++)
{
// First cell of the pair
for (int subColumn = 0; subColumn < CELL; subColumn++)
{
// The piece should be in the "middle" of the cell
// For 3 sub-lines, in sub-line 1
// For 6 sub-columns, sub-column 3
if ( subLine == 1 && subColumn == 3)
{
cout << char(game.getPieceAtPosition(iLine, iPair*2) != 0x20 ?
game.getPieceAtPosition(iLine, iPair*2) : iColor1);
}
else
{
cout << char(iColor1);
}
}
// Second cell of the pair
for (int subColumn = 0; subColumn < CELL; subColumn++)
{
// The piece should be in the "middle" of the cell
// For 3 sub-lines, in sub-line 1
// For 6 sub-columns, sub-column 3
if ( subLine == 1 && subColumn == 3)
{
cout << char(game.getPieceAtPosition(iLine,iPair*2+1) != 0x20 ?
game.getPieceAtPosition(iLine,iPair*2+1) : iColor2);
}
else
{
cout << char(iColor2);
}
}
}
}
}
守则的结构(Structure of the Code)
代码(The code) 包括三个(consists of three)**.cpp(.cpp)**文件:(files:)
- main.cpp(main.cpp):应用程序的入口点.提示用户采取行动((: Entry-point of the application. Prompts the user for an action ()新游戏,移动,撤消,保存,加载,退出(new game, move, undo, save, load, quit)),并根据要执行的操作提示输入更多信息,并从其他文件中调用函数.() and, depending on the action to be performed, prompts for more information and call the functions from the other files.)
- 国际象棋(chess.cpp):包括两个类.第一个被命名(: consists of two classes. The first one is named)
Chess
并包含(and contains)enum
s(s,)struct
和简单的功能来描述棋子,颜色和棋盘.第二个叫做(s and simple functions to describe chess pieces, colors and the board. The second one is called)Game
,它继承自(, it inherits from)Chess
.它存储单个游戏拥有的所有信息,例如棋盘中每个棋子的位置,进行的动作列表,捕获的棋子列表.它还包含确定国王是否在检查中,是否允许铸造,是否占据正方形的功能,这些都是验证移动是否有效的一切必要条件.(. It stores all the information that a single game has, like the position of every piece in the board, list of moves made, list of pieces captured. It also contained functions to determine if the king is in check, if castling is allowed, if a square is occupied, everything that is necessary to verify if a move is valid or not.) - user_interface.cpp(user_interface.cpp):基本上包括将信息打印到控制台的功能,例如打印面板,最后一步,菜单,给用户的消息等.(: Basically consists of functions printing information to the console, like printing the board, last moves, menu, messages for the user, etc.) 我对应用程序的设计方式是,如果要改进用户界面(例如,如果有人决定派发此代码并开发GUI),则不应在其中进行任何更改.(I have designed the application in a way that, if the user interface is to be improved (for example, if someone decides to fork this code and develop a GUI), no changes should be made in the)**国际象棋(chess.cpp)**文件.所需的更改基本上是取代(file. Needed changes would be basically to replace the)**user_interface.cpp(user_interface.cpp)**文件带有新界面,并在(file with a new interface and replace the calls to that interface in the)**main.cpp(main.cpp)**文件.(file.)
验证动作(Validating a Move)
bool isMoveValid(Chess::Position present, Chess::Position future,
Chess::EnPassant* S_enPassant, Chess::Castling* S_castling)
[看到([See) main.cpp(main.cpp) ,第19行](, line 19]) 用户输入了移动一块的命令后,必须检查几件事以验证它是否有效.(After the user has entered the command to move a piece, several things must be checked to verify if it is a valid move.)
- 是否允许所需的零件朝那个方向移动?(Is the desired piece allowed to move in that direction?)在这里,我们必须创建一个带有所有类型盒子的开关.骑士,新手,主教和皇后并不那么复杂,因为它们总是以相同的方式运动.另一方面,典当可以垂直移动,但可以对角移动以捕获片段.同样,他们可以选择前进两个正方形,但前提是这是第一步.甚至还有"(Here, we have to create a switch with cases for all types of pieces. Knights, rooks, bishops and queen are less complicated because they always move in the same fashion. Pawns, in the other hand, move vertically but are allowed to move diagonally to capture a piece. Also, they have the option of advancing two squares, but only if it is its first move. And there is even the “)传人(en passant)“移动,当棋子向前移动并捕获一块时.国王可以向每个方向移动一个正方形,但是当应用掷角时,它可以移动两个正方形(但前提是这是国王和车手的第一步)参与移动).(” move, when the pawn moves forward and yet captures a piece. The King can move one square to every direction, but when castling is applied, it can move two squares (but only if it’s the first move for both the king and the rook involved in the move).)
- 目的地广场上还有另一块相同颜色的东西吗?(Is there another piece of the same color on the destination square?)如果为正,则此举必须无效.如果有一块,但来自其他颜色,则将捕获该块.(If positive, then the move must be invalidated. If there is a piece, but from the other color, then this piece will be captured.)
- 这一举动是否会使国王受到控制?(Would this move put the king in check?)无论国王是否已经处于制止状态,我们都需要检查该举动之后国王是否会受到任何对手的直接攻击.(Either if the king was already in check or not, we need to check if, after that move, the king would be under immediate attack by any opponent piece.)
储存移动和捕获的棋子(Storing the Moves and Captured Pieces)
我们正在利用一些(We’re taking advantage of some) C ++提供的容器(containers provided by C++) 存储游戏信息.但是首先,我创建了一个简单的结构,可以存储一个白色和一个黑色的动作.每次移动都是一次曲调,其中包含要移动的棋子的位置,后跟一个破折号,以及目标方块,例如E2-E4.(to store the game information. But first, I created a simple structure that stores one white and one black move. Each move is a tring that contains the position of the piece to be moved, followed by a dash, and the destination square, e.g., E2-E4.)
struct Round
{
string white_move;
string black_move;
};
一种(A) 双端队列(double-ended queue) 是我选择存储回合的数据结构.这是一种通用的结构,可以从队列的开始和结尾处都接受插入和删除元素.声明和使用示例如下:(was the data structure I chose to store the Rounds. It’s a versatile structure, which accepts inserting and deleting elements from both the beginning and the end of the queue. Declaration and examples of use are as follows:)
std::deque<Round> rounds;
// How many rounds are stored?
rounds.size()
// Access a round
rounds[i].white_move.c_str()
// Clear the container
rounds.clear();
// Insert or remove elements
rounds.pop_back();
rounds.push_back(round);
对于捕获的片段,它们存储在向量中:(For the captured pieces, these are stored in vectors:)
// Save the captured pieces
std::vector<char> white_captured;
std::vector<char> black_captured;
而且它们不能像这样在屏幕上打印:(And they can’t be printed on the screen like this:)
cout << "WHITE captured: ";
for (unsigned i = 0; i < game.white_captured.size(); i++)
{
cout << char(game.white_captured[i]) << " ";
}
cout << "black captured: ";
for (unsigned i = 0; i < game.black_captured.size(); i++)
{
cout << char(game.black_captured[i]) << " ";
}
结果如下:(This is the result:)
玩游戏(Playing the Game)
开始新游戏(Starting a New Game)
启动应用程序,然后按N,然后按Enter,开始新游戏.显示板子,它变成白色.(Start the app and press N, followed by ENTER, to start a new game. The board is shown and it’s WHITE turn.)
采取行动(Make a Move)
键入M进行移动.(Type M to make a move.) 系统将提示您选择要移动的片段.通过输入两个字符(大写或小写字母将给出相同的结果)来做到这一点,首先描述该列,然后描述您当前要移动的片段所在的行.例如,国王面前的白色棋子是(You will be prompted to choose a piece to be moved. Do it by entering two characters (uppercase or lowercase will give the same results) describing first the column, then the row where the piece you want to be moved currently is. For example, the white pawn in front of the king is the)E2(E2)广场.(square.) 接下来,系统将提示您输入目标正方形.最常见的动作之一是将棋子从(Next, you’ll be prompted for the destination square. One of the most common moves is moving the pawn from)E2(E2)至(to)E4(E4).(.) 如果移动无效,将警告您.(You will be warned if the move is invalid.)
撤消移动(Undo a Move)
只需键入U,然后按Enter键即可撤消最后一步.只能撤消最后一步.(Simply type U followed by ENTER to undo the last move. It is possible to undo only the very last move.)
将军(Checkmate)
bool Game::isCheckMate()
[看到([See) 国际象棋(chess.cpp) ,第1394行.](, line 1394.]) 每一步之后,我们必须检查是否发生了将死.这些是要遵循的步骤:(After every move, we must check if a checkmate has taken place. These are the steps to be followed:)
- 国王在检查吗?(Is the king in check?)如果没有,则无需进一步检查.(If not, no need to check any further.)
- 国王可以搬到另一个广场吗?(Can the king move to another square?)如果国王可以搬到另一个广场并且不再受到攻击,那它就不是将军.(If the king can move to another square and not be under attack anymore, than it’s not checkmate.)
- 可以将攻击者带走还是将另一部分挡住?(Can the attacker be taken or another piece get in the way?)如果攻击者可以被带走,那么它不是将军.如果不能做到,那么在进攻者和国王之间还有另一块进入中间的可能性.(If the attacker can be taken, then it’s not a checkmate. If it can’t, there’s still the possibility for another piece to get in the middle of the way between the attacker and the king.) 如果对第二个和第三个问题的回答为"否”,则说明将死,比赛结束了!(If the answers to questions two and three are NO, then it’s a checkmate and the game is over!)
保存/加载游戏(Saving / Loading a Game)
如果您想稍后完成游戏,保存游戏会很有用,但它也非常有用(Save a game is useful if you want to finish it later, but also it is an incredibly useful)**调试(debugging)**工具.如果我要测试的是(tool. It was tedious to begin every time with all the pieces in their original positions if I’m testing a)将军(checkmate), 一种(, a)**ling(castling)**甚至是"(or even an ‘)**传人(en passant)**移动.能够将游戏保存在特定位置,更正代码并从同一角度再次进行测试被证明是一种非凡的工具.(’ move. Being able to save the game on a particular position, correcting the code and testing again from the same point proved to be an extraordinary tool.) 怎么做的?当用户在菜单上键入" S"以保存游戏时,系统会提示他输入名称.该应用程序将创建(或覆盖)名为"(How was it done? When the user types ‘S’ on the menu to save the game, he’s prompted for a name. The application will create (or override) a file called ‘)**name_entered.dat(name_entered.dat)**与可执行文件位于同一目录中.您可以使用打开文件(’ on the same directory as the executable. You can open the file with) 记事本++(notepad++) 好奇的话看看.文件的前几行可能如下所示:(and have a look, if you are curious. The first couple lines of the file could look like this:)
[Chess console] Saved at: Fri Feb 9 00:07:43 2018
E2-E4 | C7-C5
C2-C3 | D7-D5
包括时间和日期用于调试目的.(Time and date were included for debugging purposes.) 在标题行上方打印所有动作,每行一圈,始终从白色播放器开始.因此,在那种情况下,怀特开始将E2上的棋子推进到E4,而布莱克将棋子从C7推进到C5. (如果您想知道这是否是Black的好举动,那么,它是由(Above the header lines, all moves are printed, one round per line, always starting with the white player. So, in that case, White started advancing the pawn on E2 to E4 and Black advanced the pawn from C7 to C5. (If you’re wondering if this is a good move from Black, well, it was made by) 加里`卡斯帕罗夫对阵深蓝(Gary Kasparov against Deep Blue) ).().) 由于所有移动都存储在(Since all the moves are stored in a) 双端队列(double-ended queue) ,很容易将该信息打印到文件中,如下所示:(, it is easy to print that information to a file, as you can see below:)
void saveGame(void)
{
string file_name;
cout << "Type file name to be saved (no extension): ";
getline(cin, file_name);
file_name += ".dat";
std::ofstream ofs(file_name);
if (ofs.is_open())
{
// Write the date and time of save operation
auto time_now = std::chrono::system_clock::now();
std::time_t end_time = std::chrono::system_clock::to_time_t(time_now);
ofs << "[Chess console] Saved at: " << std::ctime(&end_time);
// Write the moves
for (unsigned i = 0; i < current_game->moves.size(); i++)
{
ofs << current_game->rounds[i].white_move.c_str() <<
" | " << current_game->moves[i].black_move.c_str() << "\n";
}
ofs.close();
createNextMessage("Game saved as " + file_name + "\n");
}
else
{
cout << "Error creating file! Save failed\n";
}
return;
}
当用户想要加载保存的游戏时,应用程序会提示用户输入文件名(同样,不输入(When the user wants to load a saved game, the application prompts the user for the name of the file (again, without the)**.dat(.dat)**延期).之后,请执行以下步骤:首先,检查文件是否存在并打开.跳过第一行(标题)后,应读取每一行,分为白色和黑色动作,并且必须验证每个动作的有效性.(extension). After that, the steps are: first, check if the file exists and open. After skipping the first line (header), every line should be read, split into White and Black moves, and every move must be verified for validity.) 那真的有必要吗?好吧,我们肯定会在保存之前验证所有移动,但是我们不能保证文件没有被篡改,因此最好还是安全一点并再次进行验证.(Is that really necessary? Well, we sure validated all the moves before saving, but we cannot guarantee that the file hasn’t been tampered with, so it’s better to be on the safe side and verify again.) 您可以在(You can find on the) github项目页面(github project page) 一堆保存的游戏,帮助我测试和调试了游戏.要特别注意(a bunch of saved games that helped me test and debug the game. Pay special attention to the) 卡斯帕罗夫VS深蓝游戏1.day(KasparovVSdeepblue_game_1.dat) .重新创建第一场比赛之间的每一步对我来说都很有趣(. It was a lot of fun for me to recreate every move from Game 1 between) <深蓝与卡斯帕罗夫>,1996年(Deep Blue versus Kasparov, 1996) .这是一项重要的游戏,因为这是在正常的国际象棋比赛条件和经典时间控制下,由下象棋的计算机与在位的世界冠军对决的第一场比赛.(. It is an important game because it was the first game to be won by a chess-playing computer against a reigning world champion under normal chess tournament conditions and classical time controls.)
虫子(Bugs)
此应用程序当然不是没有错误的.如果您遇到错误,崩溃,游戏中的无效情况等,(This application is certainly not bug-free. If you encounter an error, a crash, an invalid situation in the game, etc.,)请发电子邮件给我(please email me)屏幕截图(a screenshot)或者(甚至更好)保存游戏并将其发送给我(or (even better) save the game and send me the)**.dat(.dat)**文件.非常感谢您的帮助!(file. Your help is much appreciated!)
改进/未来步骤(Improvements / Future Steps)
Unicode中的国际象棋符号(Chess Symbols in Unicode)
并非所有人都知道(Not everyone knows that there are) Unicode中的国际象棋符号(chess symbols in Unicode) .然而,将它们输出到控制台并不是那么简单.有两个警告:(. Nevertheless, it’s not that straightforward to output them to a console. Two caveats are:)
- 控制台必须(The console must) 以Unicode输出文本(output text in Unicode)
- 控制台使用的字体必须(The font used by the console must) 实施棋子的字形(implement the glyphs for the chess pieces) (并非所有字体都正确)((not true for all fonts)) 使用以下源代码和ConEmu终端仿真器,我设法用Unicode打印了这些代码.(With the following source code and ConEmu terminal emulator, I managed to print the pieces in Unicode.)
void printChessPiecesUnicode()
{
_setmode(_fileno(stdout), _O_WTEXT);
std::wcout << L'\u2654' << ' ' << L'\u2655' << ' ' << L'\u2656' << ' '
<< L'\u2657' << ' ' << L'\u2658' << ' ' << L'\u2659' << endl;
std::wcout << L'\u265A' << ' ' << L'\u265B' << ' ' << L'\u265C' << ' '
<< L'\u265D' << ' ' << L'\u265E' << ' ' << L'\u265F' << endl;
}
结果如下:(This is the result:)
但是,在考虑了一段时间之后,我决定只有少数用户能够正确显示这些作品,因此这是不值得的.不过,我很好奇(However, after pondering about this matter for a while, I decided only a few users would be able to display the pieces correctly, so it was not worth the effort. Nevertheless, I’m curious to see)如果有人读这个(if anyone reading this)会感到挑战(will feel challenged)用象棋字形画板和棋子.(to draw the board and the pieces with chess glyphs.)
图形用户界面(Graphical User Interface)
这款游戏可能会受益的明显改进之一就是漂亮的GUI.这里有很多选择:(One of the obvious improvements this game could benefit is a beautiful GUI. Plenty of options here:) wxWidgets(wxWidgets) ,(,) Windows表格(Windows Forms) 和(and) Windows Presentation Foundation(WPF)(Windows Presentation Foundation (WPF)) ,仅举几例.(, to name a few.) 由于国际象棋游戏的所有逻辑都在以下两个类中实现(Since all the logic of the chess game is implemented in two classes in the)**国际象棋(Chess.cpp)**文件,可以将其内置到DLL中,其他编程语言可以访问该DLL.(file, it can be built into a DLL which can be accessed by other programming languages.) 如果您不得不解决我建议的任何改进,欢迎您(If you feel compelled to address any of the improvements I suggested, you’re welcome) 在GitHub上分叉项目(to fork the project on GitHub) 让我们进一步讨论吧!(and let’s discuss it further!)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C++ cross-platform 新闻 翻译