前言
对于初学者,使用java开发五子棋游戏可以很好的帮助我们巩固相关基础知识。主要概括为两部分:1、java GUI中学会容器间的排版布局,跳转交互,容器中图像(棋盘、棋子)的绘制。2、简单数据结构与算法抽象,学会将五子棋进行抽象用已有或自定的数据结构进行表示,悔棋功能使用的栈,设计ai算法,逻辑判断。
一、界面设计
考虑将界面分为两个部分,一是悔棋、认输、模式选择等按钮的控制设置部分。二是显示棋盘棋子,玩家进行点击下棋的棋盘部分。
二、具体实现
1.棋盘部分
棋盘部分作为玩家主要进行下棋操作的界面其业务逻辑主要有:
1、棋盘、棋子的绘制与下棋、游戏结束等事件后棋子、棋盘的重绘
2、监听鼠标点击事件,记录棋子的位置信息,并判断是否满足胜利条件,及对应的消息提示
3、ai的设计
常量接口代码:
/**
* 常量接口,进项类之间的常量共享
* @author 点子渣手
*
*/
public interface ChessConfig {
//X1,Y1 绘制棋盘在容器中的相对初始位置
public static final int X1 = 10;
public static final int Y1 = 50;
//SIZE为棋子直径或棋盘中正方形格子边长
public static final int SIZE = 40;
//LINE为棋盘横竖线的数量
public static final int LINE = 18;
}
棋盘类代码:
/**
* 棋盘类继承JPanel容器,实现常量接口
* @author 点子渣手
*
*/
public class ChessBoard extends JPanel implements ChessConfig{
//二维数组记录棋盘中棋子的情况, -1:无棋子 0:黑棋 1:白棋
private int[][] chessLog;
//整型变量标记游戏是否开始
private int start;
//棋子类(有x,y二维数组坐标值属性)的栈用于悔棋的回溯记录
private Stack<Chess> chessStack;
public ChessBoard() {
setSize(800, 800);
setVisible(true);
init();
}
//初始化
public void init() {
chessLog = new int[LINE][LINE];
for (int i = 0; i < chessLog.length; i++) {
for (int j = 0; j < chessLog[i].length; j++) {
chessLog[i][j] = -1;
}
}
chessStack = new Stack<>();
start = 0;
}
public Stack<Chess> getChessStack(){
return this.chessStack;
}
public int[][] getChessLog() {
return this.chessLog;
}
public void setChessLog(int[][] chessLog) {
this.chessLog = chessLog;
}
public int getStart() {
return this.start;
}
public void setStart(int start) {
this.start = start;
}
//重写paint方法,绘制棋盘棋子,并可在其它类中通过画笔g或repaint方法重绘
@Override
public void paint(Graphics g) {
super.paint(g);
//绘制棋盘
for(int i=0;i<LINE;i++){
g.drawLine(X1,Y1+i*SIZE, X1+(LINE-1)*SIZE, Y1+i*SIZE);
g.drawLine(X1+i*SIZE,Y1, X1+i*SIZE, Y1+(LINE-1)*SIZE);
}
//绘制棋子 ,二维数组中 0:黑棋 1:白棋
for (int i = 0; i < chessLog.length; i++) {
for (int j = 0; j < chessLog[i].length; j++) {
if (chessLog[i][j] == 0) {
//绘制带有光泽效果的黑棋子
for(int k=0;k<36;k++){
g.setColor(new Color(k*3, k*3, k*3));
g.fillOval(X1+i*SIZE-(SIZE-k)/2, Y1+j*SIZE-(SIZE-k)/2, SIZE-k, SIZE-k);
}
}else if(chessLog[i][j] == 1){
//绘制带有光泽的白棋子
for(int k=0;k<36;k++){
g.setColor(new Color(145+k*3, 145+k*3, 145+k*3));
g.fillOval(X1+i*SIZE-(SIZE-k)/2, Y1+j*SIZE-(SIZE-k)/2, SIZE-k, SIZE-k);
}
}else {
continue;
}
}
}
}
}
这里特别说明一下带有光泽的棋子的画法:
通过循环在棋子落点的中心处画越来越小的圆形,并让这些圆的颜色:当黑棋=>由黑变灰 当白棋=>由灰变白。要注意fillOval方法是以传入的x,y值为圆形所在最小正方形的左上角坐标,所以想要逐渐变小的圆的中心点保持在棋子落点需要减去当前棋子直径的一半
鼠标监听器类代码:
/**
* 棋盘部分鼠标监听器类
* 主要作用:
* 1.判断并记录鼠标点击后棋子位置
* 2.判断是否满足胜利条件
* 3.调用简单的ai方法
* @author 点子渣手
*
*/
public class ChessMouse extends MouseAdapter implements ChessConfig{
private ChessBoard cb; //ChessBoard类对象,用于repaint重绘以及作为消息弹窗的父窗体
private Graphics g; //画笔对象,由ChessBoard类对象传入
private int[][] chessLog; //二维数组记录棋盘中棋子的情况, -1:无棋子 0:黑棋 1:白棋 由ChessBoard类对象传入
private Stack<Chess> chessStack; //棋子类(有x,y二维数组坐标值属性)的栈用于悔棋的回溯记录 由ChessBoard类对象传入
private int x1,y1; //用于保存触发鼠标点击事件后的坐标值
private int lock = 0; //棋子颜色的锁标志符,默认为0 下黑棋,1 下白棋
private int[][] chessValue; //ai相关,用于记录所有落棋点的权重大小
private HashMap<String,Integer> hm; //ai相关,用于保存棋局的权重表
private int aiEnable = 0; //ai相关,有否启用ai的标志符,默认为0 不启用
//构造函数初始化
public ChessMouse() {
chessValue = new int[LINE][LINE];
hm = new HashMap<>();
hm.put("0", 5);
hm.put("00", 50);
hm.put("000", 100);
hm.put("0000", 200);
hm.put("01", 3);
hm.put("001", 40);
hm.put("0001", 80);
hm.put("00001", 200);
hm.put("1", 5);
hm.put("11", 50);
hm.put("111", 100);
hm.put("1111", 200);
hm.put("10", 3);
hm.put("110", 40);
hm.put("1110", 80);
hm.put("11110", 200);
}
public void setChessBoard(ChessBoard cb) {
this.cb = cb;
}
public void setGraphics(Graphics g) {
this.g = g;
}
public void setChessLog(int[][] chessLog) {
this.chessLog = chessLog;
}
public void setChessStack(Stack<Chess> chessStack) {
this.chessStack = chessStack;
}
public void setAiEnable(int aiEnable) {
this.aiEnable = aiEnable;
}
public int getLock() {
return this.lock;
}
//鼠标点击的处理方法
public void mouseClicked(MouseEvent e) {
//获取开始游戏标志符,为0则弹出未开始提示弹窗
if (cb.getStart() == 0) {
JOptionPane.showMessageDialog(cb, "未开始游戏");
return;
}
//x1,y1保存鼠标点击位置坐标值
x1 = e.getX();
y1 = e.getY();
//numX,numY保存点击位置与上一棋子落点(非边界左上最近落点,边界左或上最近落点)的距离
int numX = Math.abs(x1-X1)%SIZE;
int numY = Math.abs(y1-Y1)%SIZE;
//countX,countY表示根据边界值(棋子大小的一半)是否在横竖方向上归为下一落点
int countX = 0;
int countY = 0;
if (numX < SIZE/2) {
if (numY <SIZE/2) {
countX = 0;
countY = 0;
}else {
countX = 0;
countY = 1;
}
}else {
if (numY < SIZE/2) {
countX = 1;
countY = 0;
}else {
countX = 1;
countY = 1;
}
}
//posX,posY为最终棋子落点坐标
int posX = (x1-X1)/SIZE+countX;
int posY = (y1-Y1)/SIZE+countY;
//点击位置超出棋盘大小判定无效,不进行任何操作
if (posX > 17 || posX < 0 || posY > 17 || posY < 0) {
return;
}
//是否已有棋子的判定
if (chessLog[posX][posY] == 0 || chessLog[posX][posY] == 1) {
JOptionPane.showMessageDialog(cb, "此位置已被占用");
return;
}
//调用下棋方法
setChess(posX, posY);
//如果启用ai调用ai方法
if (aiEnable == 1) {
ai();
}
}
//下棋方法
public void setChess(int posX,int posY) {
//0:黑棋 1:白棋
switch (lock) {
case 0: {
chessLog[posX][posY] = 0;
lock = 1;
break;
}case 1:{
chessLog[posX][posY] = 1;
lock = 0;
break;
}
default:
throw new IllegalArgumentException("Unexpected value: " + lock);
}
//下棋记录入栈,便于悔棋回溯
Chess chess = new Chess(posX, posY);
chessStack.push(chess);
//重绘
cb.repaint();
//调用判断输赢方法
checkWin(chessLog);
}
//判断输赢
public void checkWin(int chessLog[][]) {
//0:黑棋 1:白棋
//横向相连计数
int countsX0 = 0;
int countsX1 = 0;
//竖向相连计数
int countsY0 = 0;
int countsY1 = 0;
//右斜向相连计数
int countsXYR0 = 0;
int countsXYR1 = 0;
int countsYXR0 = 0;
int countsYXR1 = 0;
//左斜向相连计数
int countsXYL0 = 0;
int countsXYL1 = 0;
int countsYXL0 = 0;
int countsYXL1 = 0;
//横竖相连判断
for (int i = 0; i < chessLog.length; i++) {
for (int j = 0; j < chessLog[i].length; j++) {
//判断竖向相连
if (chessLog[i][j] == 0) {
countsY1 = 0;
countsY0 ++;
if (countsY0 == 5) {
break;
}
}else if (chessLog[i][j] == 1) {
countsY0 = 0;
countsY1 ++;
if (countsY1 == 5) {
break;
}
}else {
countsY0 = 0;
countsY1 = 0;
}
//判断横向相连
if (chessLog[j][i] == 0) {
countsX1 = 0;
countsX0 ++;
if (countsX0 == 5) {
break;
}
}else if (chessLog[j][i] == 1) {
countsX0 = 0;
countsX1 ++;
if (countsX1 == 5) {
break;
}
}else {
countsX0 = 0;
countsX1 = 0;
}
}
if (countsX0 == 5 || countsX1 == 5|| countsY0 == 5|| countsY1 == 5) {
break;
}
countsX0 = 0;
countsX1 = 0;
countsY0 = 0;
countsY1 = 0;
}
for (int i = 0; i < chessLog.length; i++) {
for (int j = 0; j < chessLog.length - i; j++) {
//右斜向
if (chessLog[j][j+i] == 0) {
countsXYR1 = 0;
countsXYR0 ++;
if (countsXYR0 == 5) {
break;
}
}else if (chessLog[j][j+i] == 1) {
countsXYR0 = 0;
countsXYR1 ++;
if (countsXYR1 == 5) {
break;
}
}else {
countsXYR0 = 0;
countsXYR1 = 0;
}
if (chessLog[j+i][j] == 0) {
countsYXR1 = 0;
countsYXR0 ++;
if (countsYXR0 == 5) {
break;
}
}else if (chessLog[j+i][j] == 1) {
countsYXR0 = 0;
countsYXR1 ++;
if (countsYXR1 == 5) {
break;
}
}else {
countsYXR0 = 0;
countsYXR1 = 0;
}
//左斜向
if (chessLog[j][chessLog.length-1-(j+i)] == 0) {
countsXYL1 = 0;
countsXYL0 ++;
if (countsXYL0 == 5) {
break;
}
}else if (chessLog[j][chessLog.length-1-(j+i)] == 1) {
countsXYL0 = 0;
countsXYL1 ++;
if (countsXYL1 == 5) {
break;
}
}else {
countsXYL0 = 0;
countsXYL1 = 0;
}
if (chessLog[j+i][chessLog.length-1-j] == 0) {
countsYXL1 = 0;
countsYXL0 ++;
if (countsYXL0 == 5) {
break;
}
}else if (chessLog[j+i][chessLog.length-1-j] == 1) {
countsYXL0 = 0;
countsYXL1 ++;
if (countsYXL1 == 5) {
break;
}
}else {
countsYXL0 = 0;
countsYXL1 = 0;
}
}
if (countsXYR0 == 5 || countsXYR1 == 5 || countsYXR0 == 5 || countsYXR1 == 5
|| countsXYL0 == 5 || countsXYL1 == 5 || countsYXL0 == 5 || countsYXL1 == 5) {
break;
}
countsXYR0 = 0;
countsXYR1 = 0;
countsYXR0 = 0;
countsYXR1 = 0;
countsXYL0 = 0;
countsXYL1 = 0;
countsYXL0 = 0;
countsYXL1 = 0;
}
if (countsX0 == 5 || countsY0 == 5 || countsXYR0 == 5 || countsYXR0 == 5 || countsXYL0 == 5 || countsYXL0 == 5) {
JOptionPane.showMessageDialog(cb, "黑棋胜利");
reset();
}else if (countsX1 == 5 || countsY1 == 5 || countsXYR1 == 5 || countsYXR1 == 5 || countsXYL1 == 5 || countsYXL1 == 5) {
JOptionPane.showMessageDialog(cb, "白棋胜利");
reset();
}
}
//游戏结束后重置
public void reset() {
for (int i = 0; i < LINE; i++) {
for (int j = 0; j < LINE; j++) {
chessLog[i][j] = -1;
chessValue[i][j] = 0;
}
}
cb.repaint();
lock = 0;
cb.setStart(0);
chessStack.clear();
aiEnable = 0;
}
public void ai() {
for (int i = 0; i < chessLog.length; i++) {
for (int j = 0; j < chessLog[i].length; j++) {
if (chessLog[i][j] == -1) {
String code = "";
int color = -1;
//向右
for(int k=i+1;k<chessLog.length;k++){
if(chessLog[k][j] == -1){ //判断右边是否为空
break;
}else{
if(color == -1){ //右边第一颗棋子颜色
color = chessLog[k][j]; //记录棋子颜色
code += chessLog[k][j]; //记录棋局
}else if(chessLog[k][j] == color){
code += chessLog[k][j]; //记录棋局
}else{
code += chessLog[k][j]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
Integer value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
//向左
for(int k=i-1;k>=0;k--){
if(chessLog[k][j] == -1){ //判断左边是否为空
break;
}else{
if(color == -1){ //左边第一颗棋子颜色
color = chessLog[k][j]; //记录棋子颜色
code += chessLog[k][j]; //记录棋局
}else if(chessLog[k][j] == color){
code += chessLog[k][j]; //记录棋局
}else{
code += chessLog[k][j]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
//向上
for(int k=j-1;k>=0;k--){
if(chessLog[i][k] == -1){ //判断上边是否为空
break;
}else{
if(color == -1){ //上边第一颗棋子颜色
color = chessLog[i][k]; //记录棋子颜色
code += chessLog[i][k]; //记录棋局
}else if(chessLog[i][k] == color){
code += chessLog[i][k]; //记录棋局
}else{
code += chessLog[i][k]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
//向下
for(int k=j+1;k<chessLog.length;k++){
if(chessLog[i][k] == -1){ //判断下边是否为空
break;
}else{
if(color == -1){ //下边第一颗棋子颜色
color = chessLog[i][k]; //记录棋子颜色
code += chessLog[i][k]; //记录棋局
}else if(chessLog[i][k] == color){
code += chessLog[i][k]; //记录棋局
}else{
code += chessLog[i][k]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
//斜向左上
int temp =0;
if (i > j) {
temp = j;
}else {
temp = i;
}
for(int k=1;k<=temp;k++){
if(chessLog[i-k][j-k] == -1){ //判断斜向左上是否为空
break;
}else{
if(color == -1){ //斜向左上第一颗棋子颜色
color = chessLog[i-k][j-k]; //记录棋子颜色
code += chessLog[i-k][j-k]; //记录棋局
}else if(chessLog[i-k][j-k] == color){
code += chessLog[i-k][j-k]; //记录棋局
}else{
code += chessLog[i-k][j-k]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
temp =0;
//斜向右下
if (i > j) {
temp = i;
}else {
temp = j;
}
for(int k=1;k<chessLog.length-temp;k++){
if(chessLog[i+k][j+k] == -1){ //判断斜向右下是否为空
break;
}else{
if(color == -1){ //斜向右下第一颗棋子颜色
color = chessLog[i+k][j+k]; //记录棋子颜色
code += chessLog[i+k][j+k]; //记录棋局
}else if(chessLog[i+k][j+k] == color){
code += chessLog[i+k][j+k]; //记录棋局
}else{
code += chessLog[i+k][j+k]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
temp =0;
//斜向右上
if (chessLog.length-1-i > j) {
temp = j;
}else {
temp = chessLog.length-1-i;
}
for(int k=1;k<=temp;k++){
if(chessLog[i+k][j-k] == -1){ //判断斜向左上是否为空
break;
}else{
if(color == -1){ //斜向左上第一颗棋子颜色
color = chessLog[i+k][j-k]; //记录棋子颜色
code += chessLog[i+k][j-k]; //记录棋局
}else if(chessLog[i+k][j-k] == color){
code += chessLog[i+k][j-k]; //记录棋局
}else{
code += chessLog[i+k][j-k]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
code = "";
color = -1;
temp =0;
//斜向左下
if ( i > chessLog.length-1-j) {
temp = chessLog.length-1-j;
}else {
temp = i;
}
for(int k=1;k<=temp;k++){
if(chessLog[i-k][j+k] == -1){ //判断斜向左上是否为空
break;
}else{
if(color == -1){ //斜向左上第一颗棋子颜色
color = chessLog[i-k][j+k]; //记录棋子颜色
code += chessLog[i-k][j+k]; //记录棋局
}else if(chessLog[i-k][j+k] == color){
code += chessLog[i-k][j+k]; //记录棋局
}else{
code += chessLog[i-k][j+k]; //记录棋局
break;
}
}
}
//根据保存的棋局取出对应的权值,权值累加
value = hm.get(code);
if(value != null){
chessValue[i][j]+=value;
}
}
}
}
//找到ChessValue中最大值
int posX = 0;
int posY = 0;
int temp = chessValue[0][0];
for (int m = 0; m < chessValue.length; m++) {
for (int n = 0; n < chessValue[m].length; n++) {
if (chessValue[m][n] > temp) {
temp = chessValue[m][n];
posX = m;
posY = n;
}
chessValue[m][n] = 0;
}
}
//在权重最大值位置下棋
setChess(posX, posY);
}
}
输赢检测的方法:
就是对二维数组进行不同方向上的遍历,注意棋子颜色变换后要对记录值清零,左右斜向的遍历要注意边界值分析。
简单ai的设计:
由于我们对棋子的记录是由二维数组保存的,0为黑棋、1为白棋,那么对单一方向上的棋子相连情况(棋局),我们可以用一维或线性的数字串来表示,比如:000,表示三个黑棋相连。通过这种思想,我们可以将某一空位棋子落点周边的棋局情况分为16种,统一通过000,001这样字符串表示,并设立16种棋局对应的权重表,在获取空位棋子落点一个方向的棋局字符串后,通过HashMap取出对应的权值并保存到对应位置的chessValue中,累加直到完成空位棋子落点周围8个方向的棋局检测:上,下,左,右,斜向左上,斜向右上,斜向左下,斜向右下。最后遍历二位数组chessValue,其中值最大的点为ai将要下棋的点。
以下是本例中所使用的权值表,可进行更改以达到更好的效果
2.程序入口与控制设置部分
这一部分代码比较简单,主要是界面的设置和按钮与功能的关联,较为关键的是悔棋功能主要在按钮监听器类中实现
程序入口类代码
/**
* 程序入口
* 主要作用:
* 1.总体界面与布局设置
* 2.控制设置部分布局与组件及监听器等配置
* 3.类对象之间属性对象传递
*
* @author 33297
*
*/
public class Game extends JFrame implements ChessConfig{
public static void main(String[] args) {
Game game = new Game();
game.initUI();
}
public void initUI() {
setSize(950, 900);
setLayout(new BorderLayout());
setTitle("棋类游戏");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
//主容器采用边框布局
setLayout(new BorderLayout());
//右侧棋盘部分
ChessBoard chessBoard = new ChessBoard();
add(chessBoard,BorderLayout.CENTER);
//棋盘类中传递的对象
Graphics g = chessBoard.getGraphics();
int[][] chessLog = chessBoard.getChessLog();
Stack<Chess> chessStack = chessBoard.getChessStack();
//左侧选项栏
JPanel jPanel = new JPanel();
jPanel.setPreferredSize(new Dimension(150,0));
jPanel.setLayout(new FlowLayout());
//选项栏监听器配置
ButtonListener buttonListener = new ButtonListener();
buttonListener.setChessLog(chessLog);
buttonListener.setChessBoard(chessBoard);
buttonListener.setChessStack(chessStack);
//点击按钮配置
String[] typeArray = { "开始", "悔棋", "认输" };
for (int i = 0; i < typeArray.length; i++) {
JButton btn = new JButton(typeArray[i]);
btn.setPreferredSize(new Dimension(140,40));
//选项栏按钮添加按钮监听器
btn.addActionListener(buttonListener);
jPanel.add(btn);
}
//单选按钮配置
JRadioButton rb1 = new JRadioButton("PvP",true);
JRadioButton rb2 = new JRadioButton("PvE");
rb1.setActionCommand("PvP");
rb2.setActionCommand("PvE");
ButtonGroup group=new ButtonGroup();
group.add(rb1);
group.add(rb2);
//选项栏单选按钮组传递到按钮监听器
buttonListener.setButtonGroup(group);
jPanel.add(rb1);
jPanel.add(rb2);
add(jPanel,BorderLayout.WEST);
//鼠标监听器配置
ChessMouse mouse = new ChessMouse();
mouse.setChessBoard(chessBoard);
mouse.setGraphics(g);
mouse.setChessLog(chessLog);
mouse.setChessStack(chessStack);
buttonListener.setChessMouse(mouse);;
//给棋盘添加鼠标监听器
chessBoard.addMouseListener(mouse);
}
}
按钮监听器类代码
public class ButtonListener implements ActionListener,ChessConfig{
private ChessBoard cb; //ChessBoard类对象,用于repaint重绘以及作为消息弹窗的父窗体
private int[][] chessLog; //二维数组记录棋盘中棋子的情况, -1:无棋子 0:黑棋 1:白棋 由ChessBoard类对象传入
private Stack<Chess> chessStack; //棋子类(有x,y二维数组坐标值属性)的栈用于悔棋的回溯记录 由ChessBoard类对象传入
private ButtonGroup group; //单选按钮组,用于获取值来判定游戏模式
private ChessMouse chessMouse; //ChessMouse类对象,用于设置ai开启,以及人机对战模式中ai先手下棋
public void setChessStack(Stack<Chess> chessStack) {
this.chessStack = chessStack;
}
public void setChessBoard(ChessBoard cb) {
this.cb = cb;
}
public void setChessLog(int[][] chessLog) {
this.chessLog = chessLog;
}
public void setButtonGroup(ButtonGroup group) {
this.group = group;
}
public void setChessMouse(ChessMouse chessMouse) {
this.chessMouse = chessMouse;
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
switch (command) {
case "开始": {
if (cb.getStart() == 1) {
JOptionPane.showMessageDialog(cb, "游戏已经开始");
return;
}else {
String groupCommand = group.getSelection().getActionCommand();
if (groupCommand.equals("PvE")) {
Object[] sequence = {"黑棋先手","白棋后手"};
String s = (String) JOptionPane.showInputDialog(cb,"选择下棋顺序","提示",JOptionPane.QUESTION_MESSAGE,null,sequence,sequence[0]);
if (s == null) {
return;
}else if (s.equals("白棋后手")) {
chessMouse.setAiEnable(1);
chessMouse.setChess(LINE/2, LINE/2);
}else if (s.equals("黑棋先手")) {
chessMouse.setAiEnable(1);
}
}
}
cb.setStart(1);
break;
}
case "悔棋": {
if (cb.getStart() == 0) {
JOptionPane.showMessageDialog(cb, "游戏未开始");
return;
}
if (chessStack.size() <= 1) {
JOptionPane.showMessageDialog(cb, "不满足悔棋条件");
return;
}
Chess chess = chessStack.pop();
chessLog[chess.getX()][chess.getY()] = -1;
chess = chessStack.pop();
chessLog[chess.getX()][chess.getY()] = -1;
cb.repaint();
break;
}
case "认输":{
if (cb.getStart() == 0) {
JOptionPane.showMessageDialog(cb, "游戏未开始");
return;
}
if (chessMouse.getLock() == 0) {
JOptionPane.showMessageDialog(cb, "黑棋方认输,白棋胜利");
chessMouse.reset();
return;
}else if (chessMouse.getLock() == 1) {
JOptionPane.showMessageDialog(cb, "白棋方认输,黑棋胜利");
chessMouse.reset();
return;
}
}
default:
throw new IllegalArgumentException("Unexpected value: " + command);
}
}
}
值得注意的地方:
1.单选按钮要setActionCommand才能在其所在ButtonGroup对象.getSelection().getActionCommand()中得到相应的值
2.悔棋操作是在对手下完棋后,回溯两步到自己还未下棋前,在栈中pop出两个保存棋子落点的Chess类对象,并分别在chessLog二维数组中将他们设为-1(无棋)即可
总结
作为一个练习项目,目前已经实现的五子棋仍有很大的改进空间,如优化UI界面,添加更多功能,以及ai的优化,可利用决策树或接入机器学习等。期待以后的完善。