先画出棋盘界面:
class Gb extends JFrame implements MouseListener ,Runnable {
BufferedWriter bw = null;
BufferedReader br = null;//文档的流
Chess[] chesses = new Chess[17 * 17];//棋子数组,每个网格一个数组
TreeMap<Integer,Integer> tm = new TreeMap<>();//key为序号 value为坐标对应的数组index
TreeMap<Integer,Integer> tmTemp;//读取进度时用,点击加载覆盖tm
static int width = Toolkit.getDefaultToolkit().getScreenSize().width;
static int height = Toolkit.getDefaultToolkit().getScreenSize().height;//获取屏幕的分辨率
boolean isReading = false;
boolean upCanPress = true;
boolean downCanPress = false;//用于读取数据的三个变量
public Gb() {
this.setTitle("五子棋游戏完成版");
this.setSize(900, 800);
this.setLocation((width - 800) / 2, (height - 800) / 2 );
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);
this.repaint();
this.addMouseListener(this);
}
public void paint(Graphics g) {
BufferedImage buf = new BufferedImage(900, 800, BufferedImage.TYPE_INT_RGB);//确定界面的大小,以及涂色方式为RGB
Graphics g1 = buf.createGraphics();
//选项
//背景和棋盘底色
//http://zhongguose.com/#zhizihuang
g1.setColor(new Color(248,232,193));//背景颜色
g1.fill3DRect(0, 0, 900, 800, true);//绘色:(起始坐标X,起始坐标Y,结束坐标X,结束坐标Y)
g1.setColor(new Color(235,177,13));//棋盘色
g1.fill3DRect(60, 60, 680, 680, true);
//棋盘线
for (int i = 80; i <= 720; i += 40) {
g1.setColor(Color.BLACK);
g1.drawLine(80, i, 720, i);
g1.drawLine(i, 80, i, 720);
}
//右上角黑白棋
//用于提醒下一棋子是什么颜色
if(isBlack) {
g1.setColor(Color.BLACK);
g1.fillOval(770, 50, 30, 30);
}else {
g1.setColor(Color.WHITE);
g1.fillOval(770, 50, 30, 30);
}
//音乐键
//图片在资源文件夹中,打开放在项目文件下
try {
imageMusic= playMusic ? ImageIO.read(new File("performance.png")) : ImageIO.read(new File("volume-mute.png"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
g1.drawImage(imageMusic,820,47,null);
//功能按键
g1.setFont(new Font("微软雅黑",Font.BOLD,15));
g1.setColor(new Color(158,204,171));
g1.fill3DRect(770, 100, 100, 40,true);
g1.fill3DRect(770, 170, 100, 40,true);
g1.fill3DRect(770, 240, 100, 40,true);
g1.fill3DRect(770, 310, 100, 40,true);
g1.fill3DRect(770, 380, 100, 40,true);
//读取功能,点击之后可以读档,相关按键在功能开启后出现
if(isReading) {
g1.fill3DRect(770, 450, 100, 40,upCanPress);
g1.fill3DRect(770, 520, 100, 40,true);
g1.fill3DRect(770, 590, 100, 40,downCanPress);
}
g1.fill3DRect(770, 660, 100, 40,true);
g1.setColor(Color.BLACK);
g1.drawString("重新开始", 780, 125);
g1.drawString("悔棋", 780, 195);
g1.drawString("认输", 780, 265);
g1.drawString("保存进度", 780, 335);
g1.drawString("读取进度", 780, 405);
if(isReading) {
g1.drawString("上一步", 780, 475);
g1.drawString("加载游戏", 780, 545);
g1.drawString("下一步", 780, 615);
}
g1.drawString("返回大厅", 780, 685);
//棋子填充颜色
for(int i = 0 ; i < 17 ; i++ ) {
for(int j = 0 ; j < 17 ; j++) {
if(chesses[ i + j * 17] != null) {
int realX = 70 + i * 40;
int realY = 70 + j * 40;
//判断棋子颜色 true为黑色 false为白色
if (chesses[i + j * 17].isBlack() == true) {
g1.setColor(Color.BLACK);
g1.fillOval(realX, realY, 20, 20);
}else if (chesses[i + j * 17].isBlack() == false) {
g1.setColor(Color.WHITE);
g1.fillOval(realX, realY, 20, 20);
}
}
}
}
}
棋子类的构建:
public class Chess {
private int x;
private int y;
private int orderNum;
private boolean black;
public Chess() {
}
public Chess(int x, int y, int orderNum, boolean color) {
super();
this.x = x;
this.y = y;
this.orderNum = orderNum;
this.black = color;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getOrderNum() {
return orderNum;
}
public void setOrderNum(int orderNum) {
this.orderNum = orderNum;
}
public boolean isBlack() {
return black;
}
public void setBlack(boolean color) {
this.black = color;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Chess other = (Chess) obj;
return black == other.black;
}
}
棋子和棋盘的存储思路:
棋盘是17*17(非标准,当时一拍脑袋决定的),一般来说,用二维数组 chesses[17][17]存储棋盘信息是很符合大脑习惯的,不过二维数组有不方便的地方,直接用chesses[17 * 17]来存储了,其实也很好理解,数组索引对应每个棋子 X + Y * 17(可能需要理解一下),或者由索引反推坐标就是:X = i (数组索引) % 17; Y = i / 17;。
chesses[17 * 17]是棋盘的存储数据,下一个棋子就会在对应索引新建一个棋子对象,并在对应的坐标绘制棋子图像。
落子的存储是用TreeMap,key为落子顺序(1,2,3,4…),value为棋子的一维数组索引。
这样存储的好处是方便读取数据,读取存档的时候很方便。
当然还可以另一个思路:
因为Chess类中申明了落子顺序OrderNum,直接用ArrayList存储落子信息以及棋盘信息,用ArrayList的方法也很方便。
下棋子:
//如果游戏结束,canPlay = false
if(canPlay) {
//下棋子
int x = (e.getX() - 60) / 40;
int y = (e.getY() - 60) / 40;
if (e.getX() >= 80 && e.getY() >= 80 && e.getX() <= 720 && e.getY() <= 720) {
//填充
if(chesses[x + y * 17] == null) {
chesses[x + y * 17] = new Chess(x,y,countNum++,isBlack);
isBlack = !isBlack;
isSaved = false;
//测试:棋子序号 + 横坐标 + 纵坐标
System.out.println(chesses[x + y * 17].getOrderNum() + "..." +chesses[x + y * 17].getX() + "..." + chesses[x + y * 17].getY());
//test
System.out.println(maxLine(chesses[x + y * 17]));
}
tm.put(chesses[x + y * 17].getOrderNum(), x + y * 17);
this.repaint();
}else {
return;
}
//判断胜负
isWin(maxLine(chesses[x + y * 17]));
}
maxLine方法:
米字形判断,每次落子都判断上下左右斜方向的最长连子数。
基本思路:以横向为例,落子后判断索引±4的最长连子,需要注意一个问题,就是棋子在棋盘的边界,需要加一个判断,如果纵坐标变动了(变成另一排),则重新计算maxLine。如果是竖向判断,则需要监控X坐标有无变动;斜向的话,需要监控Y坐标,如果变化为2,重新计算。
//判断最长连子数
public int maxLine(Chess chess) {
int xLine = 1;
int yLine = 1;
int rightLine = 1;
int leftLine = 1;
int max = 1;
int index = chess.getX() + chess.getY() * 17;
//横向判断
for (int i = index - 4 ; i < index + 4 ; i ++) {
if (i > 0 && i < 17*17 - 1 && (i + 1) / 17 - i / 17 == 0 ) {
if(chesses[i] != null && chesses[i].equals(chess) && chesses[i + 1] != null && chesses[i + 1].equals(chess)) {
xLine++;
}else {
xLine = 1;
continue;
}
max = xLine > max ? xLine : max;
}else {
xLine = 1;
}
}
//纵向判断
if (max == 5) {
return 5;
}else {
for (int i = index - 4 * 17 ; i < index + 4 * 17 ; i += 17) {
if (i > 0 && i < 17*17 - 17 && (i + 17) % 17 - i % 17 == 0 ) {
if(chesses[i] != null && chesses[i].equals(chess) && chesses[i + 17] != null && chesses[i + 17].equals(chess)) {
yLine++;
}else {
yLine = 1;
continue;
}
max = yLine > max ? yLine : max;
}else {
yLine = 1;
}
}
}
//斜向\
if(max == 5) {
return 5;
}else {
for (int i = index - 4 * 18; i < index + 4 * 18 ; i += 18) {
if (i > 0 && i < 17*17 - 18 && (i+ 18) / 17 - i / 17 == 1 ) {
if(chesses[i] != null && chesses[i].equals(chess) && chesses[i + 18] != null && chesses[i + 18].equals(chess)) {
leftLine++;
}else {
leftLine = 1;
continue;
}
max = leftLine > max ? leftLine : max;
}else {
leftLine = 1;
}
}
}
// 斜向/
if(max == 5) {
return 5;
}else {
for (int i = index - 4 * 16; i < index + 4 * 16 ; i += 16) {
if (i > 0 && i < 17*17 - 16 && (i+ 16) / 17 - i / 17 == 1 ) {
if(chesses[i] != null && chesses[i].equals(chess) && chesses[i + 16] != null && chesses[i + 16].equals(chess)) {
rightLine++;
}else {
rightLine = 1;
continue;
}
max = rightLine > max ? rightLine : max;
}else {
rightLine = 1;
}
}
}
return max;
}
isWin方法:
//判断是否胜利,输入最长连子数
public void isWin(int i) {
if (i >= 5) {
canPlay = false;
if(isBlack == true) {
JOptionPane.showMessageDialog(this, "白方胜利");
}else {
JOptionPane.showMessageDialog(this, "黑方胜利");
}
}
}
canPlay用来控制能否下棋,游戏判出胜负后就不能继续落子了,需要点悔棋或者重新开始才能继续。
这样就实现了下棋的基本功能了,至于重新开始,悔棋之类的功能,在单机版很容易实现,之后会po出代码,先将一下稍微有点复杂的读取功能:
先看效果图:
选好文档后,(没有保存文档就乱点然后保存一个)就会弹出“上一步”“加载游戏”“下一步”三个功能按键,“上一步” 和 “下一步”是用来读档的,方便知道落子的顺序,同时也可以选中到想要的进度开始游戏。点击加载就可以从当前画面下的棋盘开始一局游戏了。然后,这三个功能键就会消失,等到再次读取进度的时候再出现。
功能键的画图实现代码在绘图中,功能实现代码如下:
if(e.getX() >= 770 && e.getX() <= 870 && e.getY() >= 380 && e.getY() <= 420) {//这里是坐标
int isYes = JOptionPane.showConfirmDialog(this, "是否读取进度","readload",JOptionPane.YES_NO_OPTION);
if(isYes== 0) {
isReading = true;
readLoad();//定义的一个方法,具体在一下代码块里,作用是弹出一个选择文件的框
tmTemp = new TreeMap<>();
for(Map.Entry<Integer, Integer> entry : tm.entrySet()) {
tmTemp.put(entry.getKey(), entry.getValue());
}
}
}
/*
* 读取中的扩展功能
*/
if(isReading) {
canPlay = false;
canPress = false;
int SIZE = tm.size();
int count = tmTemp.size();
//上一步
if(e.getX() >= 770 && e.getX() <= 870 && e.getY() >= 450 && e.getY() <= 490 && upCanPress) {
if(count > 0 ) {
chesses[tmTemp.get(count)] = null;
tmTemp.remove(count-- );
isBlack = !isBlack;
}
//test
System.out.println(tm+" " + SIZE + "\n" +tmTemp + count );
}
//加载
if(e.getX() >= 770 && e.getX() <= 870 && e.getY() >= 520 && e.getY() <= 560) {
isReading = false;
restart();
for(Map.Entry<Integer, Integer> entry : tmTemp.entrySet()) {
tm.put(entry.getKey(), entry.getValue());
chesses[entry.getValue()] = new Chess(entry.getValue() % 17 , entry.getValue() / 17 ,countNum ++ ,isBlack);
isBlack = !isBlack;
isWin(maxLine(chesses[entry.getValue()]));
canPress = true;
}
this.repaint();
}
//下一步
if(e.getX()