JAVA实现经典《中国象棋》游戏

sw
前言

中国象棋是起源于中国的一种棋,属于二人对抗性游戏的一种,在中国有着悠久的历史。由于用具简单,趣味性强,成为流行极为广泛的棋艺活动。

中国象棋使用方形格状棋盘,圆形棋子共有32个,红黑二色各有16个棋子,摆放和活动在交叉点上。双方交替行棋,先把对方的将(帅)“将死”的一方获胜。

中国象棋是一款具有浓郁中国特色的益智游戏,新增的联网对战,趣味多多,聚会可以约小朋友一起来挑战。精彩的对弈让你感受中国象棋的博大精深。

《中国象棋》游戏是用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想。,人机对弈基于极大极小值搜索算法。

主要需求

按照中国象棋的规则,实现红黑棋对战,要有AI对手,可以玩家跟AI的对弈,也可以两个玩家自己玩。

主要设计

1、寻找棋盘界面和对应的棋子图片,程序设计棋盘界面和功能菜单

2、设计不同的棋子的移动逻辑

3、棋子移动时,要有音效

4、设计对手AI的逻辑算法,这里运用了极大极小值搜索算法,设置不同的搜索深度AI(智能不同)

5、对局开始前,双方棋子在棋盘上的摆法。6、对局时,由执红棋的一方先走,双方轮流走一步。7、轮到走棋的一方,将某个棋子从一个交叉点走到另一个交叉点,或者吃掉对方的棋子而占领其交叉点,都算走了一着。8、双方各走一着,称为一个回合。9、走一着棋时,如果己方棋子能够走到的位置有对方棋子存在,就可以把对方棋子吃掉而占领那个位置。10、一方的棋子攻击对方的帅(将),并在下一着要把它吃掉,称为“照将”,或简称“将”。“照将”不必声明。被“照将”的一方必须立即“应将”,即用自己的着法去化解被“将”的状态。如果被“照将”而无法“应将”,就算被“将死”。

11、特别设计了人机对弈,人人对弈,还有AI对AI对弈

功能截图

游戏开始

游戏菜单设置

移动效果

代码实现

棋盘面板设计

@Slf4jpublicclassBoardPanelextsJPanelimplementsLambdaMouseListener{/***用于标记棋盘走棋痕迹*/privatefinaltransientTraceMarkertraceMarker;/***当前走棋开始坐标位置对应棋子*/privatetransientChessPiececurFromPiece;/***场景*/privatetransientSituationsituation;/***Createthepanel.*/publicBoardPanel(){setBorder(newEmptyBorder(5,5,5,5));setLayout(null);//初始化标记符traceMarker=newTraceMarker();//添加鼠标事件addMouseListener(this);}/***更新标记*/publicvoidupdateMark(Placefrom,Placeto){//更新标记curFromPiece=null;//更改标记(from,to);}/***初始化所有标记*/publicvoidinitMark(){();}/***添加棋子*/publicvoidinit(Situationsituation){=situation;//移除所有组件();//添加棋子().forEach(it-add(()));().getEatenPieceList().forEach(it-add(()));//初始化标记符();repaint();}/***@parame鼠标按压事件对象*/@OverridepublicvoidmouseReleased(MouseEvente){//位置PlacepointerPlace=(());if(pointerPlace==null){return;}if(()!=null){("已经存在胜利者:{},无法走棋",());return;}//当前走棋方@NonNullPartpointerPart=();//当前焦点棋子ChessPiecepointerPiece=(pointerPlace);//通过当前方和当前位置判断是否可以走棋//step:formif(curFromPiece==null){//当前焦点位置有棋子且是本方棋子if(pointerPiece!===pointerPart){//本方棋子,同时是from指向curFromPiece=pointerPiece;(pointerPlace);//获取toListMyListPlacelist=(newAnalysisBean(()),pointerPart,pointerPlace);(list);_();("true-当前焦点位置有棋子且是本方棋子");finalListPoollistPool=();(list);return;}("warning-from焦点指示错误");return;}if((())){("false-from==to");return;}//当前焦点位置有棋子且是本方棋子if(pointerPiece!===pointerPart){==pointerPart:"当前焦点位置有棋子且是本方棋子之前指向了对方棋子";//更新curFromPiececurFromPiece=pointerPiece;(pointerPlace);MyListPlacelist=(newAnalysisBean(()),pointerPart,pointerPlace);(list);_();("true-更新curFromPiece");().addListToPool(list);return;}finalStepBeanstepBean=((),pointerPlace);//如果不符合规则则直接返回finalPiece[][]pieces=();if(!(pieces,pointerPart,,)){//如果当前指向棋子是本方棋子("不符合走棋规则");return;}//如果达成长拦或者长捉,则返回finalStepBeanforbidStepBean=();if(forbidStepBean!=====){_MOV_();("长拦或长捉");return;}AnalysisBeananalysisBean=newAnalysisBean(pieces);//如果走棋后,导致两个BOSS对面,则返回if(!(,,)){_MOV_();("BOSS面对面");return;}/*模拟走一步棋,之后再计算对方再走一步是否能够吃掉本方的boss*/if((stepBean,((pointerPart)))){_MOV_();("BOSS危险");if(!().isActiveWhenBeCheck()){return;}}//当前棋子无棋子或者为对方棋子,且符合规则,可以走棋Object[]objects=newObject[]{,,};finalbooleansSuccess=().getCommandExecutor().sCommandWhenNotRun(,objects);if(!sSuccess){("命令未发送成功:{}=={}",,(objects));}}@OverridepublicvoidpaintComponent(Graphicsg){(g);Imageimg=_();intimgWidth=(this);intimgHeight=(this);//获得图片的宽度与高度intfWidth=getWidth();intfHeight=getHeight();//获得窗口的宽度与高度intx=(fWidth-imgWidth)/2;inty=(fHeight-imgHeight)/2;//520576514567(("%s,%s,%s,%s,%s,%s",imgWidth,imgHeight,fWidth,fHeight,x,y));(img,0,0,null);}}复制代码

命令执行器,用于处理走棋中的命令

@Slf4jpublicclassCommandExecutor{/***异步调用线程,来处理走棋命令*/privatefinalCtrlLoopThreadCompctrlLoopThreadComp;privatefinalBoardPanelboardPanel;/***是否持续运行标记*/privatevolatilebooleansustain;publicCommandExecutor(BoardPanelboardPanel){=boardPanel;=(this::loop).setName("CommandExecutor").catchFun(_FUNCTION_CONTINUE);}/***下一步骤命令*/privateCommandTypenextCommand;/***下一步骤命令的参数*/privateObjectnextParamObj;privatevolatilebooleanisRun;/***@paramcommandType命令类型*/publicvoidsCommand(@NonNullCommandTypecommandType){sCommand(commandType,null);}/***@paramcommandType命令类型*@paramparamObj命令参数*/publicsynchronizedvoidsCommand(@NonNullCommandTypecommandType,ObjectparamObj){=commandType;=paramObj;sustain=false;();}/***只有在线程没有运行的情况下,才能添加成功**@paramcommandType命令类型*@paramparamObj命令参数*@return是否添加成功*/publicsynchronizedbooleansCommandWhenNotRun(@NonNullCommandTypecommandType,ObjectparamObj){if(isRun){returnfalse;}sCommand(commandType,paramObj);returntrue;}privatevoidloop(){finalCommandTypecommand;finalObjectparamObj;synchronized(this){command=;paramObj=;=null;=null;}if(command!=null){isRun=true;try{("处理事件[{}]start",());consumerCommand(command,paramObj);("处理事件[{}]",());}catch(Exceptione){("执行命令[{}]发生异常",(),e);newThread(()-(boardPanel,(),(),_MESSAGE)).start();}}else{();isRun=false;}}/***运行*/privatevoidconsumerCommand(finalCommandTypecommandType,ObjectparamObj){switch(commandType){caseSuspCallBackOrAiRun:break;caseCallBackOneTime:().rollbackOneStep();break;caseAiRunOneTime:if(().aiRunOneTime()!=null){("已经决出胜方!");}break;caseSustainCallBack:sustain=true;while(sustain){if(!().rollbackOneStep()){sustain=false;break;}(().getComIntervalTime(),Thread::sleep).logThrowable();}break;caseSustainAiRun:sustain=true;while(sustain){if(().aiRunOneTime()!=null){("已经决出胜方,AI执行暂停!");sustain=false;break;}(().getComIntervalTime(),Thread::sleep).logThrowable();}break;caseSustainAiRunIfNextIsAi:sustain=true;while(sustain){//如果下一步棋手不是AI,则暂停if(!(().getPlayerType(().getSituation().getNextPart()))){sustain=false;("下一步棋手不是AI,暂停!");}elseif(().aiRunOneTime()!=null){("已经决出胜方,AI执行暂停!");sustain=false;}else{(().getComIntervalTime(),Thread::sleep).logThrowable();}}break;caseLocationPiece:finalObject[]params=(Object[])paramObj;Placefrom=(Place)params[0];Placeto=(Place)params[1];PlayerTypetype=(PlayerType)params[2];().locatePiece(from,to,type);sCommand();break;default:thrownewShouldNotHappenException("未处理的命令:"+commandType);}}/***命令支持枚举(以下命令应当使用同一个线程运行,一个事件结束之后,另一个事件才能开始运行.)*/@SuppressWarnings("java:S115")publicenumCommandType{SuspCallBackOrAiRun("停止撤销|AI计算"),CallBackOneTime("撤销一步"),SustainCallBack("持续撤销"),AiRunOneTime("AI计算一步"),SustainAiRun("AI持续运行"),SustainAiRunIfNextIsAi("COM角色运行"),LocationPiece("ui落子命令");@GetterprivatefinalStringlabel;CommandType(Stringlabel){=label;}}}复制代码

核心算法

@NoArgsConstructor(access=)@Slf4jpublicclassAlphaBeta{privatestaticfinalintMAX=100_000_000;/***这里要保证Min+Max=0,哪怕是微不足道的差距都可能导致发生错误*/privatestaticfinalintMIN=-MAX;/***根据棋子数量,动态调整搜索深度**@parampieceNum棋子数量*@return调整搜索深度差值*/publicstaticintsearchDeepSuit(finalintpieceNum){//根据棋子数量,动态调整搜索深度if(pieceNum20){return-2;}elseif(pieceNum=4){return4;}elseif(pieceNum=8){return2;}return0;}/***生成待选的列表,就是可以下子的空位,如果deep2则对搜索结果进行排序.**@paramanalysisBean棋盘分析对象*@paramcurPart当前走棋方*@paramdeep搜索深度*@return可以下子的空位集合*/privatestaticMyListStepBeangeneNestStepPlaces(finalAnalysisBeananalysisBean,finalPartcurPart,finalintdeep){finalPiece[][]pieces=;//是否杀棋MyListStepBeanstepBeanList=().getAStepBeanList();for(intx=0;_X;x++){for(inty=0;_Y;y++){finalPiecefromPiece=pieces[x][y];if(fromPiece!===curPart){finalPlacefrom=(x,y);//TODO考虑下此处添加至集合的做法在计算时是否有优化空间.finalMyListPlacelist=(analysisBean,curPart,from);if(()){().addListToPool(list);continue;}finalObject[]elementData=();for(inti=0,len=();ilen;i++){((from,(Place)elementData[i]));}().addListToPool(list);}}}//是否排序,如果搜索深度大于2,则对结果进行排序//排序后的结果,进入极大极小值搜索算法时,容易被剪枝.if(deep2){orderStep(analysisBean,stepBeanList,curPart);}returnstepBeanList;}/***对空位列表进行排序,排序后的空位列表,进入极大极小值搜索算法时,容易被剪枝.**@paramanalysisBean棋盘分析对象*@paramstepBeanList可以下子的空位列表*@paramcurPart当前走棋方*/privatestaticvoidorderStep(finalAnalysisBeananalysisBean,finalMyListStepBeanstepBeanList,finalPartcurPart){finalPiece[][]srcPieces=;//进入循环之前计算好循环内使用常量MyListDoubleBeanInteger,StepBeanbestPlace=().getADoubleBeanList();//对方棋手finalPartoppositeCurPart=(curPart);intbest=MIN;finalObject[]objects=();for(inti=0;();i++){finalStepBeanitem=(StepBean)objects[i];finalPlaceto=;//备份finalPieceeatenPiece=srcPieces[][];intscore;//判断是否胜利if(eatenPiece!===){score=MAX;}else{//走棋finalintinvScr=(,to,eatenPiece);();//评分score=negativeMaximumWithNoCut(analysisBean,oppositeCurPart,-best);//退回上一步(,to,eatenPiece,invScr);}//这里添加进所有的分数(newDoubleBean(score,item));if(scorebest){//找到一个更好的分,就把以前存的位子全部清除best=score;}}/*排序后返回*///这样排序是正确的,可以有效消减数量((o1,o2)-()-());();((()));().addListToDoubleBeanListPool(bestPlace);}/***负极大值搜索算法(不带剪枝算法)**@paramanalysisBean局势分析对象*@paramcurPart当前走棋方*@return负极大值搜索算法计算分值*/privatestaticintnegativeMaximumWithNoCut(AnalysisBeananalysisBean,PartcurPart,intalphaBeta){//1.初始化各个变量finalPiece[][]pieces=;intbest=MIN;//2.生成待选的列表,就是可以下子的列表MyListStepBeanstepBeanList=geneNestStepPlaces(analysisBean,curPart,1);finalObject[]objects=();for(inti=0,len=();ilen;i++){finalStepBeanitem=(StepBean)objects[i];Placefrom=;Placeto=;//备份PieceeatenPiece=pieces[][];intscore;//判断是否胜利if(eatenPiece!===){score=MAX;}else{//走棋finalintinvScr=(from,to,eatenPiece);();score=(curPart);//退回上一步(from,to,eatenPiece,invScr);}if(scorebest){//找到一个更好的分,就更新分数best=score;}if(scorealphaBeta){//alpha剪枝break;}}().addListToStepBeanListPool(stepBeanList);return-best;}/***奇数层是电脑(max层)thisSide,偶数层是human(min层)otherSide**@paramsrcPieces棋盘*@paramcurPart当前走棋方*@paramdeep搜索深度*@paramforbidStep禁止的步骤(长捉或长拦)*@return下一步的位置*/publicstaticSetStepBeangetEvaluatedPlace(finalPiece[][]srcPieces,finalPartcurPart,finalintdeep,finalStepBeanforbidStep){//1.初始化各个变量finalAnalysisBeananalysisBean=newAnalysisBean(srcPieces);//2.获取可以下子的空位列表MyListStepBeanstepBeanList=geneNestStepPlaces(analysisBean,curPart,deep);//3.移除不该下的子(forbidStep);//进入循环之前计算好循环内使用常量SetStepBeanbestPlace=newHashSet();intbest=MIN;//对方棋手finalPartoppositeCurPart=(curPart);//下一深度finalintnextDeep=deep-1;("size:{},content:{}",(),stepBeanList);finalObject[]objects=();for(inti=0,len=();ilen;i++){StepBeanitem=(StepBean)objects[i];finalPlaceto=;//备份finalPieceeatenPiece=srcPieces[][];intscore;//判断是否胜利if(eatenPiece!===){//步数越少,分值越大score=MAX+deep;}else{//走棋finalintinvScr=(,to,eatenPiece);//评分if(deep=1){score=(curPart);}else{score=negativeMaximum(analysisBean,oppositeCurPart,nextDeep,-best);}//退回上一步(,to,eatenPiece,invScr);}if(score==best){//找到相同的分数,就添加这一步(item);}if(scorebest){//找到一个更好的分,就把以前存的位子全部清除best=score;();(item);}}();().addListToStepBeanListPool(stepBeanList);returnbestPlace;}/***奇数层是电脑(max层)thisSide,偶数层是human(min层)otherSide**@paramsrcPieces棋盘*@paramcurPart当前走棋方*@paramdeep搜索深度*@paramforbidStep禁止的步骤(长捉或长拦)*@return下一步的位置*/publicstaticSetStepBeangetEvaluatedPlaceWithParallel(finalPiece[][]srcPieces,finalPartcurPart,finalintdeep,finalStepBeanforbidStep){//1.初始化各个变量finalAnalysisBeansrcAnalysisBean=newAnalysisBean(srcPieces);//2.获取可以下子的空位列表MyListStepBeanstepBeanList=geneNestStepPlaces(srcAnalysisBean,curPart,deep);//3.移除不该下的子(forbidStep);//进入循环之前计算好循环内使用常量finalSetStepBeanbestPlace=newHashSet();finalAtomicIntegerbest=newAtomicInteger(MIN);//对方棋手finalPartoppositeCurPart=(curPart);//下一深度finalintnextDeep=deep-1;("size:{},content:{}",(),stepBeanList);(()).parallel().filter(Objects::nonNull).map(::cast).forEach(item-{("并行流==Thread:{}",().getId());finalPiece[][]pieces=(srcPieces);finalAnalysisBeananalysisBean=newAnalysisBean(pieces);finalPlaceto=;//备份finalPieceeatenPiece=pieces[][];intscore;//判断是否胜利if(eatenPiece!===){//步数越少,分值越大score=MAX+deep;}else{//走棋finalintinvScr=(,to,eatenPiece);//评分if(deep=1){score=(curPart);}else{score=negativeMaximum(analysisBean,oppositeCurPart,nextDeep,-());}//退回上一步(,to,eatenPiece,invScr);}if(score==()){//找到相同的分数,就添加这一步synchronized(bestPlace){(item);}}if(()){//找到一个更好的分,就把以前存的位子全部清除(score);synchronized(bestPlace){();(item);}}();});().addListToStepBeanListPool(stepBeanList);();returnbestPlace;}/***负极大值搜索算法**@paramanalysisBean局势分析对象*@paramcurPart当前走棋方*@paramdeep搜索深度*@paramalphaBetaalphaBeta剪枝分值*@return负极大值搜索算法计算分值*/privatestaticintnegativeMaximum(AnalysisBeananalysisBean,PartcurPart,intdeep,intalphaBeta){//1.初始化各个变量finalPiece[][]pieces=;intbest=MIN;//对方棋手finalPartoppositeCurPart=(curPart);//下一深度finalintnextDeep=deep-1;//2.生成待选的列表,就是可以下子的列表finalMyListStepBeanstepBeanList=geneNestStepPlaces(analysisBean,curPart,deep);finalObject[]objects=();for(inti=0,len=();ilen;i++){finalStepBeanitem=(StepBean)objects[i];Placefrom=;Placeto=;//备份PieceeatenPiece=pieces[][];intscore;//判断是否胜利if(eatenPiece!===){//步数越少,分值越大score=MAX+deep;}else{//走棋finalintinvScr=(from,to,eatenPiece);//评估if(deep=1){score=(curPart);}else{score=negativeMaximum(analysisBean,oppositeCurPart,nextDeep,-best);}//退回上一步(from,to,eatenPiece,invScr);}if(scorebest){//找到一个更好的分,就更新分数best=score;}if(scorealphaBeta){//alpha剪枝break;}}().addListToStepBeanListPool(stepBeanList);return-best;}}复制代码
总结

通过此次的《中国象棋》游戏实现,让我对swing的相关知识有了进一步的了解,对java这门语言也有了比以前更深刻的认识。

java的一些基本语法,比如数据类型、运算符、程序流程控制和数组等,理解更加透彻。java最核心的核心就是面向对象思想,对于这一个概念,终于悟到了一些。


作者:小虚竹and掘金
链接:

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

文章版权声明:除非注明,否则均为虚境探索者原创文章,转载或复制请以超链接形式并注明出处。

上一个 回合制RPG游戏推荐 不容错过的经典大作

下一个 两只企鹅在游戏里秀恩爱的下场是什么?