《算法竞赛入门经典》习题43 黑白棋(Othello, ACM、ICPC World Finals 1992, UVa220)

原题及翻译

Othello is a game played by two people on an 8 x 8 board, using disks that are white on one side and black on the other.
奥赛罗是一个由两个人在一个8×8的棋盘上玩的游戏,使用的是一面是白色,另一面是黑色的棋子。
One player places disks with the white side up and the other player places disks with the black side up.
一位玩家将棋子放在白色朝上的位置,另一位玩家将棋子放在黑面朝上的位置。
The players alternate placing one disk on an unoccupied space on the board.
玩家交替地将棋子放在游戏板上一个未占用的空间上。
In placing a disk, the player must bracket at least one of the other color disks.
在放置棋子时,玩家必须将另一个颜色的棋子中的至少一个放入棋盘。
Disks are bracketed if they are in a straight line horizontally, vertically, or diagonally, with a disk of the current player’s color at each end of the line.
如果棋子在水平、垂直或对角的直线上,则棋子加括号,并且在该直线的两端各有一个当前玩家颜色的棋子。
When a move is made, all the disks that were bracketed are changed to the color of the player making the move.
移动时,括号内的所有棋子都将更改为进行移动的玩家的颜色。
(It is possible that disks will be bracketed across more than one line in a single move.)
(棋子可能会在一次移动中跨多行被括号括住。)
在这里插入图片描述
在这里插入图片描述
On the left
在左边
Legal Moves for White
白色的合法移动
(2,3),(3,3),(3,5),(3,6)(6,2),(7,3),(7,4),(7,5)
On the right
在右边
Board Configuration after White Moves to (7,3)
白板棋子移到(7,3)
Write a program to read a series of Othello games.
编写一个程序来阅读一系列奥赛罗游戏。

Input

输入
The first line of the input is the number of games to be processed.
输入的第一行是要处理的游戏数。
Each game consists of a board configuration followed by a list of commands.
每个游戏由一个游戏板和一个命令列表组成。
The board configuration consists of 9 lines.
游戏板由9条线路组成。
The first 8 specify the current state of the board.
前8个指定板的当前状态。
Each of these 8 lines contains 8 characters, and each of these characters will be one of the following:
这8行中的每一行包含8个字符,每个字符都是以下字符之一:
‘-’ indicating an unoccupied square
“-”表示一个空位
‘B’ indicating a square occupied by a black disk
“B”表示黑色棋子所占的正方形
‘W’ indicating a square occupied by a white disk
“W”表示白色棋子所占的正方形
The ninth line is either a ‘B’ or a ‘W’ to indicate which is the current player.
第九行是“B”或“W”,表示当前玩家。
You may assume that the data is legally formatted.
您可以假定数据是合法格式化的。
Then a set of commands follows.
接下来是一组命令。
The commands are to list all possible moves for the current player, make a move, or quit the current game.
命令是列出当前玩家的所有可能移动或退出当前游戏。
There is one command per line with no blanks in the input
每行有一个命令,输入中没有空格。

Output

输出
The commands and the corresponding outputs are formatted as follows:
命令和相应输出的格式如下:
List all possible moves for the current player.
列出当前玩家的所有可能移动。
The command is an ‘L’ in the first column of the line.
命令是L行的第一列。
The program should go through the board and print all legal moves for the current player in the format (x, y) where x represents the row of the legal move and y represents its column.
程序应该浏览棋盘并以格式(x,y)打印当前玩家的所有合法移动,其中x代表合法移动的行,y代表其列。
These moves should be printed in row major order which means:
这些移动应按行主要顺序打印,这意味着:
1)all legal moves in row number i will be printed before any legal move in row number j if j is greater than i
1)行号i中的所有合法移动将在行号j中的任何合法移动之前打印,如果j大于i
2) if there is more than one legal move in row number i, the moves will be printed in ascending order based on column number.
2)如果第i行中有多个合法移动,移动将根据列号按升序打印。所有合法的行动都应该放在同一条线上。
All legal moves should be put on one line. If there is no legal move because it is impossible for the current player to bracket any pieces, the program should print the message ‘No legal move.’
如果由于当前玩家无法将任何棋子放在括号内而没有合法移动,程序应打印消息“无合法移动”。
Make a move.
行动起来。
The command is an ‘M’ in the first column of the line, followed by 2 digits in the second and third column of the line.
命令是行的第一列中的“m”,然后是行的第二列和第三列中的2位数字。
The digits are the row and the column of the space to place the piece of the current player’s color, unless the current player has no legal move.
除非当前玩家没有合法移动,否则数字是放置当前玩家颜色块的空间的行和列。
If the current player has no legal move, the current player is first changed to the other player and the move will be the move of the new current player.
如果当前玩家没有合法移动,则当前玩家首先被更改为其他玩家,移动将是新的当前玩家的移动。
You may assume that the move is then legal.
你可以假设这是合法的。
You should record the changes to the board, including adding the new piece and changing the color of all bracketed pieces.
您应该记录对游戏板的更改,包括添加新棋子和更改所有带括号的棋子的颜色。
At the end of the move, print the number of pieces of each color on the board in the format ‘Black - xx White - yy’ where xx is the number of black pieces on the board and yy is the number of white pieces on the board.
在移动结束时,以“黑色-XX白色-YY”格式打印板上每种颜色的棋子数,其中XX是板上的黑色棋子数,YY是板上的白色棋子数。
After a move, the current player will be changed to the player that did not move.
移动后,当前玩家将更改为未移动的玩家。
Quit the current game.
退出当前游戏。
The command will be a ‘Q’ in the first column of the line.
命令将是行的第一列中的“q”。
At this point, print the final board configuration using the same format as was used in the input.
此时,使用输入中使用的相同格式打印最终游戏板。
This terminates input for the current game.
这将终止当前游戏的输入。
You may assume that the commands will be syntactically correct.
您可以假定这些命令在语法上是正确的。
Put one blank line between output from separate games and no blank lines anywhere else in the output.
在不同游戏的输出之间放置一个空白行,而输出中的其他任何地方都没有空白行。

Sample Input

样例输入
在这里插入图片描述

Sample Output

样例输出
在这里插入图片描述

分析

1.做这道题最关键的是要读懂题,一开始看纯英文原题的时候,看到两张黑白棋图,想当然的就以为是五子棋,然后以五子棋的规则去读题,结果完全稀里糊涂的,然后用百度翻译去翻译,(百度翻译一翻译长文章就不行了,disk翻译成磁盘,其实应该翻译成棋子,player翻译成播放器,其实应该是玩家)。

2.要知道奥赛罗的规则:黑白双方轮流放棋子,每次必须让新放的棋子“夹住”至少一枚对方棋子,然后把所有被新放棋子“夹住”的对方棋子替换成己方棋子。一段连续(横、竖或者斜向)的同色棋子被“夹住”的条件是两端都是对方棋子(不能是空位)。

思路

1.首先,由于还是棋盘游戏类问题,需要一个二维数组来储存棋盘的所有结点信息(Checkerboard[8][8]),一个含有两个元素的数组来存储黑棋和白棋的个数(Number_of_pieces[2]),由于输入的命令可能是L和Q单个字母,也可能是M35这种字母数字组合型,所以要声明一个char类型的数组来存储命令command[8],还有两个数组(Runx[8],Runy[8]),如果只看这两个数组的话可能不知道什么意思,但是不得不说,能想出来这种方法的人真实牛牪犇,因为这两个数组我又把我原来的代码重新改了一遍。由于这些变量要在整个程序中使用,所以声明为全局变量。

#include <stdio.h>
#include <string.h>
char Checkerboard[8][8];
char Current_piece,command[8];
int Number_of_pieces[2]={0,0};
int Runx[8]={0,0,1,-1,1,-1,1,-1};
int Runy[8]={1,-1,0,0,1,-1,-1,1};
bool Flag, L_Refresh;

2.其次,分析题目需要什么重复使用的功能,把这些功能写成函数能减少很多思考量。
a、因为判断当前玩家是否有合法操作之后要决定是否换成下一位玩家,所以先写一个简单的交换函数。

void Exchange_player()
{//用于交换当前玩家。
 if(Current_piece=='W') Current_piece='B';
 if(Current_piece=='B') Current_piece='W';
}

b、题目中有要求打印出黑白棋子的个数,这可以作为一个单独的代码块:

        for (int i = 0; i < 8; ++i)
        {//统计黑白棋的个数
      	for (int j = 0; j < 8; ++j)
            {
                if (Checkerboard[i][j] == 'W') 
                Number_of_pieces[0]++;
                else if (Checkerboard[i][j] == 'B') 
                Number_of_pieces[1]++;
            }
        }

c、Q命令要求退出游戏并打印整个游戏板,这个比较简单,可以写一个简单的函数:

void Q()
{//打印整个游戏板
    for(int i=0;i<8;i++)
    {
     for(int j=0;j<8;j++)
     {
      printf("%c",Checkerboard[i][j]);
  }
  printf("\n");
 }
}

接下来就要写一些比较复杂的函数了,比如L命令和M命令,还有判断棋子是否合法以及翻转棋子的函数。

d、先来写主函数确定整体框架,主函数包括读入数据和对数据做处理,还有调用各种函数:

int main()
{
 int n;
    scanf("%d",&n);
    //读入游戏的次数
    while (n--)
    {
        for (int i = 0; i < 8; ++i) scanf("%s", Checkerboard[i]);
        //直接将一行的字符串读入到游戏板上
        scanf("\n%c", &Current_piece);
        //读入当前选手的棋子类型
        L_Refresh = 0;
        Number_of_pieces[0] = Number_of_pieces[1] = 0;
        for (int i = 0; i < 8; ++i)
        {//统计黑白棋的个数
      for (int j = 0; j < 8; ++j)
            {
                if (Checkerboard[i][j] == 'W') Number_of_pieces[0]++;
                else if (Checkerboard[i][j] == 'B') Number_of_pieces[1]++;
            }
        }
  while(scanf("%s",command))
  {
   if(command[0]=='L') L(true);
   else if(command[0]=='M') M();
   else if(command[0]=='Q'){Q();break;}
  }
        if (n) printf("\n");
    }
    return 0;
}

e、然后是L函数,L函数要打印所有的合法操作,这就需要一个判断棋子是否合法的函数了:
L():

void L(bool Whether)
{//打印所有的合法操作,按照从上到下,从左到右的顺序
    L_Refresh=1;Flag=0;
    for (int i=0;i<8;i++)
    {//遍历棋盘的所有位置
        for (int j=0;j<8;j++)
        {
            if (Checkerboard[i][j] != '-') continue;
            //跳过所有i,j位置上有棋子的点
            for (int x=0;x<8;x++)
            {//遍历i,j位置周围八个点
             if (judge(i,j,Runx[x],Runy[x],Whether))  break;
   }
        }
    }
    if (Whether)Flag?printf("\n"):printf("No legal move.\n");
}

这里就用到了我前边说的那个很神奇的想法,利用数组遍历i,j周围的八个 位置,比用八个if判断有省力很多(我之前就这么干过,很费劲)。
judge():

bool judge(int i, int j, int x, int y, bool Whether)
{//判断函数
    int a=i+1,b=j+1,flag=0;
    while((i+=x)<8&&(j+=y)<8&&i>=0&&j>=0)
    {//保证不越界的情况下
        if (Checkerboard[i][j] == '-') break;
        //如果当前位置没有棋子,则直接退出
        if (Checkerboard[i][j] ==Current_piece)
        {//在i,j位置放下当前玩家的棋子
            if (!flag) break;
            //当flg不为0的时候,退出
            if (Whether)
            {
                if (Flag) printf(" ");
                printf("(%d,%d)",a,b);
            }
            if (!Flag)Flag=true;
            return true;
        }
        flag++;
    }
    return false;
}

judge函数在M命令的时候同样也要用到,所以先写出来。
f、最后是M函数,因为M命令中有需要翻转棋子的内容,所以还要写一个翻转棋子的函数,注意翻转后要重新统计黑白棋子的个数。
M():

void M()
{
    char i=command[1]-49,j=command[2]-49;
    //将行和列转换成数字需要减去‘0’字符对应的序号
    if (!L_Refresh) L(0);
    if (!Flag) Exchange_player();
    for (int x=0;x<8;x++)
    {//八次循环将i,j位置周围八个棋子全部遍历并判断能否改变
     if (judge(i,j,Runx[x],Runy[x],0)) change(i,j,Runx[x],Runy[x]);
 }
    Current_piece=='W'?Number_of_pieces[0]+=1:Number_of_pieces[1]+=1;
    //累加黑白棋子的个数
    Checkerboard[i][j] =Current_piece;
    Exchange_player();
    //改变玩家
    L_Refresh = 0;
    printf("Black - %2d White - %2d\n", Number_of_pieces[1], Number_of_pieces[0]);
    //打印黑白棋子的数目
}

change():

void change(int i, int j, int x, int y)
{//将能翻转的棋子翻转并统计黑白棋个数
    int n = 0;
    while (Checkerboard[i+=x][j+=y]!=Current_piece)
    {
        Checkerboard[i][j]=Current_piece;n++;
        //将棋子翻转然后统计翻转棋子的个数
    }
    //判断棋子的类型然后累加黑白棋的个数
    if(Current_piece=='W')
    {
     Number_of_pieces[0]+=n;Number_of_pieces[1]-=n;
 }
 if(Current_piece=='B')
 {
  Number_of_pieces[0]-=n;Number_of_pieces[1]+=n;
 }
}

完整代码

#include <stdio.h>
#include <string.h>
char Checkerboard[8][8];
char Current_piece,command[8];
int Number_of_pieces[2]={0,0};
int Runx[8]={0,0,1,-1,1,-1,1,-1};
int Runy[8]={1,-1,0,0,1,-1,-1,1};
bool Flag, L_Refresh;
void Exchange_player()
{//用于交换当前玩家。
 if(Current_piece=='W') Current_piece='B';
 if(Current_piece=='B') Current_piece='W';
}
bool judge(int i, int j, int x, int y, bool Whether)
{//判断函数
    int a=i+1,b=j+1,flag=0;
    while((i+=x)<8&&(j+=y)<8&&i>=0&&j>=0)
    {//保证不越界的情况下
        if (Checkerboard[i][j] == '-') break;
        //如果当前位置没有棋子,则直接退出
        if (Checkerboard[i][j] ==Current_piece)
        {//在i,j位置放下当前玩家的棋子
            if (!flag) break;
            //当flg不为0的时候,退出
            if (Whether)
            {
                if (Flag) printf(" ");
                printf("(%d,%d)",a,b);
            }
            if (!Flag)Flag=true;
            return true;
        }
        flag++;
    }
    return false;
}
void change(int i, int j, int x, int y)
{//将能翻转的棋子翻转并统计黑白棋个数
    int n = 0;
    while (Checkerboard[i+=x][j+=y]!=Current_piece)
    {
        Checkerboard[i][j]=Current_piece;n++;
        //将棋子翻转然后统计翻转棋子的个数
    }
    //判断棋子的类型然后累加黑白棋的个数
    if(Current_piece=='W')
    {
     Number_of_pieces[0]+=n;Number_of_pieces[1]-=n;
 }
 if(Current_piece=='B')
 {
  Number_of_pieces[0]-=n;Number_of_pieces[1]+=n;
 }
}
void L(bool Whether)
{//打印所有的合法操作,按照从上到下,从左到右的顺序
    L_Refresh=1;Flag=0;
    for (int i=0;i<8;i++)
    {//遍历棋盘的所有位置
        for (int j=0;j<8;j++)
        {
            if (Checkerboard[i][j] != '-') continue;
            //跳过所有i,j位置上有棋子的点
            for (int x=0;x<8;x++)
            {//遍历i,j位置周围八个点
             if (judge(i,j,Runx[x],Runy[x],Whether))  break;
   }
        }
    }
    if (Whether)Flag?printf("\n"):printf("No legal move.\n");
}
void M()
{
    char i=command[1]-49,j=command[2]-49;
    //将行和列转换成数字需要减去‘0’字符对应的序号
    if (!L_Refresh) L(0);
    if (!Flag) Exchange_player();
    for (int x=0;x<8;x++)
    {//八次循环将i,j位置周围八个棋子全部遍历并判断能否改变
     if (judge(i,j,Runx[x],Runy[x],0)) change(i,j,Runx[x],Runy[x]);
 }
    Current_piece=='W'?Number_of_pieces[0]+=1:Number_of_pieces[1]+=1;
    //累加黑白棋子的个数
    Checkerboard[i][j] =Current_piece;
    Exchange_player();
    //改变玩家
    L_Refresh = 0;
    printf("Black - %2d White - %2d\n", Number_of_pieces[1], Number_of_pieces[0]);
    //打印黑白棋子的数目
}
void Q()
{//打印整个游戏板
    for(int i=0;i<8;i++)
    {
     for(int j=0;j<8;j++)
     {
      printf("%c",Checkerboard[i][j]);
  }
  printf("\n");
 }
}
int main()
{
 int n;
    scanf("%d",&n);
    //读入游戏的次数
    while (n--)
    {
        for (int i = 0; i < 8; ++i) scanf("%s", Checkerboard[i]);
        //直接将一行的字符串读入到游戏板上
        scanf("\n%c", &Current_piece);
        //读入当前选手的棋子类型
        L_Refresh = 0;
        Number_of_pieces[0] = Number_of_pieces[1] = 0;
        for (int i = 0; i < 8; ++i)
        {//统计黑白棋的个数
      for (int j = 0; j < 8; ++j)
            {
                if (Checkerboard[i][j] == 'W') Number_of_pieces[0]++;
                else if (Checkerboard[i][j] == 'B') Number_of_pieces[1]++;
            }
        }
  while(scanf("%s",command))
  {
   if(command[0]=='L') L(true);
   else if(command[0]=='M') M();
   else if(command[0]=='Q'){Q();break;}
  }
        if (n) printf("\n");
        //换行
    }
    return 0;
}

结语

“努力不一定有收获,但是不努力一定没有收获!”

每天磕一道ACM 除夕打卡

原文地址:https://www.cnblogs.com/AlexKing007/p/12339573.html