在前一篇博文中讲解了五子棋的UI的创建,这里在有UI的基础上,继续编写落子存储、胜负逻辑、悔棋、重新开始、存储与回放棋谱等操作
目录
过程存储
前面一篇文章讲到了如何在棋盘上绘制棋子,接下来要做的就是存储这些落子的过程,这其中包含了
1. 落子后更新棋盘表chess_map
这里要维护一个Chess_Map类,其中包含了一个二维数组chess_map,用于存储棋盘数据,每一个点为0时表示该处为空,为1时表示该处为黑子,为2时表示该处为白子
并加入一些功能方法,比如判断某坐标处是否为空、设置某坐标处信息等
public class Chess_Map {
private int[][] chess_map = new int[16][16];
Chess_Map(){
for(int i=0;i<16;i++){
for (int j=0;j<16;j++){
chess_map[i][j]=0;
}
}
}
public void chess_map_reset(){
for(int i=0;i<16;i++){
for (int j=0;j<16;j++){
chess_map[i][j]=0;
}
}
}
public int[][] getChess_map() {
return chess_map;
}
public void setChess_map(int x,int y, int v) {
this.chess_map[x][y]=v;
}
public void setChess_map(int[][] x) {
this.chess_map=x;
}
//判断该位置是否为空
public boolean map_pos_empty(int x , int y){
if(chess_map[x][y]==0){
return true;
}
return false;
}
}
有了这个数据结构之后,就可以在每次落子时更新这个map
chess_map.setChess_map(pos[0], pos[1], chess_flag);
2. 落子后将落子信息添加到chess_flow中用于存储按序的步骤,用于后续的悔棋和复盘
这里为了简单,可以直接在Chess_Map类中加入一个Stack,用于存储落子顺序,由于在map中已经存储了棋子颜色的信息,因此这里可以只存放坐标信息,用一个数组来表示坐标即可
private final Stack<int[]> chess_flow
同样的,在每次落子时push进stack
chess_flow.push(pos);
3. 除此之外,还有一些用于控制的标志位,这里就不作赘述了,读者可以自行设计
胜负逻辑
这是五子棋的一个关键代码,如何判断落子后某一方的胜负呢?
首先需要明确,五子棋的胜利条件是横向、纵向、斜向有五个同一颜色的棋子连成一线,这里我最开始想用遍历chess_map的方式进行判断,后来发现这个方法实在是太晕蠢了!
实际上只需要判断每一次落子的时候,以这个子为中心,遍历其周围的棋盘内容即可!在具体实现中我分为了四个大方向(横向、纵向、左上右下、左下右上)来进行判断,具体代码如下
这里仅给出横向的判断,其他三个方向同理,不作赘述
public int judge_if_win(int[][] chess_map,int x,int y){
int flag=chess_map[x][y];
//int tmp_x=x,tmp_y=y;
int count=1;
//横向 左
for(int i=x-1;i>x-5 && i>=0;i--){
if(chess_map[i][y]==flag){
count++;
}
else{
break;
}
}
//横向 右
for(int i=x+1;i<x+5 && i<16;i++){
if(chess_map[i][y]==flag){
count++;
}
else{
break;
}
}
if(count>=5){
return flag;
}
else count=1;
if(count>=5){
return flag;
}
else {
return 0;
}
}
最后将这个方法封装一下,可以直接更新判断胜利的flag
public void judge(Chess_Map chess_map,int x,int y){
win_flag = chess_utils.judge_if_win(chess_map.getChess_map(), x, y);
if (win_flag != 0) {
//黑赢
if (win_flag == 1) {
chess_utils.show_dialog("执黑者赢");
}
//白赢
else if (win_flag == 2) {
chess_utils.show_dialog("执白者赢");
}
}
}
悔棋
下棋难免会脑子一抽出现低级失误,这时候悔棋的作用就体现了,要想实现悔棋功能,就要用到之前过程存储中维护的chess_flow,如果需要悔棋,按下悔棋按键,则会从chess_flow中pop出栈顶的坐标,也就是最后一步棋,随后抹去chess_map中该步棋的数据,并且重绘棋盘即可
int[] pos = chess_flow.pop();
int chess_flag = map[pos[0]][pos[1]];
if (chess_flag > 0) {
map[pos[0]][pos[1]] = 0;
board_listener.setChess_flag(chess_flag);
panel_board.repaint();
}
此外,肯定还需要维护一些状态,比如如果chess_flow为空、已经胜利了等等,并且对各种情况做出相应的响应,这里给出一个demo
//已经下过 并且一局未结束 并且不在复盘 可悔棋
if (!chess_flow.empty() && board_listener.getWin_flag() == 0 && !board_listener.isReview_flag()) {
int[] pos = chess_flow.pop();
int chess_flag = map[pos[0]][pos[1]];
if (chess_flag > 0) {
map[pos[0]][pos[1]] = 0;
board_listener.setChess_flag(chess_flag);
panel_board.repaint();
}
}
else {
if(chess_flow.empty()){
JOptionPane.showMessageDialog(null, "您已无棋可悔");
}
if (board_listener.getWin_flag() != 0) {
JOptionPane.showMessageDialog(null, "本局游戏已结束");
}
if(board_listener.isReview_flag()){
JOptionPane.showMessageDialog(null, "正在复盘,不能悔棋");
}
}
重新开始
重新开始逻辑比较简单,将维护的各种内容清空或初始化,并且将各种flag置为默认值即可,另外还需要重绘棋盘为空,这里给出一个方法
public void start_over(){
//步骤stack清空
chess_flow.clear();
//重置chess_map
for(int i=0;i<16;i++){
for(int j=0;j<16;j++){
chess_map.setChess_map(i,j,0);
}
}
board_listener.setChess_flag(1);
//board_listener.setOffline_flag(true);
board_listener.setWin_flag(0);
board_listener.setReview_flag(false);
board_listener.setOnline_myturn(true);
board_listener.setRegret_flag(false);
board_listener.setStartover_flag(false);
panel_board.repaint();
}
存储本局棋谱
这一步比较难搞,要建立与本地文件的输出流,并且将行棋流chess_flow存储,另外还需要完成棋谱的命名与确认等操作,之类直接给出一个方法,将其添加到重新开始按键的监听器中即可
@Override
public void actionPerformed(ActionEvent e) {
String name="";
int n=SAVE_FAIL;
while(Objects.equals(name, "") || n==SAVE_FAIL){
name = JOptionPane.showInputDialog("请输入保存棋谱名:");
if(name==null){
break;
}
else if(name.equals("")){
chess_utils.show_dialog("名字不能为空");
}
else {
try {
n = chess_utils.save_flow(chess_flow, "chess_logs\\"+name+".txt");
if(n==SAVE_SUCCESS){
chess_utils.show_dialog("已保存");
}
else{
chess_utils.show_dialog("保存失败");
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
其中的save_flow函数在Chess_Utils类中给出,用于将chess_flow中的坐标存储为txt文本
public int save_flow(Stack<int[]> chess_flow, String path) throws IOException {
int n=chess_flow.size();
//输出流
FileWriter writer;
File file=new File(path);
if(!file.exists()){
if(file.createNewFile()) {
//实例化输出流
writer = new FileWriter(file);
for (int i = 0; i < n; i++) {
int[] tmp = chess_flow.pop();
writer.write(tmp[0] + "," + tmp[1]);
writer.write("\n");
}
writer.flush();
writer.close();
return SAVE_SUCCESS;
}
else{
return SAVE_FAIL;
}
}
else{
int o = JOptionPane.showConfirmDialog(null,"该文件已存在,是否覆盖","提示",JOptionPane.YES_NO_OPTION);
if(o==JOptionPane.YES_OPTION){
writer = new FileWriter(file);
for (int i = 0; i < n; i++) {
int[] tmp = chess_flow.pop();
writer.write(tmp[0]+","+tmp[1]);
writer.write("\n");
}
writer.flush();
writer.close();
return SAVE_SUCCESS;
}
return SAVE_FAIL;
}
}
回放棋局
上一步完成了存储棋谱的过程,这里就要从本地文件中读取相应的数据信息,同样的,先在回放棋局中加入下列代码,用作功能
@Override
public void actionPerformed(ActionEvent e) {
if(board_listener.isOnline_flag()){
chess_utils.show_dialog("联机模式下不可回放棋谱");
}
else{
String name = "";
while (name.equals("")) {
name = JOptionPane.showInputDialog(null, "请输入要读取的棋谱名:");
if (name == null) {
break;
} else if (name.equals("")) {
chess_utils.show_dialog("名字不能为空");
} else {
try {
//重置棋盘
chess_utils.start_over();
Stack<int[]> stack = chess_utils.read_flow("chess_logs\\"+name+".txt");
Stack<int[]> stack1 = new Stack<>();
while (!stack.empty()) {
stack1.push(stack.pop());
}
while ((!stack1.empty())) {
chess_flow.push(stack1.pop());
}
board_listener.setReview_flag(true);
chess_utils.show_dialog("读取成功,点击下一步行棋");
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
}
}
其中的read_flow方法也在Chess_Utils类中给出,用于读取本地棋谱
public Stack<int[]> read_flow(String path) throws IOException {
FileReader reader=new FileReader(path);
BufferedReader bufferedReader=new BufferedReader(reader);
Stack<int[]> flow =new Stack<>();
String lineStr;
while((lineStr =bufferedReader.readLine())!=null){
String[] split_str=lineStr.split(",");
int[] a = new int[2];
a[0]=Integer.parseInt(split_str[0]);
a[1]=Integer.parseInt(split_str[1]);
flow.push(a);
}
reader.close();
bufferedReader.close();
return flow;
}
下一步
读取完棋盘后,就要进行回放了,通过点击下一步按钮,可以一步一步回放行棋过程
int chess_flag=1;
@Override
public void actionPerformed(ActionEvent e) {
if(!chess_flow.empty() && board_listener.isReview_flag()){
int[] move = chess_flow.pop();
int map_x=move[0];
int map_y=move[1];
chess_map.setChess_map(map_x, map_y, chess_flag);
board_listener.draw_offline(map_x,map_y);
}
else{
JOptionPane.showMessageDialog(null, "棋谱已走完,您可继续行棋");
board_listener.setReview_flag(false);
}
}
至此,单机模式下的一些功能已经完善,下一篇文章会实现联机模式!