Java五子棋(局域网)

本文介绍了一款使用Java实现的五子棋游戏,包括棋盘界面、棋子类的构建、棋盘存储和落子逻辑。游戏支持局域网对战,通过Socket进行通信,实现玩家间的信息交互。文章详细讲解了读取存档、游戏流程控制以及功能键的实现,同时提供了部分关键代码示例。
摘要由CSDN通过智能技术生成

 

先画出棋盘界面:

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() 
  • 2
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值