此游戏可以适用实训课作业等,功能都已完善,如果导入发现内容显示的位置不合适需要自行调节,时间功能需要在代码里面取消注释才会一直跳动不然是偶尔刷新。
一、实现功能
1、有良好的UI界面,用户体验良好
2、学习如何绘制棋子,鼠标点击即下棋
3、判断输赢
4、点击开始游戏可以清空棋盘,重新开始
5、悔棋功能
6、玩家认输,可以结束游戏,了解如何判断黑还是白认输
7、利用二维数组存储位置,0为空、1为黑、2为白
8、有求和的功能
一、棋盘
棋盘采用15x15的格式来布置
二、开发环境
我所使用的是IntelliJ IDEA 2023.3.4版本开发软件,使用的JDK版本是22。
其项目构成如下图:
二、窗体设计
1、窗体
窗体大小可自己按照喜欢的大小去调节,其中width和height取的是屏幕的宽度和高度。
public GameForm(){
this.addMouseListener(this);//鼠标监听器
this.setTitle("五子棋");//窗体名称
this.setSize(1334,750);//窗体大小
this.setLocation((width - 1334)/2,(height-750)/2);//保持窗体居中位置
this.setResizable(false);//设置窗体不可自由更换大小
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗体关闭后程序停止运行
this.addMouseListener(this);
this.setVisible(true);//显示窗体
}
2、屏幕的大小
取得屏幕的大小
int width = Toolkit.getDefaultToolkit().getScreenSize().width;//取得屏幕的宽度
int height = Toolkit.getDefaultToolkit().getScreenSize().height;//取得屏幕的高度
三、棋盘背景图和绘制棋盘棋子
1、背景导入
背景图素材我会放到最后
public void paint(Graphics g){
//绘制的东西太多会导致屏闪,所以使用双缓冲技术,防止屏闪
BufferedImage bi = new BufferedImage(1334,750,BufferedImage.TYPE_INT_ARGB);//先缓冲到内存的图片上,在给到BufferedImage再通过Graphics显示
Graphics g2 = bi.createGraphics();
//使用try包围将背景图存入内存里
try {
bgImage = ImageIO.read(new File("这里复制自己存放背景图的绝对路径,也就是文件夹路径"));
} catch (IOException e) {
throw new RuntimeException(e);
}
//背景图
g.drawImage(bgImage,0,5,this);
}
2、棋盘横轴和纵轴绘制
/*
绘制棋盘框架
*/
//Y轴X轴
for (int i = 0;i<15;i++){
g2.drawLine(324,44 + 47 * i,981,44 + 47 * i);
g2.drawLine(324 + 47 * i,44,324 + 47 * i,701);
}
//棋盘上的五个点位,从左到右
g2.fillOval(456,175,20,20);
g2.fillOval(456,552,20,20);
g2.fillOval(643,364,20,20);
g2.fillOval(831,176,20,20);
g2.fillOval(832,552,20,20);
3、棋子绘制
这里的47是我每个格子的间隔,324是行的起点位置,其中23是间隔数的一半,也就是棋子的大小
for (int i = 0;i<15;i++){//行
for (int j = 0; j<15;j++){//列
if (allChess[i][j] == 1) {
//黑子
int tempX = i * 47 + 324 + 47;
int tempY = j * 47 + 44 + 47;
g.fillOval(tempX-23,tempY-23,46,46);
}
if (allChess[i] [j] == 2) {
//白子
int tempX = i * 47 + 324 + 47;
int tempY = j * 47 + 44 + 47;
g.setColor(Color.WHITE);
g.fillOval(tempX-23,tempY-23,46,46);
g.setColor(Color.BLACK);
g.drawOval(tempX-23,tempY-23,46,46);
}
}
}
四、棋子坐标存储和按钮的设计
1、存储棋子的坐标
我采用的是二维数组来存棋子坐标,然后再用一个一维数组来存储当时棋子所在的位置用来设计悔棋功能。
//用二维数组保存下过的全部棋子坐标
int[][]allChess = new int[15][15];//15x15的棋盘布局,以0为空1\2为有标准来判断
int [] lastPiece = new int[2];
2、按钮的设计
我这里的按钮并不是组件,而是采用坐标。坐标可以用一个鼠标事件和屏幕的宽和高来取得。
/*
四个游戏内按钮
*/
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 97 && e.getY() <= 154){
int result = JOptionPane.showConfirmDialog(this,"是否重新开始游戏?");
if(result == 0){
//现在可以重新开始游戏,将数组数据全部清零
for (int i = 0;i<15;i++){
for (int j = 0;j<15;j++){
allChess[i][j] = 0;
}
}
//将显示的信息恢复到最初始状态
message = "黑方先行";
isBlack = true;
canPlay = true;
this.repaint();//重新绘制窗体
}
}
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 303 && e.getY() <= 401){
int result = JOptionPane.showConfirmDialog(this,"是否同意对方悔棋?");
if (result == 0){
int x_last = lastPiece[0];
int y_last = lastPiece[1];
int color1 = allChess[x_last][y_last];
allChess[lastPiece[0]][lastPiece[1]] = 0;
if (color1 == 1) {//若为黑子
message = "轮到黑方";
isBlack = true;
}else {
message = "轮到白方";
isBlack = false;
}
canPlay = true;
}
this.repaint();
}
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 437 && e.getY() <= 497 ){
int result = JOptionPane.showConfirmDialog(this,"是否与对方和棋");
if (result == 0){
JOptionPane.showMessageDialog(this,"双方和棋,游戏结束");
}
canPlay = false;
}
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 580 && e.getY() <= 637){
int result = JOptionPane.showConfirmDialog(this,"是否确定认输");
if (result == 0){
if (isBlack){
JOptionPane.showMessageDialog(this,"黑方已经认输,游戏结束");
}else {
JOptionPane.showMessageDialog(this,"白方已经认输,游戏结束");
}
canPlay = false;//认输后不能再走动
}
}
}
五、局势时间记录
/*
局势时间
*/
public void time(){
int hour = 0;
int minute = 0;
int second = 0;
//int totalSecond = hour*3600 + minute*60 + second;
//计时器
int totalSecond = hour + minute + second;
while (totalSecond >= 0){
remainingMinute = (totalSecond % 3600)/60;
remainingSecond = totalSecond % 60;
System.out.println(remainingMinute+ "分钟" +remainingSecond+ "秒");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
totalSecond++;
}
System.out.println("时间到!");
}
六、五连珠的判断
1、横纵向判断
/*
横纵向判断
*/
public boolean checkWin() {
boolean flag=false;
//判断横向的是否有五个棋子,纵坐标是相同的即allChess[X][Y]中Y值是相同的
int color=allChess[x][y];
int count=1;//保存共有多少相同颜色的棋子
count=this.checkCount(1, 0, color);//判断横方向
if(count>=5) { //已经完成5连
flag=true;
}else { //如果不为5连继续判断
//判断纵向
count=this.checkCount(0, 1, color);//纵向
if(count>=5) { //判断纵向是否五连
flag=true;
}else { //否则继续执行
//右上 左下
count=this.checkCount(1, -1, color);//判断右下是否五连
if(count>=5) {
flag=true;
}else {
//左下
count=this.checkCount(-1, 1, color);
if(count>=5) { //判断左下是否五连
flag=true;
}else {
//右下、左上
count=this.checkCount(1, 1, color);
if(count>=5) { //判断右上是否五连
flag=true;
}else {
//左上
count=this.checkCount(-1,-1, color);
if(count>=5) { //判断左上是否五连
flag=true;
}
}
}
}
}
}
return flag;
}
//判断棋子连接的数量
private int checkCount(int xChange,int yChange,int color) {//横向、纵向、和颜色
int count =1;//用来判断数量
int tempX=xChange;//临时变量,用来保存xChange的值
int tempY=yChange;//临时变量,用来保存yChange的值
while(x+xChange>=0 &&x+xChange<=14 && y+yChange>=0 &&y+yChange<=14 && color==allChess[x+xChange][y+yChange]) {
count++;
if(xChange!=0)//如果不等于0让xChange自加
xChange++;
if(yChange!=0) {
if(yChange>0)
yChange++;
else {
yChange--;
}
}
}
xChange=tempY;//返回xChange的值
yChange=tempX;//返回xChange的值
while(color==allChess[x-xChange][y-yChange]) {
count++;
if(xChange!=0)
xChange++;
if(yChange!=0) {
if(yChange>0)
yChange++;
else {
yChange--;
}
}
}
return count;
}
七、战绩保存
因为我们实训没有要求做数据库所以我仅用文本来存储战绩,下面代码调用的时候需要传输存储的字符。
package whole;
/*
文本存储战绩
*/
import java.io.*;
public class Achievements {
public void achievements(String data){
try {
File file = new File("这里填存放的路径//战绩.txt");
if (!file.exists()){
file.createNewFile();//创建新文件,如果有名字一样的直接覆盖
}
FileOutputStream fos = new FileOutputStream(file,true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write(data);
bw.newLine();
bw.flush();
bw.close();
osw.close();
fos.close();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e1){
e1.printStackTrace();
}
}
}
八、完整代码
项目创建按照上面创建在导入其代码即可运行
Main.java
package whole;/*
五子棋双人大战
*/
import javax.imageio.ImageIO;
import javax.swing.JFrame;//Swing窗体类
import java.awt.*;
import javax.swing.JOptionPane;//Swing消息框类
import javax.xml.crypto.Data;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args){
GameForm gf = new GameForm();
gf.time();
}
}
GameForm.java
package whole;
/*
窗体
*/
import javax.imageio.ImageIO;
import java.awt.Graphics;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;//鼠标监听
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import javax.swing.JFrame;
public class GameForm extends JFrame implements MouseListener,Runnable {
int width = Toolkit.getDefaultToolkit().getScreenSize().width;//取得屏幕的宽度
int height = Toolkit.getDefaultToolkit().getScreenSize().height;//取得屏幕的高度
BufferedImage bgImage = null;//背景图
//保存棋子的坐标
int x = 0;
int y = 0;
//判断当前是黑还是白
boolean isBlack = true;
//标识当前游戏是否可以继续
boolean canPlay = true;
//用二维数组保存下过的全部棋子坐标
int[][]allChess = new int[15][15];//15x15的棋盘布局,以0为空1\2为有标准来判断
int [] lastPiece = new int[2];
//保存显示的信息
String message = "黑方先下";
//战绩存放类
Achievements achie = new Achievements();
//时间类
GameTime gameTime = new GameTime();
int remainingMinute;
int remainingSecond;
/*
窗体设置框架
*/
public GameForm(){
this.addMouseListener(this);//鼠标监听器
this.setTitle("五子棋");//窗体名称
this.setSize(1334,750);//窗体大小
this.setLocation((width - 1334)/2,(height-750)/2);//保持窗体居中位置
this.setResizable(false);//设置窗体不可自由更换大小
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置窗体关闭后程序停止运行
this.addMouseListener(this);
this.setVisible(true);//显示窗体
}
/*
背景图设置框架
*/
public void paint(Graphics g){
//绘制的东西太多会导致屏闪,所以使用双缓冲技术,防止屏闪
BufferedImage bi = new BufferedImage(1334,750,BufferedImage.TYPE_INT_ARGB);//先缓冲到内存的图片上,在给到BufferedImage再通过Graphics显示
Graphics g2 = bi.createGraphics();
//使用try包围将背景图存入内存里
try {
bgImage = ImageIO.read(new File("这里填背景图存放的路径/backbag.jpg"));
} catch (IOException e) {
throw new RuntimeException(e);
}
//文字信息
g.drawImage(bgImage,0,5,this);
g.setFont(new Font("简体字",Font.BOLD,25));
g.drawString("游戏信息:" + message,25,300);
g.setFont(new Font("简体字",Font.BOLD,25));
//时间信息
g.drawString("黑方时间:无限制",90,380);
g.drawString("白方时间:无限制",90,485);
g.drawString(String.valueOf("分" + remainingMinute +"秒" + remainingSecond),180,120);
//this.repaint();//取消注释即可解锁动态刷新对局时间
/*
绘制棋盘框架
*/
//Y轴X轴
for (int i = 0;i<15;i++){
g2.drawLine(324,44 + 47 * i,981,44 + 47 * i);
g2.drawLine(324 + 47 * i,44,324 + 47 * i,701);
}
//棋盘上的五个点位,从左到右
g2.fillOval(456,175,20,20);
g2.fillOval(456,552,20,20);
g2.fillOval(643,364,20,20);
g2.fillOval(831,176,20,20);
g2.fillOval(832,552,20,20);
for (int i = 0;i<15;i++){//行
for (int j = 0; j<15;j++){//列
if (allChess[i][j] == 1) {
//黑子
int tempX = i * 47 + 324 + 47;
int tempY = j * 47 + 44 + 47;
g.fillOval(tempX-23,tempY-23,46,46);
}
if (allChess[i] [j] == 2) {
//白子
int tempX = i * 47 + 324 + 47;
int tempY = j * 47 + 44 + 47;
g.setColor(Color.WHITE);
g.fillOval(tempX-23,tempY-23,46,46);
g.setColor(Color.BLACK);
g.drawOval(tempX-23,tempY-23,46,46);
}
}
}
g2.drawImage(bi,0,5,this);
}
/*
局势时间
*/
public void time(){
int hour = 0;
int minute = 0;
int second = 0;
//int totalSecond = hour*3600 + minute*60 + second;
//计时器
int totalSecond = hour + minute + second;
while (totalSecond >= 0){
remainingMinute = (totalSecond % 3600)/60;
remainingSecond = totalSecond % 60;
System.out.println(remainingMinute+ "分钟" +remainingSecond+ "秒");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
totalSecond++;
}
System.out.println("时间到!");
}
/*
鼠标监听事件框架
*/
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
if (canPlay == true) {
x = e.getX();
y = e.getY();
if (x >= 324 && x <= 981 && y >= 44 && y <= 701) {
x = (x - 324) / 47;
y = (y - 70) / 47;
//记录棋子位置
lastPiece[0] = x;
lastPiece[1] = y;
//判断现在是什么颜色的棋子
if (allChess[x][y] == 0) {
if (isBlack == true) {
allChess[x][y] = 1;
isBlack = false;
message = "轮到白方";
} else {
allChess[x][y] = 2;
isBlack = true;
message = "轮到黑方";
}
boolean winFlang = this.checkWin();
if (winFlang == true) {
JOptionPane.showMessageDialog(this,
"游戏结束," + (allChess[x][y] == 1 ? "黑" : "白") + "获胜");
canPlay = false;
if (winFlang == true)
achie.achievements(allChess[x][y] == 1 ? "黑方获胜" : "白方获胜");
}
}
this.repaint();//重新执行一次paint
}
}
/*
四个游戏内按钮
*/
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 97 && e.getY() <= 154){
int result = JOptionPane.showConfirmDialog(this,"是否重新开始游戏?");
if(result == 0){
//现在可以重新开始游戏,将数组数据全部清零
for (int i = 0;i<15;i++){
for (int j = 0;j<15;j++){
allChess[i][j] = 0;
}
}
//将显示的信息恢复到最初始状态
message = "黑方先行";
isBlack = true;
canPlay = true;
this.repaint();//重新绘制窗体
}
}
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 303 && e.getY() <= 401){
int result = JOptionPane.showConfirmDialog(this,"是否同意对方悔棋?");
if (result == 0){
int x_last = lastPiece[0];
int y_last = lastPiece[1];
int color1 = allChess[x_last][y_last];
allChess[lastPiece[0]][lastPiece[1]] = 0;
if (color1 == 1) {//若为黑子
message = "轮到黑方";
isBlack = true;
}else {
message = "轮到白方";
isBlack = false;
}
canPlay = true;
}
this.repaint();
}
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 437 && e.getY() <= 497 ){
int result = JOptionPane.showConfirmDialog(this,"是否与对方和棋");
if (result == 0){
JOptionPane.showMessageDialog(this,"双方和棋,游戏结束");
}
canPlay = false;
}
if (e.getX() >= 1105 && e.getX() <=1295 && e.getY() >= 580 && e.getY() <= 637){
int result = JOptionPane.showConfirmDialog(this,"是否确定认输");
if (result == 0){
if (isBlack){
JOptionPane.showMessageDialog(this,"黑方已经认输,游戏结束");
}else {
JOptionPane.showMessageDialog(this,"白方已经认输,游戏结束");
}
canPlay = false;//认输后不能再走动
}
}
}
/*
横纵向判断
*/
public boolean checkWin() {
boolean flag=false;
//判断横向的是否有五个棋子,纵坐标是相同的即allChess[X][Y]中Y值是相同的
int color=allChess[x][y];
int count=1;//保存共有多少相同颜色的棋子
count=this.checkCount(1, 0, color);//判断横方向
if(count>=5) { //已经完成5连
flag=true;
}else { //如果不为5连继续判断
//判断纵向
count=this.checkCount(0, 1, color);//纵向
if(count>=5) { //判断纵向是否五连
flag=true;
}else { //否则继续执行
//右上 左下
count=this.checkCount(1, -1, color);//判断右下是否五连
if(count>=5) {
flag=true;
}else {
//左下
count=this.checkCount(-1, 1, color);
if(count>=5) { //判断左下是否五连
flag=true;
}else {
//右下、左上
count=this.checkCount(1, 1, color);
if(count>=5) { //判断右上是否五连
flag=true;
}else {
//左上
count=this.checkCount(-1,-1, color);
if(count>=5) { //判断左上是否五连
flag=true;
}
}
}
}
}
}
return flag;
}
//判断棋子连接的数量
private int checkCount(int xChange,int yChange,int color) {//横向、纵向、和颜色
int count =1;//用来判断数量
int tempX=xChange;//临时变量,用来保存xChange的值
int tempY=yChange;//临时变量,用来保存yChange的值
while(x+xChange>=0 &&x+xChange<=14 && y+yChange>=0 &&y+yChange<=14 && color==allChess[x+xChange][y+yChange]) {
count++;
if(xChange!=0)//如果不等于0让xChange自加
xChange++;
if(yChange!=0) {
if(yChange>0)
yChange++;
else {
yChange--;
}
}
}
xChange=tempY;//返回xChange的值
yChange=tempX;//返回xChange的值
while(color==allChess[x-xChange][y-yChange]) {
count++;
if(xChange!=0)
xChange++;
if(yChange!=0) {
if(yChange>0)
yChange++;
else {
yChange--;
}
}
}
return count;
}
@Override
public void mouseReleased(MouseEvent mouseEvent) {
}
@Override
public void mouseEntered(MouseEvent mouseEvent) {
}
@Override
public void mouseExited(MouseEvent mouseEvent) {
}
/*
线程方法
*/
@Override
public void run() {
}
}
Achievements.java
package whole;
/*
文本存储战绩
*/
import java.io.*;
public class Achievements {
public void achievements(String data){
try {
File file = new File("这里填绝对路径//战绩.txt");
if (!file.exists()){
file.createNewFile();//创建新文件,如果有名字一样的直接覆盖
}
FileOutputStream fos = new FileOutputStream(file,true);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write(data);
bw.newLine();
bw.flush();
bw.close();
osw.close();
fos.close();
}catch (FileNotFoundException e){
e.printStackTrace();
}catch (IOException e1){
e1.printStackTrace();
}
}
}
需要做数据库的数据库链接方法在另一篇文章点击主页可以找到
https://blog.csdn.net/m0_62304704/article/details/140065293?spm=1001.2014.3001.5502
完整项目和素材在下面链接
阿里云盘链接: https://www.alipan.com/s/LqaSJ7VkeiH
提取码: ry40
版权归原作者 不会敲代码的马铃薯头小鬼 所有, 如有侵权,请联系我们删除。