zoukankan      html  css  js  c++  java
  • 利用开源HTML5引擎lufylegend.js结合javascript实现的五子棋人机对弈

    前言

        本文主要介绍利用开源引擎 lufylegend.js开发基于Html5的游戏--五子棋,主要叙述其详细开发过程。

    游戏规则

        玩过五子棋的都应该知道五子棋的规则,这里就简单介绍其规则。

        1、传统五子棋的棋具与围棋大致相同,棋子分为黑白两色,棋盘为15×15,棋子放置于棋盘线交叉点上。两人对局,各执一色,轮流下一子,先将横、竖或斜线的5个或5个以上同色棋子连成不间断的一排者为胜。    

        2、由于传统五子棋具有不公平性,而现代五子棋禁手规则令一部分棋手望而怯步。于是产生了职业制传统五子棋,职业制传统五子棋虽然准备麻烦,但胜在简单公平,而且难度增加,久而习之,思维活跃。

    规则如下:
    1、准备19×19棋盘两张。
    2、黑白子数目必须满足。
    3、第一回合先手只能下一手,其余回合可以下连续两手。
    4、后手每回合均可以下连续两手。
    5、每颗子所投的棋盘没有限制。
    6、只要任意一方在两个棋盘上且同一个回合上连为五子为胜。
    7、若任意一方在两个棋盘上且不同一个回合上连为五子为负。
    8、若任意一方在不足两个棋盘上且同一个回合上连为五子为负。
    综合效应
    “禁手”思维+“交换”思维+“井字游戏”原理=连珠
    如:RIF规则、Sakata规则、Yamaguchi规则Tarannikov规则等。
    为了方面起见,这里只考虑传统打法,也是大多数人喜欢的打法。

    用到的术语

        活五:任意方向的相同颜色棋子连成5个棋子
        活四:任意方向的相同颜色棋子连成4个棋子,且两边都没有其他棋子
        冲四:任意方向的相同颜色棋子连成4个棋子,且一边没有其他棋子
        活三:任意方向的相同颜色棋子连成3个棋子,且两边都没有其他棋子
        死三:任意方向的相同颜色棋子连成3个棋子,且一边没有其他棋子
        活二:任意方向的相同颜色棋子连成2个棋子,且两边都没有其他棋子
        死二:任意方向的相同颜色棋子连成2个棋子,且一边没有其他棋子
        单一:任意方向的相同颜色棋子连成1个棋子,且两边都没有其他棋子

    开发思路

         好了,废话不多说,接下来就介绍一下开发思路,首先要明确,当玩家每下一个棋子的时候,如何能教会电脑下棋,也就是如何让电脑知道往哪个地方下子。这里就需要让电脑扫描整个棋盘,分析整个棋型,通过计算得出哪个地方最有可能赢,或者最有可能阻止玩家赢。那么,就需要给每个棋型拟定一个分数,每次玩家下子的时候,就扫描棋盘,给定棋盘每个位置空子(即没有棋子的位置)的分值,然后就下分值最高的那个点。
         比如:如果这个地方如果电脑下子,可能构成活四,那么分数+5000,如果玩家可能构成活四,分数+2000。

    详细过程

        我表达不是很好,说的很笼统,直接贴上代码吧。

        初始化棋盘

          
    //背景层、格子层、棋子层
    var backLayer,cellLayer,chessLayer;
    var BOARD_SIZE = 15;//棋盘格子数量(15*15);
    var OFFSET = 40;//格子偏移量
    var CELL_WIDTH = 40;//行宽
    var CENTER = 8;
    var array = new Array();//记录每个格子是否有棋子0表示空位1表示己方棋子2表示地方棋子
    var isPlay = true;//是否该玩家下棋
    var C_STEP = 0,P_STEP = 0;//AI和玩家走的棋数
    
    function main(){
    	backLayer = new LSprite();
    	backLayer.graphics.drawRect(1,"darkorange",[0,0,LGlobal.width,LGlobal.height],true,"darkorange");
    	addChild(backLayer);
    	var textFiled;
    	//画棋盘周围的数字和字母
    	for(var i = 0;i<BOARD_SIZE;i++){
    		textField = new LTextField();
    		textField.color = "black";
    		textField.x = OFFSET >> 2;
    		textField.y = OFFSET+(CELL_WIDTH*(i));
    		textField.font="Arial";
    		textField.size = 8;
    		textField.text = BOARD_SIZE - i;
    		backLayer.addChild(textField);
    		textField = new LTextField();
    		textField.color = "black";
    		textField.x = OFFSET+(CELL_WIDTH*i);
    		textField.y = ((OFFSET*3) >> 2) + OFFSET + CELL_WIDTH * (BOARD_SIZE-1);
    		textField.font = "Arial";
    		textField.size = 8;
    		textField.text = String.fromCharCode(65+i);
    		backLayer.addChild(textField);
    	}
    	//AI初始化
    	AI.init();
    	
    	//画出棋盘
    	drawBoard();
    	
    	chessLayer = new LSprite();
    	backLayer.addChild(chessLayer);
    	
    	//按钮
    	//var resetButton = new LButtonSample1("重玩");
    	//resetButton.x = 10;
    	//resetButton.y = 500;
    	//backLayer.addChild(resetButton);
    	//resetButton.addEventListener(LMouseEvent.MOUSE_UP,reset);
    	
    	backLayer.addEventListener(LMouseEvent.MOUSE_MOVE,onmove);
    	backLayer.addEventListener(LMouseEvent.MOUSE_DOWN,ondown);
    	
    };
    
    //function reset(){
    	
    //};
    
    //画棋盘
    function drawBoard(){
    	//画竖线条
    	for(var i = 0;i < BOARD_SIZE;i++){
    		backLayer.graphics.drawLine(2,"#000000",[i*CELL_WIDTH+OFFSET,OFFSET,i*CELL_WIDTH+OFFSET,(BOARD_SIZE-1)*CELL_WIDTH+OFFSET]);
    	}
    	//画横线条
    	for(var i = 0;i < BOARD_SIZE;i++){
    		backLayer.graphics.drawLine(2,"#000000",[OFFSET,i*CELL_WIDTH+OFFSET,(BOARD_SIZE-1)*CELL_WIDTH+OFFSET,i*CELL_WIDTH+OFFSET]);
    	}	
    	
    	//画棋盘上的小圆点
    	drawStar(CENTER,CENTER);
    	drawStar((BOARD_SIZE + 1) >> 2,(BOARD_SIZE + 1) >> 2);
    	drawStar((BOARD_SIZE + 1) >> 2,((BOARD_SIZE + 1) * 3) >> 2);
    	drawStar(((BOARD_SIZE + 1)*3) >> 2,(BOARD_SIZE + 1) >> 2);
    	drawStar(((BOARD_SIZE + 1)*3) >> 2,((BOARD_SIZE + 1) * 3) >> 2);
    };
    
    function drawStar(cx,cy){
    	var x = (cx - 1)*CELL_WIDTH+OFFSET;
    	var y = (cy - 1) * CELL_WIDTH+OFFSET;
    	backLayer.graphics.drawArc(0,"#000000",[x,y,4,0,Math.PI * 2],true,"#000000");
    };
    //在棋盘指定位置画出黑白棋子
    function drawChess(cx,cy,color){
    	//棋子欲放入格子的中心坐标
    	var x = cx * CELL_WIDTH + OFFSET;
    	var y = cy * CELL_WIDTH + OFFSET;
    	var R = CELL_WIDTH >> 1;//棋子半径,为格子宽度/2
    	chessLayer.graphics.drawArc(0,color,[x,y,R,0,Math.PI * 2],true,color);
    	isPlay = false;
    };
    //画出鼠标点击后棋子欲落下的区域
    function drawCell(cx,cy){
    	if(cx >= 0 && cx < BOARD_SIZE && cy >= 0 && cy < BOARD_SIZE){
    		if(cellLayer){
    			cellLayer.die();
    			cellLayer.removeAllChild();
    			backLayer.removeChild(cellLayer);
    			cellLayer = null;
    		}
    		cellLayer = new LSprite();
    		backLayer.addChild(cellLayer);
    		var length = CELL_WIDTH >> 1;
    		cx = cx * CELL_WIDTH + OFFSET;
    		cy = cy * CELL_WIDTH + OFFSET;
    		cellLayer.graphics.drawLine(2,"red",[cx-length,cy - length,cx-length/2,cy-length]);
    		cellLayer.graphics.drawLine(2,"red",[cx-length,cy - length,cx-length,cy-length/2]);
    		cellLayer.graphics.drawLine(2,"red",[cx+length,cy - length,cx+length/2,cy-length]);
    		cellLayer.graphics.drawLine(2,"red",[cx+length,cy - length,cx+length,cy-length/2]);
    		cellLayer.graphics.drawLine(2,"red",[cx+length,cy + length,cx+length,cy+length/2]);
    		cellLayer.graphics.drawLine(2,"red",[cx+length,cy + length,cx+length/2,cy+length]);
    		cellLayer.graphics.drawLine(2,"red",[cx-length,cy + length,cx-length/2,cy+length]);
    		cellLayer.graphics.drawLine(2,"red",[cx-length,cy + length,cx-length,cy+length/2]);
    	}
    };

    由于五子棋的核心算法就是AI部分,所以这部分初始化棋盘的代码大可不必深究,接下来就是AI算法。直接贴代码吧
    var AI = AI || {};
    //代表每个方向
    AI.direction = {
    	TOP:1,
    	BOTTOM:2,
    	LEFT:3,
    	RIGHT:4,
    	LEFT_TOP:5,
    	LEFT_BOTTOM:6,
    	RIGHT_TOP:7,
    	RIGHT_BOTTOM:8
    };
    //初始化
    AI.init = function(){
    	//初始化数组为0
    	for(var i = 0;i < BOARD_SIZE;i++){
    		array[i] = new Array();
    		for(var j = 0;j < BOARD_SIZE;j++){
    			array[i][j] = 0;
    		}
    	}
    };
    //AI棋型分析
    AI.analysis = function(x,y){
    	//如果为第一步则,在玩家棋周围一格随机下棋,保证每一局棋第一步都不一样
    	if(P_STEP == 1){
    		return this.getFirstPoint(x,y);
    	}
    	var maxX = new Array(),
    		maxY = new Array(),
    		maxWeight = 0,
    		max = new Array(),
    		min = new Array(),
    		i, j, tem;
    		for (i = BOARD_SIZE - 1; i >= 0; i--) {
    			for (j = BOARD_SIZE; j >= 0; j--) {
    				if (array[i][j] !== 0) {
    					continue;
    				}
    				tem = this.computerWeight(i, j,2);
    				if (tem > maxWeight) {
    					maxWeight = tem;
    					maxX = i;
    					maxY = j;
    				}
    			}
    		}
    	return new Point(maxX,maxY);
    };
    //下子到i,j X方向 结果: 多少连子 两边是否截断
    AI.putDirectX = function (i, j, chessColor) {
    		var m, n,
    			nums = 1,
    			side1 = false,//两边是否被截断
    			side2 = false;
    		for (m = j - 1; m >= 0; m--) {
    			if (array[i][m] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[i][m] === 0) {
    					side1 = true;//如果为空子,则没有截断
    				}
    				break;
    			}
    		}
    		for (m = j + 1; m < BOARD_SIZE; m++) {
    			if (array[i][m] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[i][m] === 0) {
    					side2 = true;
    				}
    				break;
    			}
    		}
    		return {"nums": nums, "side1": side1, "side2": side2};
    	},
    	//下子到i,j Y方向 结果
    AI.putDirectY = function (i, j, chessColor) {
    		var m, n,
    			nums = 1,
    			side1 = false,
    			side2 = false;
    		for (m = i - 1; m >= 0; m--) {
    			if (array[m][j] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[m][j] === 0) {
    					side1 = true;
    				}
    				break;
    			}
    		}
    		for (m = i + 1; m < BOARD_SIZE; m++) {
    			if (array[m][j] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[m][j] === 0) {
    					side2 = true;
    				}
    				break;
    			}
    		}
    		return {"nums": nums, "side1": side1, "side2": side2};
    	},
    	//下子到i,j XY方向 结果
    AI.putDirectXY = function (i, j, chessColor) {
    		var m, n,
    			nums = 1,
    			side1 = false,
    			side2 = false;
    		for (m = i - 1, n = j - 1; m >= 0 && n >= 0; m--, n--) {
    			if (array[m][n] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[m][n] === 0) {
    					side1 = true;
    				}
    				break;
    			}
    		}
    		for (m = i + 1, n = j + 1; m < BOARD_SIZE && n < BOARD_SIZE; m++, n++) {
    			if (array[m][n] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[m][n] === 0) {
    					side2 = true;
    				}
    				break;
    			}
    		}
    		return {"nums": nums, "side1": side1, "side2": side2};
    	},
    AI.putDirectYX = function (i, j, chessColor) {
    		var m, n,
    			nums = 1,
    			side1 = false,
    			side2 = false;
    		for (m = i - 1, n = j + 1; m >= 0 && n < BOARD_SIZE; m--, n++) {
    			if (array[m][n] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[m][n] === 0) {
    					side1 = true;
    				}
    				break;
    			}
    		}
    		for (m = i + 1, n = j - 1; m < BOARD_SIZE && n >= 0; m++, n--) {
    			if (array[m][n] === chessColor) {
    				nums++;
    			}
    			else {
    				if (array[m][n] === 0) {
    					side2 = true;
    				}
    				break;
    			}
    		}
    		return {"nums": nums, "side1": side1, "side2": side2};
    	},
    //计算AI下棋权重
    AI.computerWeight = function(i,j,chessColor){
    	var weight = (BOARD_SIZE - 1) - (Math.abs(i - BOARD_SIZE >> 1) + Math.abs(j - BOARD_SIZE >> 1)), //基于棋盘位置权重(越靠近棋盘中心权重越大)
    	pointInfo = {},	//某点下子后连子信息
    	//x方向
    	pointInfo = this.putDirectX(i, j, chessColor);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
    	pointInfo = this.putDirectX(i, j, chessColor-1);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
    	//y方向
    	pointInfo = this.putDirectY(i, j, chessColor);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
    	pointInfo = this.putDirectY(i, j, chessColor-1);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
    	//左斜方向
    	pointInfo = this.putDirectXY(i, j, chessColor);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
    	pointInfo = this.putDirectXY(i, j, chessColor-1);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
    	//右斜方向
    	pointInfo = this.putDirectYX(i, j, chessColor);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, true);//AI下子权重
    	pointInfo = this.putDirectYX(i, j, chessColor-1);
    	weight += this.weightStatus(pointInfo.nums, pointInfo.side1, pointInfo.side2, false);//player下子权重
    	return weight;
    };
    //权重方案   活:两边为空可下子,死:一边为空
    AI.weightStatus = function (nums, side1, side2, isAI) {
    		var weight = 0;
    		switch (nums) {
    			case 1:
    				if (side1 && side2) {
    					weight = isAI ? 15 : 10;	//一
    				}
    				break;
    			case 2:
    				if (side1 && side2) {
    					weight = isAI ? 100 : 50;	//活二
    				}
    				else if (side1 || side2) {
    					weight = isAI ? 10 : 5;	//死二
    				}
    				break;
    			case 3:
    				if (side1 && side2) {
    					weight = isAI ? 500 : 200;	//活三
    				}
    				else if (side1 || side2) {
    					weight = isAI ? 30 : 20;	//死三
    				}
    				break;
    			case 4:
    				if (side1 && side2) {
    					weight = isAI ? 5000 : 2000;	//活四
    				}
    				else if (side1 || side2) {
    					weight = isAI ? 400 : 100;	//死四
    				}
    				break;
    			case 5:
    				weight = isAI ? 100000 : 10000;	//五
    				break;
    			default:
    				weight = isAI ? 500000 : 250000;
    				break;
    		}
    		return weight;
    	};
    //判断是否胜出,胜返回true否则返回false
    //思路:从下子的地方为中心朝4个方向判断,若连成五子,遇空子或敌方棋子则改变方向则胜出
    //不用全盘遍历,因为只有下子的地方才会有胜出的可能
    //flag标识AI还是玩家1为玩家2为AI
    AI.isAIWin = function(x,y,flag){
    	var count1 = 0;
    	var count2 = 0;
    	var count3 = 0;
    	var count4 = 0;
    	//左右判断
    	for(var i = x;i >= 0;i--){
    		if(array[i][y]!=flag){
    			break;
    		}
    		count1++;
    	}
    	for(var i = x+1;i<BOARD_SIZE;i++){
    		if(array[i][y] != flag){
    			break;
    		}
    		count1++;
    	}
    	//上下判断
    	for(var i = y;i>=0;i--){
    		if(array[x][i] != flag){
    			break;
    		}
    		count2++;
    	}
    	for(var i = y+1;i<BOARD_SIZE;i++){
    		if(array[x][i] != flag){
    			break;
    		}
    		count2++;
    	}
    	//左上右下判断
    	for(var i = x,j=y;i>=0&&j>=0;i--,j--){
    		if(array[i][j] != flag){
    			break;
    		}
    		count3++;
    	}
    	for(var i = x+1,j=y+1;i<BOARD_SIZE&&j<BOARD_SIZE;i++,j++){
    		if(array[i][j] != flag){
    			break;
    		}
    		count3++;
    	}
    	//右上左下判断
    	for(var i =x,j=y;i>=0&&j<BOARD_SIZE;i--,j++){
    		if(array[i][j] != flag){
    			break;
    		}
    		count4++;
    	}
    	for(var i =x+1,j=y-1;i<BOARD_SIZE&&j>=0;i++,j--){
    		if(array[i][j] != flag){
    			break;
    		}
    		count4++;
    	}
    	var win = 0;//AI是否赢了
    	if(count1>=5||count2>=5||count3>=5||count4>=5){
    		win = flag;
    	}
    	return win;
    };
    //AI第一步棋
    //参数x,y为玩家第一步棋的坐标
    AI.getFirstPoint = function(x,y){
    	var point = new Point(x,y);
    	if(x < 3 || x > BOARD_SIZE - 3 || y < 3 || y > BOARD_SIZE - 3){
    		point.x = BOARD_SIZE >> 1;
    		point.y = BOARD_SIZE >> 1;
    	}else{
    		var direction = random({
    			min:1,
    			max:8
    		});
    		switch(direction){
    		case this.direction.TOP:
    			point.y = y - 1;
    			break;
    		case this.direction.BOTTOM:
    			point.y = y + 1;
    			break;
    		case this.direction.LEFT:
    			point.x = x - 1;
    			break;
    		case this.direction.RIGHT:
    			point.x = x + 1;
    			break;
    		case this.direction.LEFT_TOP:
    			point.x = x - 1;
    			point.y = y - 1;
    			break;
    		case this.direction.LEFT_BOTTOM:
    			point.x = x - 1;
    			point.y = y + 1;
    			break;
    		case this.direction.RIGHT_TOP:
    			point.x = x + 1;
    			point.y = y - 1;
    			break;
    		case this.direction.RIGHT_BOTTOM:
    			point.x = x + 1;
    			point.y = y + 1;
    			break;
    			default:
    				point.x = x - 1;
    				point.y = y - 1;
    				break;
    		}
    	}
    	return point;
    };

    function Point(x,y){
    	var self = this;
    	self.x = x;
    	self.y = y;
    };
    最后的效果如下:


    好了,整个五子棋就开发完成了,有任何疑问,我们可以交流交流
  • 相关阅读:
    Alibaba 阿里组件 nacos注册中心 gateway网关 flowable流程引擎 vue.js前后分离 spring cloud微服务
    Java JDBC 连接 MySQL8 数据库
    Java设计模式【命令模式】
    Spring Boot内嵌tomcat关于getServletContext().getRealPath获取得到临时路径的问题
    Java代理模式学习 (Proxy模式)
    Java单例模式浅析 (Singleton模式)
    由歌词引发的模式思考之中篇(AbstractFactory模式)
    Java模拟FilterChain的实现 (Chain Of Responsibility模式)
    由歌词引发的模式思考之上篇(FactoryMethod模式)
    由歌词引发的模式思考之下篇(模拟Spring的BeanFactory)
  • 原文地址:https://www.cnblogs.com/james1207/p/3304146.html
Copyright © 2011-2022 走看看