今天来一同做个小项目:五子棋;
       不用说,大家都耳熟能详(还是要讲一下玩法的),双方分别使用黑白两色的棋子,轮流下在棋盘竖线与横线的交叉点上,先形成五子连线者获胜。
那么今天我们就来设计一个小棋盘,来实现一个简单的五子棋游戏。
首先,先来写一下项目流程(用写代码的方式比较好看,嗯):
项目流程{
    1.立项与项目开发计划{
        用C++实现:五子棋{
            我们来用二维数组去表示的棋盘;
        }
    }
    2.可行性分析{
        因为今天要这个基本的小旗,所以会用到数组,自定义函数,对齐函数setw(),别忘写相应的头文件#include<iomanip>等;
        同时要记着定义变量时的声明{
            写清楚初始值、数组作用还有自定义函数的作用;
            函数怎样去实现功能;
        }
    }
    3.项目设计{
        我们等会写个代码如何实现(自定义函数){
            先去大致结构实现,再测试去揪一些细节;
        }我们就用正常的输出界面就行(可视化我也不会‘-’);
    }
    4.架构搭建{
        #include<iostream>
        #include<iomanip>
        #include<cstring> //好像也用不到呀6
        int 数组();
        int 初始值 = xx;
        函数声明;
        int main(){
            xxxxxxxx;
            xxxxxxxx;
            return 0;
        }
        类型 函数名(){
        }
        类型 函数名(){
        }
        类型 函数名(){
        }
        类型 函数名(){
        }
        类型 函数名(){
        }
        and so on........;
    }
    5.开发{
       C++进度:函数、数组
           定义一个全局变量,比如:初始值(不按要求就退出或回到初始值);
           不要重复命名,要注意;
           先完成大致,再揪其他;
    }
    6.测试{
        这的话,玩就行了;
    }
}
好了,看完项目流程,我们就开始干吧‘ - ’:
       
我们先来定义一个数组名叫map的19*19的小棋盘,用二维数组方便一些
int map[19][19];
再定义个用于记数的全局变量:
int flag;
我用‘0’表示空地,‘1’表示黑棋,用‘2’来表示白棋,所以,可以用 flag%2,来进行判断,当 flag&2 == 0时,(能整除),当前为黑棋的回合,那么 flag%2 == 1时为白棋的回合,况且再每次落子后,flag++;
接着来写最重要的自定义函数功能:
先定义一个无参数类型的 inin() 函数,我让它的功能表现在:初始化游戏数据
        将棋盘的值初始化为0
        当前回合设为黑棋(flag设为0)
    参数: void
    返回值: void
void init(){
   int i,j;
   for(i=0;i<19;++i){
     for(j=0;j<19;++j){
       map[i][j] = 0;
      }
   }
   flag = 0;
}
先定义 i,j 分别用来控制循环行号与列号,因为定义数组是从0到18(一般数组索引从0开始),所以在循环中将 map[i][j] 设置为0,来表示初始为空,再将 flag 赋予0值。

再定义一个int类型的 isWin() 函数(这还是个难点,因为要控制方向!),让它的功能表现在:根据传入的坐标(map对应位置)和flag值 判断落点后是否获胜
    参数:
        x: 当前回合落子的x坐标
        y: 当前回合落子的y坐标
    返回值:
        0表示没有获胜
        1表示黑子胜利
        2表示白子胜利
int isWin(int x,int y){
    int flag_2 = (flag%2 == 0) ? 1:2;
    int count = 1;
    int i,j;
    for(i=y+1;i<19 && map[x][i] == flag_2;++i){
        count++;
    }
    for(i=y-1;i>=0 && flag_2 == map[x][i];--i){
       count++;
    }
    if(count>5) return flag_2;
    count = 1;
    for(i=x+1;i<19 && map[i][y] == flag_2;++i){
        count++;
    }
    for(i=x-1;i>=0 && map[i][y] == flag_2;--i){
       count++;
    }
    if(count>5) return flag_2;
    count = 1;
    for(i=x+1,j=y+1;i<19 && j<19 && map[i][j] == flag_2;++i,++j){
       count++;
    }
    for(i=x-1,j=y-1;i>=0 && j>=0 && map[i][j] == flag_2;--i,--j){
       count++;
    }
    count = 1;
    for(i=x+1,j=y-1;i<19 && j>=0 && map[i][j] == flag_2;++i,--j){
       count++;
    }
    for(i=x-1,j=y+1;i>=0 && j<19 && map[i][j] == flag_2;--i,++j){
        count++;
    }
    if(count>5) cout<<flag_2;
    return 0;
}
对于方向来讲,可以从水平、竖直、主对角线和副对角线来判断:
       先说水平方向:从当前列 y 向右检查,统计连续相同棋子的数量;从当前列 y 向左检查,统计连续相同棋子的数量;如果棋子连续的数量超过5,则返回 flag_2 表示当前棋子颜色获胜。
       再来看竖直方向:从当前行 x 向下检查,统计连续相同棋子的数量;从当前行 x 向上检查,统计连续相同棋子的数量;同样如果棋连续的数量超过5,则返回 flag_2 表示当前棋子颜色获胜。
       那么,对于主对角线方向:从当前位置 (x, y) 向右上方检查,统计连续相同棋子的数量;从当前位置 (x, y) 向左下方检查,统计连续相同棋子的数量;当棋子连续的数量超过5,则返回 flag_2 表示当前棋子颜色获胜。
       同样,对于副对角线方向:、从当前位置 (x, y) 向右下方检查,统计连续相同棋子的数量;从当前位置 (x, y) 向左上方检查,统计连续相同棋子的数量;当棋子连续的数量超过5,则返回 flag_2 表示当前棋子颜色获胜。

接着定义一个int 类型的 playerMove() 函数,让它的功能表现在:在指定位置落子
        如果map[x][y]是空地 则修改map[x][y]的值:改为相应颜色(flag对应颜色)        
            否则不操作
    参数:
        x: 当前回合落子的x坐标
        y: 当前回合落子的y坐标
    返回值:
        0表示落子失败 (棋盘已经有子)
        1表示落子成功
int playerMove(int x, int y){
  if(x<0 || x>=19 || y<0 || y>=19) return 0; //在指定位置落子
  if(map[x][y] != 0) return 0;
  // if(flag%2 == 0) map[x][y] = 1;
  // else map[x][y] = 2;
  int flag_2 = (flag%2 == 0) ? 1:2;
  map[x][y] = flag_2;
  flag++;
  return 1;    //1表示落子成功
}
对于这个函数来说,
落子是在棋盘的指定位置放置当前玩家的棋子。返回 0 表示落子失败(位置非法或已有棋子),而返回 1 表示落子成功。
    if(x<0 || x>=19 || y<0 || y>=19) return 0; 
检查 (x, y) 是否在棋盘范围内,如果不在范围内,返回 0 表示落子失败。
  if(map[x][y] != 0) return 0;
检查 (x, y) 位置是否已经有棋子,如果已经有棋子,返回 0 表示落子失败。
  int flag_2 = (flag%2 == 0) ? 1:2;
根据 flag 的值确定当前落子的棋子颜色,flag_2 用于表示当前棋子的颜色:1 表示黑棋,2 表示白棋。
  map[x][y] = flag_2;   //在 (x, y) 位置放置当前玩家的棋子。
    flag++;    //增加 flag 的值,表示回合数增加,准备进入下一回合。
随后再定义一个写有主菜单的函数 menuView() :功能显而易见,展示选项, 玩家可以在这里选择进入游戏, 进入设置或退出游戏
        while(1){
            1. 展示选项
            2. 用户输入
            3. 根据输入进行对应处理
                进入游戏: 调用游戏界面函数gameView();
                进入设置: 打印敬请期待... 重新循环
                退出游戏: 调用exit(0);
        }
    参数: void
    返回值: void
void menuView(){     
      while(1){
     cout<<"=======五子棋======="<<endl;
     cout<<"1.进入游戏"<<endl;
     cout<<"2.设置"<<endl;
     cout<<"3.退出游戏"<<endl;
     cout<<"===================="<<endl;
     cout<<"请选择:"<<endl;
      int choice;
      cin>>choice;
      switch(choice){
     case 1: 
        gameView();
         break;
     case 2:
        cout<<"打印敬请期待..."<<endl; 
        continue;
     case 3:
        exit(0);
     default:
           cout<<"请重选:"<<endl;
        break;
     }
   }
}
这个,没啥好说,就是 switch case 语句要写在循环里,不然会出bug。

这里定义的是gameView_ShowMap()函数,它的功能吗,就是根据map数组 打印游戏棋盘
void gameView_ShowMap(){
   int i,j;
   // cout<<" ";
   for(j=0;j<19;++j) 
   //  cout<<j%10<<" "; //对齐
       cout<<setw(2)<<j<<" ";
   cout<<endl;
   for(i=0;i<19;++i){
      //cout<<i%10<<" ";    //只显示各位
     cout<<setw(2)<<i<<" ";
     for(j=0;j<19;++j){
      // if(map[i][j] == 0) cout<<0<<" "; //空
      // else if(map[i][j] == 1) cout<<1<<" ";  //黑
      // else if(map[i][j] == 2) cout<<2<<" ";  //白
        cout<<setw(2)<<map[i][j]<<" ";
     }
     cout<<endl;
   }
}
要点就是行,列对齐,所以我在循环里加个setw(2),(用这个时别忘加个头文件:#include<iomanip>,不然又会报错)
    cout<<setw(2)<<j<<" ";
来保证列对齐,行对齐也这样写
  cout<<setw(2)<<i<<" ";
打注释的代码是我之前写的,但功能不完善的,有些在之前写的函数中用法重复了。

接着定义的是 winView() 函数,其功能是: 根据flag的值,打印游戏胜利界面,用户可以按任意键回到主菜单
void winView(){
    if(flag%2 == 0) cout<<"黑棋赢了"<<endl;
    else cout<<"白棋赢了"<<endl;
    cout<<"用户按任意键回到主菜单"<<endl;
    cin.get();
    menuView();
}
    cin.get();
get()用于任意输入,但它不会自动回到menuView,所以我要在调用一次 menuView,来进行手动返回。

最后一个函数 gameView用于实现游戏界面整合:
        初始化游戏数据(调用函数init())
        while(1){
            打印游戏界面(调用函数gameView_ShowMap())
            接收玩家坐标输入
            落子(调用落子函数playerMove())
                (如果落子失败 重新开始循环)
            判断游戏是否胜利(调用胜利判断函数isWin())
                (如果游戏胜利 调用胜利界面函数 然后结束当前界面)
            切换玩家(修改flag值)
        }
    参数: void
    返回值: void
void gameView(){
   init();
   // while(1){
   //     gameView_ShowMap();
   //     int x,y;
   //     cin>>x>>y;
   //     // int flag_1 = (flag%2 == 0) ? 1:2;
   //     if(playerMove(x,y)){
   //         if(isWin(x,y)){
   //             gameView_ShowMap();
   //             winView();
   //             return;
   //         }
   //         // flag++;
   //     }else cout<<"无效,重输入"<<endl;
   // }    
     while(1){
        gameView_ShowMap();
      int x,y;
      cout<<"请输入坐标(x y,范围0-18):";
      while(!(cin>>x>>y)){// 检查输入是否有效
        cin.clear(); // 清除错误状态
        cout<<"无效输入,请重新输入坐标(x y,范围0-18): ";
      }
       if(x<0 || x>=19 || y<0 || y>=19){
        cout<<"坐标超出范围,请输入0-18之间的数字!"<<endl;
        continue;
      }
      if(playerMove(x,y)){
         if(isWin(x,y)){
          gameView_ShowMap();
          winView();
           return;
        }
      }else{
        cout<<"该位置已有棋子,请重新输入!"<<endl;
      }
    }
}
因为19*19 不能访问下标,所以在输入19要给与不可访问提醒,并加个判断x和y大于0小于19,不加可能会报错,但我之前写的时没给提示。
            if(x<0 || x>=19 || y<0 || y>=19){
        cout<<"坐标超出范围,请输入0-18之间的数字!"<<endl;
        continue;
      }
最最后,再写上主函数,并调用函数就行:
int main(){
   menuView();
   return 0;
}
至此,所有功能已全部写好,我们来一起看下完整代码:
#include<iostream>
#include<cstring> //也用不到
#include<iomanip>
using namespace std;
int map[19][19];
int flag;     // 如: flag = 20,表示当前是第[20]次落子,由黑方落子
void gameView();   //函数声明
void init(){
   int i,j;
   for(i=0;i<19;++i){
       for(j=0;j<19;++j){
       map[i][j] = 0;
     }
   }
   flag = 0; 
 }
int isWin(int x,int y){
    int flag_2 = (flag%2 == 0) ? 1:2;
    int count = 1;
    int i,j;
    for(i=y+1;i<19 && map[x][i] == flag_2;++i){
       count++;
    }
    for(i=y-1;i>=0 && flag_2 == map[x][i];--i){
       count++;
    }
    if(count>5) return flag_2;
    count = 1;
    for(i=x+1;i<19 && map[i][y] == flag_2;++i){
        count++;
    }
    for(i=x-1;i>=0 && map[i][y] == flag_2;--i){
       count++;
    }
    if(count>5) return flag_2;
    count = 1;
    for(i=x+1,j=y+1;i<19 && j<19 && map[i][j] == flag_2;++i,++j){
       count++;
    }
    for(i=x-1,j=y-1;i>=0 && j>=0 && map[i][j] == flag_2;--i,--j){
        count++;
    }
    count = 1;
    for(i=x+1,j=y-1;i<19 && j>=0 && map[i][j] == flag_2;++i,--j){
        count++;
    }
    for(i=x-1,j=y+1;i>=0  && j<19 && map[i][j] == flag_2;--i,++j){
        count++;
    }
    return 0;
}
int playerMove(int x,int y){
  if(x<0 || x>=19 || y<0 || y>=19) return 0; //在指定位置落子
  if(map[x][y] != 0) return 0;
    // if(flag%2 == 0) map[x][y] = 1;
    // else map[x][y] = 2;
  int flag_2 = (flag%2 == 0) ? 1:2;
  map[x][y] = flag_2;
  flag++;
  return 1;    //1表示落子成功
}
void menuView(){
   while(1){
      cout<<"=======五子棋======="<<endl;
     cout<<"1.进入游戏"<<endl;
     cout<<"2.设置"<<endl;
     cout<<"3.退出游戏"<<endl;
     cout<<"===================="<<endl;
     cout<<"请选择:"<<endl;
     int choice;
     cin>>choice;
     switch(choice){
        case 1: 
           gameView();
          break;
       case 2:
          cout<<"打印敬请期待..."<<endl; 
          continue;
       case 3:
          exit(0);
       default:
          cout<<"请重选:"<<endl;
          break;
     }
   }
}
void gameView_ShowMap(){
   int i,j;
   // cout<<" ";
   for(j=0;j<19;++j) 
    //cout<<j%10<<" "; //对齐
     cout<<setw(2)<<j<<" ";
      cout<<endl;
     for(i=0;i<19;++i){
     //cout<<i%10<<" ";    //只显示个位
      cout<<setw(2)<<i<<" ";
       for(j=0;j<19;++j){
        // if(map[i][j] == 0) cout<<0<<" "; //空
        // else if(map[i][j] == 1) cout<<1<<" ";  //黑
        // else if(map[i][j] == 2) cout<<2<<" ";  //白
        cout<<setw(2)<<map[i][j]<<" ";
       }
       cout<<endl;
   }
}
void winView(){
    if(flag%2 == 0) cout<<"黑棋赢了"<<endl;
    else cout<<"白棋赢了"<<endl;
    cout<<"用户按任意键回到主菜单"<<endl;
    cin.ignore();
    cin.get();
    menuView();
}
void gameView(){
   init();
   // while(1){
   //   gameView_ShowMap();
   //   int x,y;
   //   cin>>x>>y;
   //   // int flag_1 = (flag%2 == 0) ? 1:2;
   //   if(playerMove(x,y)){
   //       if(isWin(x,y)){
   //            gameView_ShowMap();
   //          winView();
   //       return;
   //     }
   //     // flag++;
   //  }else cout<<"无效,重输入"<<endl;
   // }
   while(1){
     gameView_ShowMap();
     int x,y;
     cout<<"请输入坐标(x ,y,范围0-18):";
     while(!(cin>>x>>y)){// 检查输入是否有效
       cin.clear(); // 清除错误状态
       cout<<"无效输入,请重新输入坐标(x y,范围0-18): ";
     }
     if(x<0 || x>=19 || y<0 || y>=19){
       cout<<"坐标超出范围,请输入0-18之间的数字!"<<endl;
       continue;
     }
     if(playerMove(x,y)){
       if(isWin(x,y)){
         gameView_ShowMap();
         winView();
         return;
       }
     }else{
       cout<<"该位置已有棋子,请重新输入!"<<endl;
     }
   }
}
int main(){
    menuView()
  return 0;
}
看着还行,好了收工,下次见!