提示:本人大二时的java大作业,当时没有学数据库,只是学到界面哪里,所以做出的条件有限,哈哈,看看就好,有帮助了,就拿走,不谢!
Java五子棋
前言
主要就是涉及到java界面编程,实现Runable接口重写run方法,实现多线程,来控制计时,另外加一些java基本语法的使用。
一、项目结构
二、代码
代码如下(示例):
FiveGame.java
package Wuziqi;
import javax.swing.*;//JFrame 、 JOptionPane引入
import java.awt.*;
import java.awt.event.MouseEvent;//鼠标事件 用到getX,getY
import java.awt.event.MouseListener;//鼠标监听
import java.awt.image.BufferedImage;
//JFrame是一个底层的容器
public class FiveGame extends JFrame implements MouseListener,Runnable {
//继承 JFrame类,实现 MouseListener接口 和 Runnable接口
// 使用实现接口Runnable的对象来创建线程
// 获取显示器屏幕大小
int width = Toolkit.getDefaultToolkit().getScreenSize().width;
int height = Toolkit.getDefaultToolkit().getScreenSize().height;
int x, y; // 定义鼠标的坐标
int[][] allChess = new int[17][17]; // 用数组来保存棋子,0表示无子,1表示黑子,2表示白子
boolean isblack= true; //用来表示黑子还是白子, true表示黑子 false表示白子
boolean canPlay = true; // 用来表示当前游戏是否结束
String message = "黑方先行";
String blackMessage = "无限制";
String whiteMessage = "无限制";
int MaxTime=0;//计时器 - 倒计时 设置的时长
Thread t= new Thread(this);//创建线程
int blackT=0;//记录黑方和白方的剩余时间
int whiteT=0;
//保存棋谱,记录双方每一步落子的位置
int[] chessX = new int[255];
int[] chessY = new int[255];
int countX, countY;
//定义标志位 游戏胜负已定 不可执行悔棋
boolean flag1=true;
//未落子时不可执行悔棋,标志位
boolean flag2=false;
public FiveGame() {
//标题
this.setTitle("简易版五子棋");
//窗体大小
this.setSize(600, 600);
//窗体弹出位置始终保证在屏幕中央
this.setLocation((width - 500) / 2, (height - 500) / 2);
//JFrame执行关闭操作时,将退出程序
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗口不可改变,固定窗口大小
this.setResizable(false);
//使窗体可见
this.setVisible(true);
// 刷新页面,并再调用paint()方法
this.repaint();
//打开鼠标事件(按下、释放、单击、进入或离开)的侦听器接口
this.addMouseListener(this);
//开启线程-开始时线程挂起
t.start();
t.suspend();
}
/*
对于轻量级组件,一般是重写 paint 方法以快速的绘制组件,但是对于重量及组件,由于重新绘制时间长,容易产生闪烁的现象,
所以一般是采用重写 update 方法,利用双缓冲图片来解决闪烁的问题。
repaint -> update -> paint
repaint,update和paint这三个方法在Component中定义,由于awt,swing组件都直接或间接继承自Component,
所以几乎所有的awt,swing组件都有这三个方法.
*/
public void paint(Graphics g) {
//双缓冲流,防止屏闪(将信息都缓存在内存信息中,统一画出)
BufferedImage buf = new BufferedImage(600, 600, BufferedImage.TYPE_INT_RGB);
// 创建画笔 在已存在的窗体或控件上绘图
Graphics g1 = buf.createGraphics();
//设置填充颜色 画一个着色块
g1.setColor(new Color(0, 169, 158));
//画突出矩形
g1.fill3DRect(40, 80, 400, 400, true);
/*绘制棋盘16x16*/
for (int i = 0; i <= 16; i++) {
//设置画笔颜色
g1.setColor(Color.WHITE);
g1.drawLine(40, 80 + i * 25, 400 + 40, 80 + i * 25); //画棋盘横线
g1.drawLine(40 + i * 25, 80, 40 + i * 25, 400 + 80); //画棋盘竖线
}
//设计字体显示效果 (String 字体,int 风格,int 字号)
g1.setFont(new Font("黑体", Font.BOLD, 15));//黑体 加粗 15号
g1.drawString("游戏信息:" + message, 50, 60);
// 画黑方时间与白方时间字符串的边框(画矩形)
g1.drawRect(30, 500, 180, 40);
g1.drawRect(250, 500, 180, 40);
g1.setFont(new Font("宋体", 0, 17));
g1.drawString("黑方时间: " + blackMessage, 40, 520);
g1.drawString("白方时间: " + whiteMessage, 260, 520);
//绘制按钮框
g1.setFont(new Font("宋体", 0, 15));
g1.drawRect(500, 70, 80, 35);
g1.drawString("重新开始", 510, 95); //重新开始按钮
g1.drawRect(500, 150, 80, 35);
g1.drawString("游戏设置", 510, 175); //游戏设置按钮
g1.drawRect(500, 230, 80, 35);
g1.drawString("游戏说明", 510, 255); // 游戏说明按钮
g1.drawRect(500, 310, 80, 35);
g1.drawString("退出游戏", 510, 335); // 退出游戏按钮
g1.drawRect(500, 380, 80, 35);
g1.drawString(" 悔棋 ", 510, 405); // 悔棋
g1.drawRect(500, 460, 80, 35);
g1.drawString(" 认输 ", 510, 485); // 认输
g1.drawRect(500, 520, 80, 35);
g1.drawString(" 关于 ", 510, 545); // 关于
//画棋子
for (int i = 0; i <=16; i++) {
for (int j = 0; j <=16; j++) {
//画实心黑子
if (allChess[i][j] == 1) {
int tempX = i * 25 + 40;
int tempY = j * 25 + 80;
g1.setColor(Color.BLACK);
// fillOval 绘制实心椭圆 默认黑色填充
g1.fillOval(tempX - 10, tempY - 10, 16, 16);
}
//画实心白子
if (allChess[i][j] == 2) {
int tempX = i * 25 + 40;
int tempY = j * 25 + 80;
g1.setColor(Color.WHITE);//设置填充颜色
g1.fillOval(tempX - 10, tempY - 10, 16, 16);
g1.setColor(Color.BLACK); //颜色恢复
g1.drawOval(tempX - 10, tempY - 10, 16, 16);
}
}
}
// 将缓存的内容绘制到窗体上
g.drawImage(buf, 0, 0, this);
}
public void mousePressed(MouseEvent e) {
// 鼠标按下
if (canPlay) {
x = e.getX();
y = e.getY(); // 用来获取鼠标坐标
if (x >= 40 && x <= 450 && y >= 80 && y <= 480) {
//让鼠标在棋盘范围内 计算位置在几行几列
if((x-40)%25>12){
x=(x-40)/25 + 1;
}else {
x = (x-40)/25;
}
if((y-80)%25>12){
y=(y-80)/25 + 1;
}else {
y=(y-80)/25;
}
/*该位置未落子时才可以落子*/
if(allChess[x][y]==0) {
flag2=true;
chessX[countX++] = x;
chessY[countY++] = y;
if (isblack) {
allChess[x][y] = 1;
isblack = false;
message="白方落子";
} else {
allChess[x][y] = 2;
isblack = true;
message="黑方落子";
}
// 刷新页面,并再调用paint()方法
this.repaint();
boolean win=this.panwin();
if(win){
t.suspend();//游戏结束,停止计时,线程挂起
JOptionPane.showMessageDialog(this,"游戏结束,"+(allChess[x][y]==1?"黑方":"白方")+"获胜");
canPlay=false;//不可落子
flag1=false;//不可悔棋
}
}
}
}
// 重新开始
if(e.getX()>=499&&e.getX()<=580&&e.getY()>=68&&e.getY()<=110){
int i = JOptionPane.showConfirmDialog(this, "是否重新开始游戏?");
if (i==0){//是
flag2=false;
flag1=true;//可悔棋
canPlay=true;//可落子
//清空棋盘 保存棋子的数组置为0
allChess=new int[17][17];
//还原初值
message="黑方先行";
//黑方落子
isblack=true;
/*时间*/
blackT=MaxTime;
whiteT=MaxTime;
if(MaxTime>0){
blackMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
whiteMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
t.resume();//启动线程,开始倒计时
} else {
blackMessage = "无限制";
whiteMessage = "无限制";
}
/*调用repaint方法 重新绘图*/
this.repaint();
}
}
// 游戏设置
if(e.getX()>=499&&e.getX()<=580&&e.getY()>=150&&e.getY()<=180){
String s = JOptionPane.showInputDialog(this, "请输入游戏时长限制(数值为非负整数," +
"0为无限时间,):(单位:秒)");
try {
MaxTime=Integer.parseInt(s);//将字符串转换为int
if(MaxTime>=0){
int k=-1;
if(MaxTime>0)k = JOptionPane.showConfirmDialog(this, "游戏设置完成是否重新开始游戏?");
if(MaxTime==0)k = JOptionPane.showConfirmDialog(this, "是否确认将游戏时长设为无限制,并重新开始游戏?");
if(k==0){
flag2=false;//未落子时不可悔棋
flag1=true;//可悔棋
canPlay=true;//可落子
//清空棋盘 保存棋子的数组置为0
allChess=new int[17][17];
//还原初值
message="黑方先行";
//黑方落子
isblack=true;
/*时间*/
if(MaxTime>0){
blackT=MaxTime;
whiteT=MaxTime;
blackMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
whiteMessage = MaxTime/3600+":"+(MaxTime/60- MaxTime/3600*60)+":"+(MaxTime-MaxTime/60*60);
t.resume();//启动线程,开始倒计时
}
if(MaxTime==0){
blackMessage = "无限制";
whiteMessage = "无限制";
/*调用repaint方法 重新绘图*/
t.suspend();//线程挂起
}
/*调用repaint方法 重新绘图*/
this.repaint();
}
}
else{
JOptionPane.showMessageDialog(this,"请输入正确信息,不能是负数");
}
}
catch (NumberFormatException e1) {
JOptionPane.showMessageDialog(this,"请输入正确信息!");
}
}
// 游戏说明
if(e.getX()>=499&&e.getX()<=580&&e.getY()>=230&&e.getY()<=262){
JOptionPane.showMessageDialog(this,"黑白棋手轮番执棋子,一" +
"方先在横向纵向或斜向上连成五子者获胜!");
}
// 退出游戏
if(e.getX()>=499&&e.getX()<=580&&e.getY()>=310&&e.getY()<=339){
int result = JOptionPane.showConfirmDialog(this, "是否退出游戏?");
if(result == 0){
System.exit(0);
}
}
// 悔棋
if(flag1&&flag2) {
if (e.getX() >= 499 && e.getX() <= 580 && e.getY() >= 380 && e.getY() <= 415) {
int result = JOptionPane.showConfirmDialog(this,
(isblack ? "白方悔棋,黑方是否同意?" : "黑方悔棋,白方是否同意?"));
if (result == 0) {
allChess[chessX[--countX]][chessY[--countY]] = 0;
if (isblack) {
isblack = false;
} else {
isblack = true;
}
if(countX<=0){
flag2=false;
}
this.repaint(); //重绘棋盘
}
}
}
// 认输
if(e.getX()>=499&&e.getX()<=580&&e.getY()>=462&&e.getY()<=490) {
if (canPlay) {
t.suspend();//挂起线程 点击按钮停止计时
int i = JOptionPane.showConfirmDialog(this, "是否确认认输?");
if (i == 0) {//是
if (isblack) {
JOptionPane.showMessageDialog(this, "黑方认输,游戏结束!");
} else {
JOptionPane.showMessageDialog(this, "白方认输,游戏结束!");
}
canPlay = false;
} else {
t.resume();//不认输,继续计时
}
}
}
// 关于
if(e.getX()>=499&&e.getX()<=580&&e.getY()>=520&&e.getY()<=550){
JOptionPane.showMessageDialog(this,"游戏作者:JAVA二组");
}
}
/*判断输赢*/
private boolean panwin(){//横向-纵向-斜向 五子连珠
boolean flag=false;
int count=0;
int color=allChess[x][y];
count=this.checkCount(1,0,color);//横向找
if(count>=5){
flag=true;
}else {
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;
}
}
}
}
return flag;
}
/**
* 检查棋盘中的五子棋是否连城五子
*/
private int checkCount(int xChange , int yChenge ,int color){
int count = 1;
int tempX = xChange;
int tempy = yChenge; //保存初始值
while((x+xChange)>=0&&(x+xChange)<=16&&(y+yChenge)<=16&&(y+yChenge)>=0&&color==allChess[x+xChange][y+yChenge]){
count++;
if(xChange != 0) xChange++;
if(yChenge != 0 ){
if(yChenge > 0) {
yChenge++;
}else {
yChenge--;
}
}
}
xChange = tempX;
yChenge = tempy; // 恢复初始值
while((x-xChange)>=0&&(x-xChange)<=16&&(y-yChenge)<=16&&(y-yChenge)>=0&&color==allChess[x-xChange][y-yChenge]){
count++;
if(xChange != 0) xChange++;
if(yChenge != 0 ){
if(yChenge > 0) {
yChenge++;
}else {
yChenge--;
}
}
}
return count;
}
/*重写接口中的方法*/
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub 是注释语句,告诉看代码的人,这段代码自动生成。可写可不写
// 鼠标点击判断
}
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
// 鼠标抬起
}
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
// 鼠标进入
}
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
// 鼠标离开
}
/*
实现Runnable 接口
1.定义类实现Runnable 接口
2. 覆盖Runnable 接口中的run方法
(将线程要运行的代码存放在该run方法中)
3. 通过Thread 类建立线程对象
4.将Runnale接口的子类对象作为实际的参数传递给Thread 类的构造函数
(为什么要Runnable 接口的自欸对象传递给Thread的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属的对象)
5.调用Thread 类的start方法开启线程并且调用Runnable 接口的子类的run 方法。*/
@Override
public void run() {
if(MaxTime>0){
while (true){
if(isblack){
blackT--;
if(blackT==0){
canPlay=false;
blackMessage=0+":"+0+":"+0;//时间用完了
this.repaint();
JOptionPane.showMessageDialog(this,"黑方超时,游戏结束!");
flag1=false;
t.suspend();//线程挂起
}
}else {
whiteT--;
if(whiteT==0){
canPlay=false;
whiteMessage=0+":"+0+":"+0;//时间用完了
this.repaint();
JOptionPane.showMessageDialog(this,"白方超时,游戏结束!");
flag1=false;
t.suspend();//线程挂起
}
}
blackMessage = blackT/3600+":"+(blackT/60- blackT/3600*60)+":"+(blackT-blackT/60*60);
whiteMessage = whiteT/3600+":"+(whiteT/60- whiteT/3600*60)+":"+(whiteT-whiteT/60*60);
this.repaint();
try {//睡眠1000ms --1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Main.java
package Wuziqi;
public class Main {
public static void main(String[] args) {
new FiveGame();
}
}
三、效果图
当时写的,所有的按钮都不是真实的button组件,是画上去的。
四、ppt和word文档
找不到上传的方法,此处就省略了。
部分方法的实现思路:
1.绘制棋盘的paint(Graphics g)方法
创建画笔主要使用方法:
setColor()设置画笔颜色
drawLine()画横线
drawString()将指定字符串在指定坐标位置显示
drawRect()画矩形框
fillOval()画实心椭圆
drawOval()画空心椭圆
用双层for循环绘画棋子(二维数组保存棋谱0-无子,1-黑字,2-白子)。
2.五子连珠判断
设计思路:每下一棋子,就以当前棋子为基准进行查找,查找一共分四个路径(横向、纵向、
左上到右下、右上到左下)。
横向查找先找右边(x++,y不变),再找左(x--,y不变)
纵向查找先找下方(x不变,y++),再找上方(x不变,y--)
左上到右下查找先找右下(x++,y++),再找左上(x--,y--)
右上到左下查找先找右上(x++,y--),再找左下(x--,y++)
定义一个整型变量count赋初值为1,一个整型变量color来保存棋子颜色(1为黑棋,2为白
棋),查找到颜色相同的变量值加一,最后查找完时,返回变量值,大于等于5时判赢。
3.悔棋按钮的实现
1).定义了一个二维数组来保存棋谱(0-无棋子,1-黑子,2-白字)。
2).在定义两个一维数组来保存当前所下棋子的坐标(几行几列)。
3).在执行悔棋功能时,就是将之前保存那个棋子位置所赋值变为0,表示无棋子,在调用repaint()方法,重回棋盘达到悔棋的效果。
4.游戏设置按钮的实现
1).用JOptionPane类中的showConfirmDialog()方法获取用户输入。
2).再用Integer.parseInt(s)方法将其转换为int型变量。
3).进行处理,转化为 时:分:秒(字符串型)。
4).其他定义的变量都恢复的初始状态
5).最后调用repaint()方法,将页面刷新。
5.线程实现倒计时
创建线程,让类实现Runnable 接口覆盖接口中的run方法(将线程要运行的代码存放在该run方法中)。
通过Thread 类建立线程对象,将Runnale接口的子类对象作为实际的参数传递给Thread 类的构造函数
调用Thread 类的start方法开启线程并且调用Runnable 接口的子类的run 方法。
线程用到了:
t.start()//开启线程
t.suspend()//线程挂起
t.resume()//启动线程
延时一秒:
try {//睡眠1000ms --1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
总结
对于计算机专业的学生来说,每一次的大作业,还是挺让人兴奋的。用专门安排的时间,做自己想做的事,写自己想要的东西,在这写东西的过程中不断学习,提高自己的认知和知识体系。从遇到bug的无从入手,到网上搜搜改改的豁然开朗,从无到有,这种成就感还是让人很愉快的,哈哈。