“This is the 11th day of my participation in the First Challenge 2022. For details: First Challenge 2022”
❤️ Author’s home page: Xiao Xu Zhu
❤️ About the author: Hello everyone, I am Xiao Xu Zhu. Java field quality creator 🏆, CSDN blog expert 🏆, Huawei cloud enjoy expert 🏆, Nuggets of the year popular author 🏆, Ali Cloud expert blogger 🏆
❤️ technology live, the appreciation
❤️ like 👍 collect ⭐ look again, form a habit
preface
Chinese chess is a kind of chess originated in China. It is a kind of two-player antagonistic game and has a long history in China. Because the equipment is simple and interesting, it has become a very popular chess activity.
Chinese chess uses a square checkerboard with a total of 32 round pieces and 16 pieces each in red and black, placed and moved at intersections. The two sides alternate moves, and the one who checkmated the opposing general first wins.
Chinese chess is a puzzle game with strong Chinese characteristics, the new online battle, fun, parties can invite children to challenge together. The wonderful games make you feel the profoundness of Chinese chess.
“Chinese Chess” game is implemented with Java language, using Swing technology for interface processing, design ideas with object-oriented thought. , man-machine chess based on minimax search algorithm.
The main requirements
According to the rules of Chinese chess, to achieve red and black games, there should be AI opponents, players can play against AI, or two players can play by themselves.
The main design
1, look for the board interface and the corresponding chess pictures, program design board interface and function menu
2. Design the movement logic of different pieces
3. When the chess pieces move, there should be sound effects
4. Design the logical algorithm of the opponent AI, which uses Max/min search algorithm and sets different search depth AI(different intelligence).
5, before the start of the game, the two sides of the chess pieces on the board. 6, in the game, by the red side to go first, both sides take turns to take a step. 7. When it is the turn of a player to move a piece from one crossing point to another, or to eat the other’s piece and occupy its crossing point, it counts as a move. The two sides each walk, called a round. 9, take a chess, if their own pieces can go to the position of the other side’s pieces exist, you can eat the other side’s pieces and occupy that position. 10, one side’s pieces attack the opponent’s shuai (general), and the next move to eat it, known as “zhaojiang”, or simply “general”. “Zhao Will” need not declare. The subject must immediately “answer”, that is, use his own method to resolve the situation of the subject. If a checkmate fails to answer, he is checkmated.
Specially designed man-machine game, everyone game, and AI to AI game
Screenshot function
The game start
Game menu Settings
Effect of mobile
Code implementation
Checkerboard design
@Slf4j
public class BoardPanel extends JPanel implements LambdaMouseListener {
/** * is used to mark checkerboard moves */
private final transient TraceMarker traceMarker;
/** * The starting position of the current move corresponds to the piece */
private transient ChessPiece curFromPiece;
/** * scenario */
private transient Situation situation;
/** * Create the panel. */
public BoardPanel(a) {
setBorder(new EmptyBorder(5.5.5.5));
setLayout(null);
// Initializes the tag
traceMarker = new TraceMarker(BoardPanel.this);
// Add mouse events
addMouseListener(this);
}
/**
* 更新标记
*/
public void updateMark(Place from, Place to) {
// Update the tag
curFromPiece = null;
// Change the tag
traceMarker.endedStep(from, to);
}
/** * Initializes all tags */
public void initMark(a) {
traceMarker.initMarker();
}
/** * Add pieces */
public void init(Situation situation) {
this.situation = situation;
// Remove all components
this.removeAll();
// Add pieces
situation.getPieceList().forEach(it -> add(it.getComp()));
situation.getSituationRecord().getEatenPieceList().forEach(it -> add(it.getComp()));
// Initializes the tag
traceMarker.initMarker();
repaint();
}
/ * * *@paramE Mouse press the event object */
@Override
public void mouseReleased(MouseEvent e) {
/ / position
Place pointerPlace = ChessDefined.convertLocationToPlace(e.getPoint());
if (pointerPlace == null) {
return;
}
if(situation.winner() ! =null) {
log.warn("Winner already exists: {}, cannot move", situation.winner());
return;
}
// The current move
@NonNull Part pointerPart = situation.getNextPart();
// The current focus piece
ChessPiece pointerPiece = situation.getChessPiece(pointerPlace);
// Determine whether moves can be made by the current side and position
// step: form
if (curFromPiece == null) {
// The current focus position has a piece and is its own piece
if(pointerPiece ! =null && pointerPiece.piece.part == pointerPart) {
// The chess piece is from
curFromPiece = pointerPiece;
traceMarker.setMarkFromPlace(pointerPlace);
/ / get toList
MyList<Place> list = curFromPiece.piece.role.find(new AnalysisBean(situation.generatePieces()), pointerPart, pointerPlace);
traceMarker.showMarkPlace(list);
ChessAudio.CLICK_FROM.play();
log.info("True -> Current focus position has a piece and is its own piece");
final ListPool listPool = ListPool.localPool();
listPool.addListToPool(list);
return;
}
log.warn("Warning -> From focus indicator error");
return;
}
if (pointerPlace.equals(curFromPiece.getPlace())) {
log.warn("false -> from == to");
return;
}
// The current focus position has a piece and is its own piece
if(pointerPiece ! =null && pointerPiece.piece.part == pointerPart) {
assert curFromPiece.piece.part == pointerPart : "There's a piece in the current focus position and it's your piece that was pointing to the other piece.";
/ / update the curFromPiece
curFromPiece = pointerPiece;
traceMarker.setMarkFromPlace(pointerPlace);
MyList<Place> list = curFromPiece.piece.role.find(new AnalysisBean(situation.generatePieces()), pointerPart, pointerPlace);
traceMarker.showMarkPlace(list);
ChessAudio.CLICK_FROM.play();
log.info("True -> Update curFromPiece");
ListPool.localPool().addListToPool(list);
return;
}
final StepBean stepBean = StepBean.of(curFromPiece.getPlace(), pointerPlace);
// Return if the rule is not met
final Piece[][] pieces = situation.generatePieces();
if(! curFromPiece.piece.role.rule.check(pieces, pointerPart, stepBean.from, stepBean.to)) {// If the current pointing to a piece is its own piece
log.warn("Does not conform to the rules of chess.");
return;
}
// If long block or long block is reached, return
final StepBean forbidStepBean = situation.getForbidStepBean();
if(forbidStepBean ! =null && forbidStepBean.from == stepBean.from && forbidStepBean.to == stepBean.to) {
ChessAudio.MAN_MOV_ERROR.play();
log.warn("Long stop or long catch.");
return;
}
AnalysisBean analysisBean = new AnalysisBean(pieces);
// If two bosses face each other after a move, return
if(! analysisBean.isBossF2FAfterStep(curFromPiece.piece, stepBean.from, stepBean.to)) { ChessAudio.MAN_MOV_ERROR.play(); log.warn("BOSS to BOSS");
return;
}
/* Simulate a move and then calculate if the opponent can take his boss */
if (analysisBean.simulateOneStep(stepBean, bean -> bean.canEatBossAfterOneAiStep(Part.getOpposite(pointerPart)))) {
ChessAudio.MAN_MOV_ERROR.play();
log.warn("BOSS danger");
if(! Application.config().isActiveWhenBeCheck()) {return; }}// The current checker has no checker or is the opponent's checker and conforms to the rules
Object[] objects = new Object[]{stepBean.from, stepBean.to, PlayerType.PEOPLE};
final boolean sendSuccess = Application.context().getCommandExecutor().sendCommandWhenNotRun(CommandExecutor.CommandType.LocationPiece, objects);
if(! sendSuccess) { log.warn("Command not sent successfully: {} ==> {}", CommandExecutor.CommandType.LocationPiece, Arrays.toString(objects)); }}@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Image img = ChessImage.CHESS_BOARD.getImage();
int imgWidth = img.getWidth(this);
int imgHeight = img.getHeight(this);// Get the width and height of the image
int fWidth = getWidth();
int fHeight = getHeight();// Get the window width and height
int x = (fWidth - imgWidth) / 2;
int y = (fHeight - imgHeight) / 2;
// 520 576 514 567
log.debug(String.format("%s,%s,%s,%s,%s,%s", imgWidth, imgHeight, fWidth, fHeight, x, y));
g.drawImage(img, 0.0.null); }}Copy the code
Command executor, used to process commands in moves
@Slf4j
public class CommandExecutor {
/** * asynchronously calls the thread to process the move command */
private final CtrlLoopThreadComp ctrlLoopThreadComp;
private final BoardPanel boardPanel;
/** * Whether to run the flag */ continuously
private volatile boolean sustain;
public CommandExecutor(BoardPanel boardPanel) {
this.boardPanel = boardPanel;
this.ctrlLoopThreadComp = CtrlLoopThreadComp.ofRunnable(this::loop)
.setName("CommandExecutor")
.catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE);
}
/** ** Next command */
private CommandType nextCommand;
/** * Parameters of the next command */
private Object nextParamObj;
private volatile boolean isRun;
/ * * *@paramCommandType commandType */
public void sendCommand(@NonNull CommandType commandType) {
sendCommand(commandType, null);
}
/ * * *@paramCommandType commandType *@paramParamObj Command parameter */
public synchronized void sendCommand(@NonNull CommandType commandType, Object paramObj) {
this.nextCommand = commandType;
this.nextParamObj = paramObj;
sustain = false;
this.ctrlLoopThreadComp.startOrWake();
}
/** ** Can only be added successfully if the thread is not running@paramCommandType commandType *@paramParamObj Command parameter *@returnWhether the file is successfully added */
public synchronized boolean sendCommandWhenNotRun(@NonNull CommandType commandType, Object paramObj) {
if (isRun) {
return false;
}
sendCommand(commandType, paramObj);
return true;
}
private void loop(a) {
final CommandType command;
final Object paramObj;
synchronized (this) {
command = this.nextCommand;
paramObj = this.nextParamObj;
this.nextCommand = null;
this.nextParamObj = null;
}
if(command ! =null) {
isRun = true;
try {
log.debug("Handle event [{}] start", command.getLabel());
consumerCommand(command, paramObj);
log.debug("Handle event [{}] end", command.getLabel());
} catch (Exception e) {
log.error("Exception occurred when executing command [{}]", command.getLabel(), e);
newThread(() -> JOptionPane.showMessageDialog(boardPanel, e.getMessage(), e.toString(), JOptionPane.ERROR_MESSAGE)).start(); }}else {
this.ctrlLoopThreadComp.pause();
isRun = false; }}/** * run */
private void consumerCommand(final CommandType commandType, Object paramObj) {
switch (commandType) {
case SuspendCallBackOrAiRun:
break;
case CallBackOneTime:
Application.context().rollbackOneStep();
break;
case AiRunOneTime:
if(Application.context().aiRunOneTime() ! =null) {
log.debug("The winner has been decided!");
}
break;
case SustainCallBack:
sustain = true;
while (sustain) {
if(! Application.context().rollbackOneStep()) { sustain =false;
break;
}
Throws.con(Application.config().getComIntervalTime(), Thread::sleep).logThrowable();
}
break;
case SustainAiRun:
sustain = true;
while (sustain) {
if(Application.context().aiRunOneTime() ! =null) {
log.debug("Winner has been determined, AI execution pause!");
sustain = false;
break;
}
Throws.con(Application.config().getComIntervalTime(), Thread::sleep).logThrowable();
}
break;
case SustainAiRunIfNextIsAi:
sustain = true;
while (sustain) {
// If the next player is not AI, pause
if(! PlayerType.COM.equals(Application.config().getPlayerType(Application.context().getSituation().getNextPart()))) { sustain =false;
log.debug("Next player is not AI, pause!");
} else if(Application.context().aiRunOneTime() ! =null) {
log.debug("Winner has been determined, AI execution pause!");
sustain = false;
} else{ Throws.con(Application.config().getComIntervalTime(), Thread::sleep).logThrowable(); }}break;
case LocationPiece:
final Object[] params = (Object[]) paramObj;
Place from = (Place) params[0];
Place to = (Place) params[1];
PlayerType type = (PlayerType) params[2];
Application.context().locatePiece(from, to, type);
sendCommand(CommandExecutor.CommandType.SustainAiRunIfNextIsAi);
break;
default:
throw new ShouldNotHappenException("Unprocessed command:"+ commandType); }}/** * support enumeration (the following commands should be run on the same thread, with one event finished before another can be run.) * /
@SuppressWarnings("java:S115")
public enum CommandType {
SuspendCallBackOrAiRun("Stop revoked | AI computing"),
CallBackOneTime("Undo one step"),
SustainCallBack("Continuous withdrawal"),
AiRunOneTime("AI calculation in one step"),
SustainAiRun("AI keeps running"),
SustainAiRunIfNextIsAi("COM role run"),
LocationPiece("UI drop sub command");
@Getter
private final String label;
CommandType(String label) {
this.label = label; }}}Copy the code
The core algorithm
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class AlphaBeta {
private static final int MAX = 100 _000_000;
/** * ensure that Min + Max = 0, even a slight difference can cause errors */
private static final int MIN = -MAX;
/** * Dynamically adjust the search depth according to the number of pieces **@paramPieceNum Number of pieces *@returnAdjust the search depth difference */
public static int searchDeepSuit(final int pieceNum) {
// Dynamically adjust the search depth according to the number of pieces
if (pieceNum > 20) {
return -2;
} else if (pieceNum <= 4) {
return 4;
} else if (pieceNum <= 8) {
return 2;
}
return 0;
}
/** * generates a list of options, which are available below, and sorts the search results if deep > 2. **@paramAnalysisBean Checkerboard analysis object *@paramCurPart The current move *@paramDeep Search depth *@returnThe set of available vacancies */
private static MyList<StepBean> geneNestStepPlaces(final AnalysisBean analysisBean, final Part curPart, final int deep) {
final Piece[][] pieces = analysisBean.pieces;
// Whether to kill chess
MyList<StepBean> stepBeanList = ListPool.localPool().getAStepBeanList();
for (int x = 0; x < ChessDefined.RANGE_X; x++) {
for (int y = 0; y < ChessDefined.RANGE_Y; y++) {
final Piece fromPiece = pieces[x][y];
if(fromPiece ! =null && fromPiece.part == curPart) {
final Place from = Place.of(x, y);
// TO DO consider whether there is room for optimization when calculating the method added TO the collection here.
final MyList<Place> list = fromPiece.role.find(analysisBean, curPart, from);
if (list.isEmpty()) {
ListPool.localPool().addListToPool(list);
continue;
}
final Object[] elementData = list.eleTemplateDate();
for (int i = 0, len = list.size(); i < len; i++) { stepBeanList.add(StepBean.of(from, (Place) elementData[i])); } ListPool.localPool().addListToPool(list); }}}If the search depth is greater than 2, the results are sorted
// The sorted result is easily pruned when entering the minimax search algorithm.
if (deep > 2) {
orderStep(analysisBean, stepBeanList, curPart);
}
return stepBeanList;
}
/** * Sort the vacancy list. The sorted vacancy list is easy to prune when entering the minimax search algorithm. **@paramAnalysisBean Checkerboard analysis object *@paramStepBeanList List of vacancies that can be sublisted *@paramCurPart Current move */
private static void orderStep(final AnalysisBean analysisBean, final MyList<StepBean> stepBeanList, final Part curPart) {
final Piece[][] srcPieces = analysisBean.pieces;
// Count the constants used in the loop before entering the loop
MyList<DoubleBean<Integer, StepBean>> bestPlace = ListPool.localPool().getADoubleBeanList();
// Opposing player
final Part oppositeCurPart = Part.getOpposite(curPart);
int best = MIN;
final Object[] objects = stepBeanList.eleTemplateDate();
for (int i = 0; i < stepBeanList.size(); i++) {
final StepBean item = (StepBean) objects[i];
final Place to = item.to;
/ / backup
final Piece eatenPiece = srcPieces[to.x][to.y];
int score;
// Determine victory
if(eatenPiece ! =null && eatenPiece.role == Role.BOSS) {
score = MAX;
} else {
/ / playing chess
final int invScr = analysisBean.goForward(item.from, to, eatenPiece);
DebugInfo.incrementAlphaBetaOrderTime();
/ / score
score = negativeMaximumWithNoCut(analysisBean, oppositeCurPart, -best);
// Take a step back
analysisBean.backStep(item.from, to, eatenPiece, invScr);
}
// Add all the scores here
bestPlace.add(new DoubleBean<>(score, item));
if (score > best) { // If you find a better score, delete all the seats saved beforebest = score; }}/* Returns */ after sorting
// This sort is correct and can effectively reduce the number
bestPlace.sort((o1, o2) -> o2.getO1() - o1.getO1());
stepBeanList.clear();
bestPlace.forEach(dou -> stepBeanList.add(dou.getO2()));
ListPool.localPool().addListToDoubleBeanListPool(bestPlace);
}
/** * Negative maximum search algorithm (without pruning algorithm) **@paramAnalysisBean situation analysis object *@paramCurPart The current move *@returnThe negative maximum search algorithm calculates the score */
private static int negativeMaximumWithNoCut(AnalysisBean analysisBean, Part curPart, int alphaBeta) {
// 1. Initialize each variable
final Piece[][] pieces = analysisBean.pieces;
int best = MIN;
// 2. Generate a list to be selected
MyList<StepBean> stepBeanList = geneNestStepPlaces(analysisBean, curPart, 1);
final Object[] objects = stepBeanList.eleTemplateDate();
for (int i = 0, len = stepBeanList.size(); i < len; i++) {
final StepBean item = (StepBean) objects[i];
Place from = item.from;
Place to = item.to;
/ / backup
Piece eatenPiece = pieces[to.x][to.y];
int score;
// Determine victory
if(eatenPiece ! =null && eatenPiece.role == Role.BOSS) {
score = MAX;
} else {
/ / playing chess
final int invScr = analysisBean.goForward(from, to, eatenPiece);
DebugInfo.incrementAlphaBetaOrderTime();
score = analysisBean.getCurPartEvaluateScore(curPart);
// Take a step back
analysisBean.backStep(from, to, eatenPiece, invScr);
}
if (score > best) { // Find a better score, update the score
best = score;
}
if (score > alphaBeta) { / / alpha pruning
break;
}
}
ListPool.localPool().addListToStepBeanListPool(stepBeanList);
return -best;
}
/** * The odd-numbered layer is computer (Max layer)thisSide, and the even-numbered layer is human(min layer)otherSide **@paramSrcPieces board *@paramCurPart The current move *@paramDeep Search depth *@paramForbidStep Prohibited step (long catch or long block) *@returnNext position */
public static Set<StepBean> getEvaluatedPlace(final Piece[][] srcPieces, final Part curPart, final int deep, final StepBean forbidStep) {
// 1. Initialize each variable
final AnalysisBean analysisBean = new AnalysisBean(srcPieces);
// 2. Get the list of available vacancies
MyList<StepBean> stepBeanList = geneNestStepPlaces(analysisBean, curPart, deep);
// 3
stepBeanList.remove(forbidStep);
// Count the constants used in the loop before entering the loop
Set<StepBean> bestPlace = new HashSet<>();
int best = MIN;
// Opposing player
final Part oppositeCurPart = Part.getOpposite(curPart);
// Next depth
final int nextDeep = deep - 1;
log.debug("size : {}, content: {}", stepBeanList.size(), stepBeanList);
final Object[] objects = stepBeanList.eleTemplateDate();
for (int i = 0, len = stepBeanList.size(); i < len; i++) {
StepBean item = (StepBean) objects[i];
final Place to = item.to;
/ / backup
final Piece eatenPiece = srcPieces[to.x][to.y];
int score;
// Determine victory
if(eatenPiece ! =null && eatenPiece.role == Role.BOSS) {
// The fewer steps, the higher the score
score = MAX + deep;
} else {
/ / playing chess
final int invScr = analysisBean.goForward(item.from, to, eatenPiece);
/ / score
if (deep <= 1) {
score = analysisBean.getCurPartEvaluateScore(curPart);
} else {
score = negativeMaximum(analysisBean, oppositeCurPart, nextDeep, -best);
}
// Take a step back
analysisBean.backStep(item.from, to, eatenPiece, invScr);
}
if (score == best) { // Find the same score, add this step
bestPlace.add(item);
}
if (score > best) { // If you find a better score, delete all the seats saved before
best = score;
bestPlace.clear();
bestPlace.add(item);
}
}
ListPool.end();
ListPool.localPool().addListToStepBeanListPool(stepBeanList);
return bestPlace;
}
/** * The odd-numbered layer is computer (Max layer)thisSide, and the even-numbered layer is human(min layer)otherSide **@paramSrcPieces board *@paramCurPart The current move *@paramDeep Search depth *@paramForbidStep Prohibited step (long catch or long block) *@returnNext position */
public static Set<StepBean> getEvaluatedPlaceWithParallel(final Piece[][] srcPieces, final Part curPart, final int deep, final StepBean forbidStep) {
// 1. Initialize each variable
final AnalysisBean srcAnalysisBean = new AnalysisBean(srcPieces);
// 2. Get the list of available vacancies
MyList<StepBean> stepBeanList = geneNestStepPlaces(srcAnalysisBean, curPart, deep);
// 3
stepBeanList.remove(forbidStep);
// Count the constants used in the loop before entering the loop
final Set<StepBean> bestPlace = new HashSet<>();
final AtomicInteger best = new AtomicInteger(MIN);
// Opposing player
final Part oppositeCurPart = Part.getOpposite(curPart);
// Next depth
final int nextDeep = deep - 1;
log.debug("size : {}, content: {}", stepBeanList.size(), stepBeanList);
Arrays.stream(stepBeanList.toArray()).parallel().filter(Objects::nonNull).map(StepBean.class::cast).forEach(item -> {
log.debug("Parallel streams ==> Thread: {}", Thread.currentThread().getId());
final Piece[][] pieces = ArrayUtils.deepClone(srcPieces);
final AnalysisBean analysisBean = new AnalysisBean(pieces);
final Place to = item.to;
/ / backup
final Piece eatenPiece = pieces[to.x][to.y];
int score;
// Determine victory
if(eatenPiece ! =null && eatenPiece.role == Role.BOSS) {
// The fewer steps, the higher the score
score = MAX + deep;
} else {
/ / playing chess
final int invScr = analysisBean.goForward(item.from, to, eatenPiece);
/ / score
if (deep <= 1) {
score = analysisBean.getCurPartEvaluateScore(curPart);
} else {
score = negativeMaximum(analysisBean, oppositeCurPart, nextDeep, -best.get());
}
// Take a step back
analysisBean.backStep(item.from, to, eatenPiece, invScr);
}
if (score == best.get()) { // Find the same score, add this step
synchronized(bestPlace) { bestPlace.add(item); }}if (score > best.get()) { // If you find a better score, delete all the seats saved before
best.set(score);
synchronized (bestPlace) {
bestPlace.clear();
bestPlace.add(item);
}
}
ListPool.end();
});
ListPool.localPool().addListToStepBeanListPool(stepBeanList);
ListPool.end();
return bestPlace;
}
/** * Negative maximum search algorithm **@paramAnalysisBean situation analysis object *@paramCurPart The current move *@paramDeep Search depth *@paramAlphaBeta Pruning score *@returnThe negative maximum search algorithm calculates the score */
private static int negativeMaximum(AnalysisBean analysisBean, Part curPart, int deep, int alphaBeta) {
// 1. Initialize each variable
final Piece[][] pieces = analysisBean.pieces;
int best = MIN;
// Opposing player
final Part oppositeCurPart = Part.getOpposite(curPart);
// Next depth
final int nextDeep = deep - 1;
// 2. Generate a list to be selected
final MyList<StepBean> stepBeanList = geneNestStepPlaces(analysisBean, curPart, deep);
final Object[] objects = stepBeanList.eleTemplateDate();
for (int i = 0, len = stepBeanList.size(); i < len; i++) {
final StepBean item = (StepBean) objects[i];
Place from = item.from;
Place to = item.to;
/ / backup
Piece eatenPiece = pieces[to.x][to.y];
int score;
// Determine victory
if(eatenPiece ! =null && eatenPiece.role == Role.BOSS) {
// The fewer steps, the higher the score
score = MAX + deep;
} else {
/ / playing chess
final int invScr = analysisBean.goForward(from, to, eatenPiece);
/ / evaluation
if (deep <= 1) {
score = analysisBean.getCurPartEvaluateScore(curPart);
} else {
score = negativeMaximum(analysisBean, oppositeCurPart, nextDeep, -best);
}
// Take a step back
analysisBean.backStep(from, to, eatenPiece, invScr);
}
if (score > best) { // Find a better score, update the score
best = score;
}
if (score > alphaBeta) { / / alpha pruning
break;
}
}
ListPool.localPool().addListToStepBeanListPool(stepBeanList);
return-best; }}Copy the code
conclusion
Through the implementation of the game “Chinese Chess”, LET me have a further understanding of the relevant knowledge of Swing, the Language of Java has a deeper understanding than before.
Basic Java syntax, such as data types, operators, program flow control, and arrays, is better understood. Java is the core of the core object oriented thought, for this concept, finally realized some.
The source code for
Can pay attention to the blogger, private chat blogger free access