Java学习笔记(十二)——开发个小项目(GoBang3.0 plus)

 接上一篇,看了别的优秀作品有点感触,给自己一点灵感,完善&优化项目。

画个大纲(跟随慢慢开发过程不断完善)

1.用户

两个用户对战  一黑一白

用户可以是人,也可以是AI。对战模式支持人人,人机,机机。

  • 属性

本次比赛执棋颜色

用户名

密码

游戏得分(赢得局数)

存档棋盘信息(二维数组,chessShape类型数组,黑、白、总棋子个数,下一次下棋的棋权)

  • 方法

下棋

输赢

设置、获取属性接口

2.比赛规则

一黑一白交替轮流下棋

可以决定哪个玩家先手

不可以重复下棋到同一个位置

不可以将棋子下到边界外

可以撤回刚刚下的棋,不可以撤回上一步的棋

哪一方横竖斜到达5个棋子赢一局

点击存档,当前用户储存当前棋面所有信息

读档读取当前用户保存的棋盘,一个用户只能存一个棋盘(人机模式加进来可以分开储存)

3.界面

  • 登录界面

用户登录:

用户名、密码输入栏(带提示,密码隐藏,用户密码匹配检验),登录按键,注册按键(注册新建user储存到用户txt文档);

登录图像。     

  • 菜单界面

选择功能:

新游戏:对战模式 —— 人人,人机,机机

游戏积分 —— 获胜局数

退出游戏:关闭游戏

可以有设置按键:设置游戏背景音乐,音量等,添加用户设置棋子花色功能(现阶段默认登录用户为黑子)

  • 游戏界面

下棋主界面:

设置棋盘、背景板、菜单栏、棋子计数板、计分板、棋子、下棋指示器,刷新窗体后这些都不会消失。

选择游戏先手为黑棋还是白棋。

背景板、棋盘:17*17(16行,17根线,在中间画分界小黑点),棋盘在背景板之上。

菜单栏:撤回、清空、存档、帮助功能——撤回/清空时,计数器要跟着变化。

棋子计数板:记录当前棋盘上黑白棋子个数。(图像不重叠,随撤回清空等操作实时刷新)。

计分板:记录目前双方赢得局数。

棋子:下到交叉线(棋子校准)、不重复、不越出棋盘、刷新保存,可以撤回,可以识别获胜。

当前局数计时器:距离游戏开始的耗时。

  • 获胜界面

当有一方获胜后弹出

显示哪方获胜

显示棋面棋子数

显示获胜图片

菜单:

再战一局:触发游戏界面(棋盘清空)

退出游戏:回到菜单界面(棋盘清空)

回顾棋局:显示重新下棋步骤(撤回步骤显示回顾结束后弹窗返回

乱七八糟的功能

存档

读档

软件使用日志

第四天 —— 完善优化菜单界面、获胜界面功能

实现棋子回放、退出游戏、存档方法,完善一步撤销提示、赢棋之后界面跳转逻辑。

实现方式以及一些修正

1. 撤销:设置撤销标志,当点击撤销时,判断标志——撤销是否在上一步发生(发生为1,否则为

               0),如果发生则,弹窗提示已经撤销一步,不可以再次撤销;如果未发生,则是标志置

               为1,实现撤销功能。当每次下棋的时候都讲撤销标志置为0。                   

2. 退出游戏:弹窗确认是否离开,是:判断没有存档标志——则提示没有存档:是否离开,是则清

                      空数据,跳转菜单页面,否则都保留原棋盘页面。

3. 存档:将当前棋盘所有数据存入用户信息中。

4. 读档:判断用户是否有存档的棋盘,有则弹窗提示,并初始化棋盘;否则弹窗提示没有存档棋

               盘。

5. 回顾棋盘:先将棋盘清空,然后调用界面的paint方法重画棋盘,根据当前储存的chessIndex开

                      始绘制棋子,每次下棋之后隔1.5s判断这个棋子是否被撤回,若撤回就重新绘制前面

                      的所有棋子并将当前棋子跳过,继续绘制记录的下一个棋子。

                      棋子回顾结束后弹窗提示回顾结束,回到赢棋页面。

6.判赢方代码再学习:

  • 上次自己实现的可能是一种看起来比较聪明的穷举法。

思路是这样的:

(放一枚棋子判断是否连成5字有8个方向,然后可以归为4个方向,两两共线,那么就可以先选4个朝一头延伸,到边界或者碰到了不一样的颜色再反向延伸,记总数达到4个就判赢,return棋子颜色,否则4个大方向都结束还没有数到4就返回0。数4个是因为当前棋子默认记录)

设置了8个方向坐标,用于坐标移动:

int[][] changeLocation =new int[][]{{-1,-1},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1}};//04 15 26 37 是一个方向

具体代码:

public int ifWin(int x,int y,int colorNum){
        int countNumFive = 4;
        int currX=x,currY = y;
        int flag = 1;
        for(int i=0;i<4;i++) {    //左上到右下  竖  右上到左下  横 4个方向判断
            countNumFive=4;
            while (countNumFive > 0) {//如果没数到4就继续数
                if (flag == 1) {    //先判断方向
                    if (currX + changeLocation[i][0] >= 0 && currX + changeLocation[i][0] <= 16 && currY + changeLocation[i][1] >= 0 && currY + changeLocation[i][1] <= 16 && goBangUi.chesses[currX + changeLocation[i][0]][currY + changeLocation[i][1]] == colorNum) //判断颜色是否一样,是否抵达边界 {
                        currX += changeLocation[i][0];
                        currY += changeLocation[i][1];
                        countNumFive--;
                    }
                    else {          //换方向
                        flag = 2;
                        currX = x;
                        currY = y;
                    }
                }
                else if (flag == 2 ) {
                    if (currX + changeLocation[i+4][0] >= 0 && currX + changeLocation[i+4][0] <= 16 && currY + changeLocation[i+4][1] >= 0 && currY + changeLocation[i+4][1] <= 16 && goBangUi.chesses[currX + changeLocation[i+4][0]][currY + changeLocation[i+4][1]] == colorNum) {
                        currX += changeLocation[i+4][0];
                        currY += changeLocation[i+4][1];
                        countNumFive--;
                    }
                    else {         //这个大方向总数没连够,就换个别的大方向
                        flag = 1;
                        currX = x;
                        currY = y;
                        break;
                    }
                }
                if (countNumFive == 0) //如果连够4个+默认当前,则直接结束方法,返回颜色值
                    return colorNum;
            }
        }
        //如果4个大方向都不够5个,则返回0,表示没有连够
            return 0;
    }
  • 还学习到里一种听起来比较莽的穷举法:五元组法

思路:

一个16行*16列的棋盘上共有17*17个点,五子连棋的情况共有17*13*2(横纵)+13*13*2(左右斜)种。

只要判断当前是否有棋子连成这个局势就能判断是否有赢棋。

  • 听闻大佬是用动态规划实现的:

思路:

4个二维数组模拟棋盘上某个位置在4个方向分别可以连成的最长棋子数,每放一个棋子,其向四周扩展,半径为4,该方向上连续不空棋,或者不遇到异色的棋子的最长记录都++,当有某条记录等于5时,直接return获胜的颜色。

7.发现一个小问题

       上次不是将repaint方法是调用了paint方法吗,除此之外还做了点别的事情。如今调用的时候发现了二者的表面区别:当调用repaint方法是,他是先实现之后的代码,再调用paint刷新窗体。比如你想要刷新窗体,重画棋盘,然后棋盘上面画存档的棋子;这时候如果调用repaint,就会先画棋子,然后paint画棋盘,就把你刚刚画好的东西刷新掉了,这并不是我们想要实现的功能,就需要调用paint方法。

解决遇到的问题

1.上次问题解决放到功能实现里了,其他还没解决完。

本次待解决的问题

设置用户登录之后,显示用户信息,将用户和下棋、棋局结果、存档读档等绑定,实现赢棋加分,战绩记录。

实现双人单机、人机等分离。

代码

太长继续放github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的五子棋人机对弈小程序的示例代码,基于 Java Swing 编写。 ```java import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Gobang extends JFrame implements ActionListener, MouseListener { private static final long serialVersionUID = 1L; private JPanel panel; private JButton button; private JLabel label1, label2; private int[][] chessBoard; private boolean isBlack = true; private boolean gameOver = false; private final int ROWS = 15; private final int COLS = 15; private final int SIZE = 30; private final int MARGIN = 30; public Gobang() { setTitle("五子棋"); setSize(600, 600); setResizable(false); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); init(); setVisible(true); } private void init() { panel = new JPanel(); panel.setBackground(new Color(255, 204, 102)); panel.setLayout(null); panel.addMouseListener(this); button = new JButton("重新开始"); button.addActionListener(this); button.setBounds(450, 120, 100, 40); label1 = new JLabel("黑方先手"); label1.setFont(new Font("宋体", Font.BOLD, 20)); label1.setBounds(450, 200, 150, 40); label2 = new JLabel("轮到黑方"); label2.setFont(new Font("宋体", Font.BOLD, 20)); label2.setBounds(450, 250, 150, 40); panel.add(button); panel.add(label1); panel.add(label2); add(panel); chessBoard = new int[ROWS][COLS]; } private void drawChessBoard(Graphics g) { for (int i = 0; i <= ROWS; i++) { g.drawLine(MARGIN, MARGIN + i * SIZE, MARGIN + COLS * SIZE, MARGIN + i * SIZE); } for (int i = 0; i <= COLS; i++) { g.drawLine(MARGIN + i * SIZE, MARGIN, MARGIN + i * SIZE, MARGIN + ROWS * SIZE); } } private void drawChessPiece(Graphics g, int row, int col, boolean isBlack) { int x = MARGIN + col * SIZE; int y = MARGIN + row * SIZE; g.setColor(isBlack ? Color.BLACK : Color.WHITE); g.fillOval(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE); } private boolean checkWin(int row, int col) { int count = 1; int color = chessBoard[row][col]; int r, c; // 检查水平方向 r = row; c = col - 1; while (c >= 0 && chessBoard[r][c] == color) { count++; c--; } c = col + 1; while (c < COLS && chessBoard[r][c] == color) { count++; c++; } if (count >= 5) { return true; } // 检查竖直方向 count = 1; r = row - 1; c = col; while (r >= 0 && chessBoard[r][c] == color) { count++; r--; } r = row + 1; while (r < ROWS && chessBoard[r][c] == color) { count++; r++; } if (count >= 5) { return true; } // 检查左斜方向 count = 1; r = row - 1; c = col - 1; while (r >= 0 && c >= 0 && chessBoard[r][c] == color) { count++; r--; c--; } r = row + 1; c = col + 1; while (r < ROWS && c < COLS && chessBoard[r][c] == color) { count++; r++; c++; } if (count >= 5) { return true; } // 检查右斜方向 count = 1; r = row - 1; c = col + 1; while (r >= 0 && c < COLS && chessBoard[r][c] == color) { count++; r--; c++; } r = row + 1; c = col - 1; while (r < ROWS && c >= 0 && chessBoard[r][c] == color) { count++; r++; c--; } if (count >= 5) { return true; } return false; } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == button) { chessBoard = new int[ROWS][COLS]; isBlack = true; gameOver = false; label1.setText("黑方先手"); label2.setText("轮到黑方"); panel.repaint(); } } @Override public void mouseClicked(MouseEvent e) { if (gameOver) { return; } int x = e.getX(); int y = e.getY(); if (x < MARGIN || x > MARGIN + COLS * SIZE || y < MARGIN || y > MARGIN + ROWS * SIZE) { return; } int row = Math.round((float) (y - MARGIN) / SIZE); int col = Math.round((float) (x - MARGIN) / SIZE); if (chessBoard[row][col] != 0) { return; } chessBoard[row][col] = isBlack ? 1 : 2; panel.repaint(); if (checkWin(row, col)) { gameOver = true; String winner = isBlack ? "黑方" : "白方"; JOptionPane.showMessageDialog(panel, winner + "获胜!", "游戏结束", JOptionPane.INFORMATION_MESSAGE); return; } isBlack = !isBlack; label1.setText(isBlack ? "黑方先手" : "白方先手"); label2.setText(isBlack ? "轮到黑方" : "轮到白方"); if (!isBlack) { // AI 下棋 int[] pos = computeNextMove(); row = pos[0]; col = pos[1]; chessBoard[row][col] = 2; panel.repaint(); if (checkWin(row, col)) { gameOver = true; JOptionPane.showMessageDialog(panel, "白方获胜!", "游戏结束", JOptionPane.INFORMATION_MESSAGE); return; } isBlack = !isBlack; label1.setText(isBlack ? "黑方先手" : "白方先手"); label2.setText(isBlack ? "轮到黑方" : "轮到白方"); } } private int[] computeNextMove() { int[] pos = new int[2]; // TODO: AI 算法 return pos; } @Override public void paint(Graphics g) { super.paint(g); drawChessBoard(g); for (int i = 0; i < ROWS; i++) { for (int j = 0; j < COLS; j++) { if (chessBoard[i][j] == 1) { drawChessPiece(g, i, j, true); } else if (chessBoard[i][j] == 2) { drawChessPiece(g, i, j, false); } } } } @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} public static void main(String[] args) { new Gobang(); } } ``` 这个程序由一个主窗口和一个 JPanel 组成。主窗口负责创建并显示 JPanel,JPanel 则绘制棋盘和棋子,并处理用户交互和游戏逻辑。 在 JPanel 的构造函,我们初始化了棋盘组 `chessBoard`,表示棋盘上每个格子的状态。1 表示黑子,2 表示白子,0 表示空格。我们还添加了一个按钮和两个标签,用于重新开始游戏和显示游戏状态。 在 `drawChessBoard` 方法,我们使用 Graphics 类的 `drawLine` 方法绘制棋盘的线条。 在 `drawChessPiece` 方法,我们使用 Graphics 类的 `fillOval` 方法绘制棋子。黑子用黑色填充,白子用白色填充。 在 `checkWin` 方法,我们检查棋盘上某个格子所处的行、列、左斜方向和右斜方向是否有连续五个相同颜色的棋子。如果有,则游戏结束。 在 `actionPerformed` 方法,我们对重新开始按钮的点击事件进行处理。重新初始化棋盘组、重置游戏状态和标签,并调用 JPanel 的 `repaint` 方法刷新界面。 在 `mouseClicked` 方法,我们处理鼠标点击事件。如果游戏已经结束或点击了棋盘外部或已经有棋子存在,则不做任何处理。否则,我们根据鼠标点击坐标计算出所在的行和列,将这个位置设为当前玩家下的棋子。然后检查游戏是否结束,并切换当前玩家。如果当前玩家是电脑,则调用 `computeNextMove` 方法计算出下一个位置,并执行相同的操作。 在 `paint` 方法,我们使用 Graphics 类的 `paint` 方法绘制棋盘和棋子。 在 `computeNextMove` 方法,我们需要实现一个 AI 算法,来计算出电脑下一步应该走的位置。这里我们暂时留空,可以自己实现一个简单的算法,比如随机生成一个空白位置,作为电脑的下一步。 最后,在 `main` 方法,我们创建一个新的 Gobang 对象,启动程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值