面向对象程序设计(Java) 课程设计——三少五子棋(Final)

背景:设计五子棋游戏背景,

初步猜想:

① 设计一个五子棋小游戏,实现人机对战和双人对战两功能。

② 设计五子棋小游戏,使用Java Swing设计可视化操作界面,并使用GraPhic 2D技术设计图形

③ 使用五元组算法实现人机对战方法

参考算法链接:https://blog.csdn.net/m0_51336041/article/details/125633406

目录

一、团队成员及任务

二、项目简介

三、项目架构图

四、运行结果图

 五、项目git地址:

六、个人部分实现过程(代码关键部分说明)

一、团队成员及任务


队长:
高晔川 计科(智能)22-1 202203200035
负责模块:  Java Swing界面搭建,Graphic 2D图形绘制,Panel面板搭建,五子棋过程的实现构思 

个人博客地址:(140条消息) 面向对象程序设计(Java) 课程设计——三少五子棋 Part1_Aimless.的博客-CSDN博客
队员:
成员① 戚彦良 计科(智能)22-1 202203200029
负责模块:  负责函数算法的编写,使用函数方法体判断哪方获胜,人机对战中如何实现  排行榜功能的实现

个人博客地址:

(134条消息) 面向对象程序设计(Java)课程设计——三少五子棋Part2_m0_73640433的博客-CSDN博客
成员② 吴沅峰 计科(智能)22-1 202203200020  
负责模块:鼠标监听器、动作监听器、人机对战自动下棋算法

个人博客地址:(134条消息) 面对程序设计(Java)课程设计————三少五子棋_a1067793829的博客-CSDN博客

二、项目简介

功能描述:由三人合作运用Java编写五子棋小游戏,具有双人对战,人机对战,悔棋,Replay,GameOver,投降,排行榜功能。可从排行榜中刷新,查看战绩,并且在About us 中加入了我们制作人的信息。

最终实现的功能如下:

  基本功能:生成界面,绘制棋盘,绘制棋子,实现轮流点击下棋并判断输赢。
  开始游戏功能:初始化相应的各项数据与变量,清空棋盘,重新游戏。
  游戏说明功能:生成对话框,介绍游戏规则。
  认输功能:轮到某一方下棋时,点击此按钮,该方认输,游戏结束。
  保存战绩:贯穿整个游戏的变量胜场数,需连接数据库并将数据保存。
  查看战绩:连接并读取数据库,将存入的胜场数据读取并输出。
 

三、项目架构图

黄标:队长负责的板块

黄标:成员①的负责板块

 黄标:成员②负责的板块

四、运行结果图

 

 五、项目git地址:

https://gitee.com/HitoriBocchi0113/lottery_-final


六、个人部分实现过程(代码关键部分说明)

〇 我们假设每个数组可以存储对应的五子棋的显色情况,声明存储五子棋存在的二维数组,

① 总体界面如图所示

总体界面搭建我们使用了继承JPanel类的子类,在构造方法中声明Panel面板的信息

总体工作模块如图所示:

    JLabel selectLabel = new JLabel("游戏选择:");
    JButton doublePerson = new JButton("双人对战");
    JButton singleBlack = new JButton("人机持黑");
    JButton singleWrite = new JButton("人机持白");
    JLabel elseLabel = new JLabel("其他设置:");
    JButton regret = new JButton("悔棋");
    JButton restart = new JButton("Replay");
    JButton forExit = new JButton("GameOver");
    JButton surrender = new JButton("投降");
    JButton leaderBoard = new JButton("排行榜");
    JButton aboutUs = new JButton("About us");

在这里呈现了我们的代码块 

public TablePanel() {
        setLayout(null);
        setPreferredSize(new Dimension(TABLE_WIDTH, TABLE_HEIGHT));//设置组件的首选大小
        setBackground(Color.yellow); //设置背景颜色
        init(); //初始化一些属性
        isStart = 0;
        addMouseListener(mouseAdapter); //添加鼠标监听
        addMouseMotionListener(mouseAdapter);
        selectLabel.setFont(font1);
        doublePerson.setBounds(50,600,100,30);
        doublePerson.setFont(font2);
        singleBlack.setBounds(175,600,100,30);
        singleBlack.setFont(font2);
        singleWrite.setBounds(300,600,100,30);
        singleWrite.setFont(font2);
        elseLabel.setFont(font1);
        regret.setBounds(425,600,100,30);
        regret.setFont(font2);
        restart.setBounds(550,600,100,30);
        restart.setFont(font2);
        forExit.setBounds(50,660,100,30);
        forExit.setFont(font2);
        surrender.setBounds(175,660,100,30);
        surrender.setFont(font2);
        leaderBoard.setBounds(300,660,100,30);
        leaderBoard.setFont(font2);
        aboutUs.setBounds(425,660,100,30);
        aboutUs.setFont(font2);
        titleLabel.setFont(font3);
 
        doublePerson.addActionListener(actionListener);
        singleBlack.addActionListener(actionListener);
        singleWrite.addActionListener(actionListener);
        regret.addActionListener(actionListener);
        restart.addActionListener(actionListener);
        forExit.addActionListener(actionListener);
        surrender.addActionListener(actionListener);
        leaderBoard.addActionListener(actionListener);
        aboutUs.addActionListener(actionListener);
 
        add(selectLabel);
        add(doublePerson);
        add(singleBlack);
        add(singleWrite);
        add(elseLabel);
        add(regret);
        add(restart);
        add(forExit);
        add(surrender);
        add(leaderBoard);
        add(titleLabel);
        add(aboutUs);
    }

        通过上述代码我们可以搭建出基本的操作界面,为后续功能实现创造条件

②使用Graphic绘图

        在实现过程中,我们首先考虑了使用棋盘图片作为背景,但图片的质量可能会影响到面板的清晰度和整体的观感,甚至出现锐度低效果失真的情况,遂不考虑使用Icon类方法插入图片。最终我们使用了Graphic类方法创建了五子棋网格,代码如图所示
 

 private void initPaint(Graphics g, Graphics2D gg) {
        super.paint(g);
        g.setColor(Color.BLACK);
        for (int i = 0; i < num; i++) {
            int x = Initial_X + SP * i;
            int y = Initial_Y + SP * (num - 1);
            g.drawLine(x, Initial_Y, x, y);
        }
        for (int i = 0; i < num; i++) {
            int x = Initial_X + SP * (num - 1);
            int y = Initial_Y + SP * i;
            g.drawLine(Initial_X, y, x, y);
        }
        int[][] positions = { {3, 3}, {11, 3}, {3, 11}, {11, 11}, {7, 7} };
 
        for (int[] pos : positions) {
            int x = Initial_X + SP * pos[0] - RECT_SIZE / 2;
            int y = Initial_Y + SP * pos[1] - RECT_SIZE / 2;
            g.fillOval(x, y, RECT_SIZE, RECT_SIZE);
        }
        bs = new BasicStroke(5);
        gg.setStroke(bs);
        gg.drawRect(Initial_X - 7, Initial_Y - 7, (num - 1) * SP + 14, (num - 1) * SP + 14);
        bs = new BasicStroke(3);
        gg.setStroke(bs);
        for (int i = 1; i < num; i += 4) {
            int x = Initial_X + SP * i;
            int y = Initial_Y + SP * (num - 1);
            gg.drawLine(x, Initial_Y, x, y);
        }
        for (int i = 1; i < num; i += 4) {
            int x = Initial_X + SP * (num - 1);
            int y = Initial_Y + SP * i;
            gg.drawLine(Initial_X, y, x, y);
        }
    }

        我们首先清除之前绘制的内容,使用前两组for循环创建基本的网格,确定棋盘的基本框架。  

position数组存储的是特殊的坐标,用于存储棋盘上key point的位置,第三组循环是用于绘制棋盘上的关键点。之后两组循环是用来加粗边界。我们创建ovalPaint方法确定了每个棋子的显示状况
 

 private void ovalPaint(Graphics2D gg) {
        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num; j++) {
                int x = Initial_X + SP * i - cheeseSize / 2;
                int y = Initial_Y + SP * j - cheeseSize / 2;
                if (table[i][j] == 2) {
                    gg.setColor(Color.BLACK);
                    gg.fillOval(x, y, cheeseSize, cheeseSize);
                } else if (table[i][j] == 1) {
                    gg.setColor(Color.WHITE);
                    gg.fillOval(x, y, cheeseSize, cheeseSize);
                } else if (table[i][j] == 3) {
                    gg.setColor(Color.RED);
                    gg.drawOval(x, y, cheeseSize, cheeseSize);
                }
            }
        }
        if (win) {
            select_X = -10;
            select_Y = -10;
        } else {
            bs = new BasicStroke(1);       // 画笔宽度为1
            gg.setStroke(bs);
            //画选择框
            gg.setColor(Color.BLACK);
            gg.drawOval(Initial_X + SP * select_X - cheeseSize / 2,
                    Initial_Y + SP * select_Y - cheeseSize / 2,
                    cheeseSize, cheeseSize);
        }
    }

这样,我们可以确定基本的操作面板。

        ③函数算法的编写,使用函数方法体判断哪方获胜
判断某种棋子是否已经在当前局面中胜利。它通过遍历八个方向来判断该种棋子的连子情况,如果存在一个方向中有四个或以上的同色棋子,则将win标记置为true,表示该种棋子获胜。

        具体来说,该段代码中的directions数组存储了八个方向的偏移量,其中{ -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 }分别表示横向、纵向、左斜和右斜四个方向,{ -1, -1 }, { 1, 1 }, { -1, 1 }, { 1, -1 }分别表示左上到右下、右下到左上、左下到右上和右上到左下四个方向。然后对于每个方向,程序将棋子逐个向一个方向延伸,直到遇到一个空位置或者边界,如果在此过程中遇到了相同颜色的棋子,则将 sum 计数器加1,表示已经扩展了一个棋子,直到 sum 累计数值等于或者大于4,则表示该种棋子已经获胜。

        当某种棋子获胜时,将win标记置为true,同时记录下获胜时的棋局情况,包括步数和胜利方。这段代码将数据写入到一个文件中,文件路径为a.txt。如果文件不存在,则会创建一个空文件。

private void judgement(int type, int x, int y) {
        // 传入参数,来判断是黑(2)或白(1)子
 
        int[][] directions = { {-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}, {1, -1} };
        // 定义八个方向的偏移量
 
        for (int[] direction : directions) {
            int sum = 0;
            int dx = direction[0];
            int dy = direction[1];
            int i = x + dx;
            int j = y + dy;
 
            while (i >= 0 && i < num && j >= 0 && j < num && table[i][j] == type) {
                sum++;
                i += dx;
                j += dy;
            }
 
            i = x - dx;
            j = y - dy;
 
            while (i >= 0 && i < num && j >= 0 && j < num && table[i][j] == type) {
                sum++;
                i -= dx;
                j -= dy;
            }
 
            if (sum >= 4) {
                win = true;
                if (win){
                    try{
                        java.io.File file = new java.io.File("a.txt");
                        if (!file.exists()){
                            file.createNewFile();
                        }
                        if (oval_type == 1){
                            String result = "步数:" + step + "黑方赢" + '\n';
                            java.io.FileWriter fileWriter = new java.io.FileWriter(file,true);
                            fileWriter.write(result);
                            fileWriter.close();
                        } else {
                            String result = "步数:" + step + "白方赢" + '\n';
                            java.io.FileWriter fileWriter = new java.io.FileWriter(file,true);
                            fileWriter.write(result);
                            fileWriter.close();
                        }
                    }catch (java.io.IOException e){
                        e.printStackTrace();
                    }
                }
                return;
            }
        }
    }

core()方法

        用来评估当前棋局的分数的,以便计算计算机下一步的最佳落子点。该方法的输入参数w和b分别表示白色和黑色棋子在当前局面中连续的棋子个数,其中w和b至少有一个为0。如果两个参数都为0,则返回7,表示此处可以下子。如果w和b都大于0,则返回0,表示此处不能下子。如果只有其中一种连续的棋子,则根据对应的分数返回结果。分数越高,表明这个位置越有利。

        具体来说,当白子连续1颗时返回15分,连续2颗时返回400分,连续3颗时返回1800分,连续4颗时返回100000分;当黑子连续1颗时返回35分,连续2颗时返回800分,连续3颗时返回15000分,连续4颗时返回800000分。
 

private int score(int w, int b) {
        if (w > 0 && b > 0) {
            return 0;
        }
        if (w == 0 && b == 0) {
            return 7;
        }
        if (w == 1) {
            return 35;
        }
        if (w == 2) {
            return 800;
        }
        if (w == 3) {
            return 15000;
        }
        if (w == 4) {
            return 800000;
        }
        if (b == 1) {
            return 15;
        }
        if (b == 2) {
            return 400;
        }
        if (b == 3) {
            return 1800;
        }
        if (b == 4) {
            return 100000;
        }
 
        return -1;
    }

④ 实现具体思路:

        如果点击的是"Replay"按钮,调用init()方法重新初始化游戏状态,如果当前是人机对战模式并且轮到机器下棋,则调用automatically()方法让机器自动下棋。最后更新按钮的状态。

        如果点击的是"悔棋"按钮,从coordinate列表中获取最后两个坐标,将对应位置的棋子设置为0(空),并移除这两个坐标。如果是人机对战模式或者机器下棋模式,则再次执行一次悔棋操作。更新oval_type(当前下棋方),并根据情况更新步数和胜负状态。

        如果点击的是"GameOver"按钮,重置游戏状态,设置isStart为0,使得重新选择游戏模式的按钮可用,并禁用其他按钮。

        如果点击的是"排行榜"按钮,创建一个LeaderboardFrame对象,显示排行榜窗口。

        如果点击的是"投降"按钮,根据当前下棋方弹出相应的投降提示框,然后重置游戏状态,使得重新选择游戏模式的按钮可用,并禁用其他按钮。

        如果点击的是"About us"按钮,弹出制作人的信息对话框。

 Object source = e.getSource();
            if (source instanceof JButton){
                JButton jButton = (JButton) e.getSource();
                String text = jButton.getText();
                if ("Replay".equals(text)) {
                    init();

                    if (isStart == 3) {
                        automatically();
                        step++;
                        table[robot_x][robot_y] = 2;
                        oval_type = 1;
                    }
                    regret.setEnabled(false);
                    restart.setEnabled(false);
                } else if ("悔棋".equals(text)) {
                    int x = coordinate.get(coordinate.size() - 2);
                    int y = coordinate.get(coordinate.size() - 1);
                    table[x][y] = 0;
                    coordinate.remove(coordinate.size() - 2);
                    coordinate.remove(coordinate.size() - 1);
                    oval_type = oval_type % 2 + 1;
                    if (isStart == 2 || isStart == 3) {
                        x = coordinate.get(coordinate.size() - 2);
                        y = coordinate.get(coordinate.size() - 1);
                        table[x][y] = 0;
                        coordinate.remove(coordinate.size() - 2);
                        coordinate.remove(coordinate.size() - 1);
                        oval_type = oval_type % 2 + 1;
                    }

                    if (oval_type == 2 || isStart == 3) {
                        step--;
                    }
                    if (win) {
                        win = false;
                    }
                    if (coordinate.size() == 0) {
                        regret.setEnabled(false);
                        restart.setEnabled(false);
                    }
                } else if ("GameOver".equals(text)) {
                    isStart = 0;
                    init();
                    doublePerson.setEnabled(true);
                    singleBlack.setEnabled(true);
                    singleWrite.setEnabled(true);
                    regret.setEnabled(false);
                    restart.setEnabled(false);
                    forExit.setEnabled(false);

                }else if ("排行榜".equals(text)){
                    LeaderboardFrame leaderboardFrame = new LeaderboardFrame();



                } else if ("投降".equals(text)){
                    if (oval_type == 1){
                        JOptionPane.showMessageDialog(null,"白棋投降,游戏结束","",JOptionPane.INFORMATION_MESSAGE);
                    } else if (oval_type == 2){
                        JOptionPane.showMessageDialog(null,"黑棋投降,游戏结束","",JOptionPane.INFORMATION_MESSAGE);
                    }
                    init();
                    isStart = 0;
                    doublePerson.setEnabled(true);
                    singleBlack.setEnabled(true);
                    singleWrite.setEnabled(true);
                    leaderBoard.setEnabled(true);
                    regret.setEnabled(false);
                    restart.setEnabled(false);
                    forExit.setEnabled(false);
                    surrender.setEnabled(false);


                } else if ("About us".equals(text)) {
                    JOptionPane.showMessageDialog(null,"制作人:高晔川  戚彦良  吴沅峰");
                } else {
                    //上面三个按钮
                    if ("双人对战".equals(text)) {
                        isStart = 1;
                    } else if ("人机持白".equals(text)) {  //根据规则,黑棋先下
                        isStart = 2;
                    } else if ("人机持黑".equals(text)) {
                        isStart = 3;
                        automatically();
                        step++;
                        table[robot_x][robot_y] = 2;
                        oval_type = 1;
                    }
                    doublePerson.setEnabled(false);
                    singleBlack.setEnabled(false);
                    singleWrite.setEnabled(false);
                    forExit.setEnabled(true);
                }
                repaint();

同样,人机下棋的代码可以这种代码方式实现:

private void automatically() {
        //传入棋子种类,判断颜色

        int[][] ts = new int[num][num]; //来记录每个点上的得分

        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num; j++) {
                ts[i][j] = 0;
            }
        }

        int wn; //白色个数
        int bn; //黑色个数

        //分4种情况
        //横向
        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num - 4; j++) {
                wn = 0;
                bn = 0;
                //5个
                for (int k = j; k < j + 5; k++) {
                    if (table[i][k] == 1) {
                        wn++;
                    } else if (table[i][k] == 2) {
                        bn++;
                    }
                }
                for (int k = j; k < j + 5; k++) {
                    if (table[i][k] == 0) {
                        ts[i][k] += score(wn, bn);
                    }
                }
            }
        }

        //纵向
        for (int j = 0; j < num; j++) {
            for (int i = 0; i < num - 4; i++) {
                wn = 0;
                bn = 0;
                for (int k = i; k < i + 5; k++) {
                    if (table[k][j] == 1) {
                        wn++;
                    } else if (table[k][i] == 2) {
                        bn++;
                    }
                }
                for (int k = i; k < i + 5; k++) {
                    if (table[k][i] == 0) {
                        ts[k][i] += score(wn, bn);
                    }
                }
            }
        }

        //左上 右下
        for (int i = 0; i < num - 4; i++) {
            for (int j = 0; j < num - 4; j++) {
                wn = 0;
                bn = 0;
                for (int ki = i, kj = j; ki < i + 5; ki++, kj++) {
                    if (table[ki][kj] == 1) {
                        wn++;
                    } else if (table[ki][kj] == 2) {
                        bn++;
                    }
                }
                for (int ki = i, kj = j; ki < i + 5; ki++, kj++) {
                    if (table[ki][kj] == 0) {
                        ts[ki][kj] += score(wn, bn);
                    }
                }
            }
        }

        //右上 左下
        for (int i = 4; i < num; i++) {
            for (int j = 0; j < num - 4; j++) {
                wn = 0;
                bn = 0;
                for (int ki = i, kj = j; kj < j + 5; ki--, kj++) {
                    if (table[ki][kj] == 1) {
                        wn++;
                    } else if (table[ki][kj] == 2) {
                        bn++;
                    }
                }
                for (int ki = i, kj = j; kj < j + 5; ki--, kj++) {
                    if (table[ki][kj] == 0) {
                        ts[ki][kj] += score(wn, bn);
                    }
                }
            }
        }

        Vector<Integer> vv = new Vector<>();
        int max = Integer.MIN_VALUE;

        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num; j++) {
                if (ts[i][j] > max) {
                    max = ts[i][j];
                }
            }
        }
        for (int i = 0; i < num; i++) {
            for (int j = 0; j < num; j++) {
                if (ts[i][j] == max) {
                    vv.add(i);
                    vv.add(j);
                }
            }
        }
        Random random = new Random();
        int r = random.nextInt(vv.size() / 2);
        robot_x = vv.get(r * 2);
        robot_y = vv.get(r * 2 + 1);
        vv.clear();
    }

⑤ 彩蛋:

else if ("About us".equals(text)) {
                    JOptionPane.showMessageDialog(null,"制作人:高晔川  戚彦良  吴沅峰");
                }
······

总结/展望:

       双人对战仅限于本机用户,后期若可以使用server平台,希望可以实现不同设备在局域网之下实现联机对战。

       在五子棋棋盘上不一定智能使用五子棋,也可以本次课题搭建的界面为基础提供围棋的设计算法,将五子棋和围棋的判定标准分别封装为不同的函数,进一步体现封装性思想。

       如果要实现五子棋和围棋的组合应用应该较为容易,前期界面的搭建以及棋子的显示都有共通之处。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
五子棋Java课程设计可以包括以下几个方面的内容: 1. 棋盘的设计:可以使用二维数组来表示棋盘,每个位置可以用不同的值表示空白、黑子或白子。 2. 玩家的交互:可以使用图形界面或者命令行界面来实现玩家与游戏的交互。玩家可以通过鼠标点击或者输入坐标来下棋。 3. 游戏规则的实现:需要实现五子棋的规则,包括判断胜负、判断是否形成五子连线等。 4. AI对战功能(可选):可以实现一个简单的AI来与玩家对战。AI可以根据当前棋盘状态进行决策,选择最优的下棋位置。 5. 游戏结束和重新开始:当游戏结束时,需要显示胜利方并提供重新开始的选项。 以下是一个简单的示例代码,展示了如何实现一个基本的五子棋游戏: ```java public class GomokuGame { private char[][] board; private char currentPlayer; public GomokuGame() { board = new char[15][15]; currentPlayer = 'B'; // 黑方先行 } public void play(int row, int col) { if (board[row][col] == 0) { board[row][col] = currentPlayer; if (checkWin(row, col)) { System.out.println(currentPlayer + " wins!"); // 游戏结束,可以提供重新开始的选项 } else { currentPlayer = (currentPlayer == 'B') ? 'W' : 'B'; } } } private boolean checkWin(int row, int col) { // 检查横向是否有五子相连 int count = 1; for (int i = col - 1; i >= 0 && board[row][i] == currentPlayer; i--) { count++; } for (int i = col + 1; i < 15 && board[row][i] == currentPlayer; i++) { count++; } if (count >= 5) { return true; } // 检查纵向是否有五子相连(类似横向检查) // 检查左上到右下斜线是否有五子相连 // 检查右上到左下斜线是否有五子相连 return false; } } // 使用示例 public class Main { public static void main(String[] args) { GomokuGame game = new GomokuGame(); game.play(7, 7); // 黑方下在(7, 7)位置 game.play(7, 8); // 白方下在(7, 8)位置 // ... } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值