第一种思路代码
连连看初始化,判断可通
package com.yelinlan.game.service;
import com.yelinlan.game.utils.Common;
import com.yelinlan.game.utils.PosType;
import java.util.Random;
/**
* @项目名称: game
* @类名称: ConnMap
* @类描述: 连连看初始化,判断可通
* @创建人: 夜林蓝
* @创建时间: 2020/3/21 19:54
**/
public class ConnMap {
public char[][] conn = new char[10][10];
protected int row = 10;
protected int col = 10;
//坐标(x,y)自己定义
public PosType start;
public PosType end;
public ConnMap() {
}
public ConnMap(int row, int col) {
//偶数个
if ((row * col) % 2 == 0) {
this.conn = new char[row][col];
this.row = row;
this.col = col;
}
}
/**
* @return : void
* @方法名 : initMaze
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/7 18:23
* @功能描述 : 初始化连连看
*/
public void initMaze() {
String baseChar = "ABCDEFGHJKL";
int charLen = baseChar.length();
Random rand = new Random();
char val = baseChar.charAt(rand.nextInt(charLen));
//初始化
for (int i = 0; i < conn.length; i++) {
for (int j = 0; j < conn.length; j++) {
conn[i][j] = EMPTY;
}
}
//随机阵
char[] tempC = new char[(row - 2) * (col - 2)];
for (int i = 0; i < (row - 2) * (col - 2) / 2; i++) {
tempC[2 * i] = baseChar.charAt(rand.nextInt(charLen));
tempC[2 * i + 1] = tempC[2 * i];
}
randChar(tempC);
for (int i = 1; i < conn.length - 1; i++) {
for (int j = 1; j < conn.length - 1; j++) {
conn[i][j] = tempC[(i - 1) * (conn.length - 2) + j - 1];
}
}
}
/**
* @return : void
* @方法名 : randChar
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/20 2:51
* @功能描述 : 随机字符数组
*/
public void randChar(char[] arr) {
Random rand = new Random();
int len = arr.length;
for (int i = 0; i < len; i++) {
int index = rand.nextInt(len - i) + i;
char temp = arr[index];
arr[index] = arr[i];
arr[i] = temp;
}
}
/**
* @return : void
* @方法名 : randChar
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/20 2:51
* @功能描述 : 是否可消除
*/
public boolean canPass(PosType start, PosType end) {
this.start = start;
this.end = end;
return lean(start, end);
}
/**
* @return : boolean
* @方法名 : horizonalMid
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:42
* @功能描述 : 水平是否有障碍
*/
public boolean horizonalMid(PosType start, PosType end) {
for (int i = Common.min(start.y, end.y); i <= Common.max(start.y, end.y); i++) {
if (conn[start.x][i] != EMPTY) {
if (isStartOrEndPosition(start.x,i)) {
continue;
}
return false;
}
}
return true;
}
/**
* @return : boolean
* @方法名 : verticalUp
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:46
* @功能描述 : 垂直是否有障碍
*/
public boolean verticalMid(PosType start, PosType end) {
//中间水平
for (int i = Common.min(start.x, end.x); i <= Common.max(start.x, end.x); i++) {
if (conn[i][start.y] != EMPTY) {
if (isStartOrEndPosition(i,start.y)) {
continue;
}
return false;
}
}
return true;
}
/**
* @return : boolean
* @方法名 : lean
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:57
* @功能描述 : 斜线上 先水平后垂直
*/
public boolean leanHV(PosType start, PosType end) {
//先水平后垂直
for (int i = 0; i < row; i++) {
PosType posStart = new PosType(start.x, i);
PosType posEnd = new PosType(end.x, i);
//垂直通 水平通
if (verticalMid(posStart, posEnd) && horizonalMid(start, posStart) && horizonalMid(posEnd, end)) {
return true;
}
}
return false;
}
/**
* @return : boolean
* @方法名 : lean
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:57
* @功能描述 : 斜线上 先垂直后水平
*/
public boolean leanVH(PosType start, PosType end) {
//先垂直后水平
for (int i = 0; i < col; i++) {
PosType posStart = new PosType(i, start.y);
PosType posEnd = new PosType(i, end.y);
//垂直通 水平通
if (horizonalMid(posStart, posEnd) && verticalMid(start, posStart) && verticalMid(posEnd, end)) {
return true;
}
}
return false;
}
/**
* @return : boolean
* @方法名 : lean
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 21:50
* @功能描述 : 斜着
*/
public boolean lean(PosType start, PosType end) {
boolean leanhv = leanHV(start, end);
boolean leanvh = leanVH(start, end);
return leanhv || leanvh;
}
/**
* 判断是否是起点或终点 (解决bug)
* @return
*/
boolean isStartOrEndPosition(int x,int y){
return (x==this.start.x && y==this.start.y)||(x==this.end.x && y==this.end.y);
}
public int getRow() {
return row;
}
public int getCol() {
return col;
}
}
控制层调用
package com.yelinlan.game.Actinon;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.yelinlan.game.service.ConnMapCopy;
import com.yelinlan.game.utils.FileUtils;
import com.yelinlan.game.utils.PosType;
import com.yelinlan.game.utils.StringUtils;
import org.jboss.logging.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @项目名称: game
* @类名称: LinkGame
* @类描述:
* @创建人: 夜林蓝
* @创建时间: 2020/3/20 22:22
**/
@Controller
@RequestMapping(value = "/main")
public class LinkGameCopy {
private Logger logger = Logger.getLogger(LinkGameCopy.class);
@RequestMapping("/login")
public String login() {
return "LinkGame";
}
@RequestMapping(value = "/initLinkGame")
@ResponseBody
public Object initLinkGame(@RequestBody Map<String, Object> paramMap, HttpServletRequest req) throws IOException {
//矩阵大小
Integer size = Integer.parseInt(Objects.toString(paramMap.get("size"), ""));
ConnMapCopy maze = new ConnMapCopy(size, size);
maze.initMaze();
//一开始使用的session存取,发现在跨域的时候,数据没了。所以就存文件了,可以放数据库
FileUtils.fileLinesWrite("F:maze.json", JSON.toJSONString(maze), false);
return Promot(maze, "加载成功", 1);
}
@RequestMapping(value = "/doLinkGame")
@ResponseBody
public Object doLinkGame(@RequestBody Map<String, Object> paramMap, HttpServletRequest req) {
ConnMapCopy maze = null;
try {
List<String> stringList = FileUtils.readFileContent("F:maze.json");
String mazeStr = "";
for (String s : stringList) {
mazeStr += s;
}
maze = JSON.parseObject(mazeStr, ConnMapCopy.class);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return Promot(null, "未初始化", 0);
}
//获取位置,一定不为空
String pos_x = Objects.toString(paramMap.get("pos_x"));
String pos_y = Objects.toString(paramMap.get("pos_y"));
String pos_a = Objects.toString(paramMap.get("pos_a"));
String pos_b = Objects.toString(paramMap.get("pos_b"));
Integer x_a = StringUtils.toInteger(pos_x);
Integer y_a = StringUtils.toInteger(pos_y);
Integer x_b = StringUtils.toInteger(pos_a);
Integer y_b = StringUtils.toInteger(pos_b);
PosType startPosType = new PosType(x_a, y_a);
PosType endPosType = new PosType(x_b, y_b);
if (StringUtils.isBlank(maze.conn[x_a][y_a]) || StringUtils.isBlank(maze.conn[x_b][y_b])) {
return Promot(null, "未选中有效范围", 0);
}
if (!(maze.conn[x_a][y_a] == maze.conn[x_b][y_b])) {
return Promot(null, "无法删除,两个完全不一样", 0);
}
if (startPosType.equals(endPosType)) {
return Promot(null, "不能消除自己", 0);
}
if (!(startPosType.x < maze.getRow() && startPosType.x >= 0 && startPosType.y < maze.getCol() && startPosType.y >= 0)) {
return Promot(null, "越界", 0);
}
if (!(endPosType.x < maze.getRow() && endPosType.x >= 0 && endPosType.y < maze.getCol() && endPosType.y >= 0)) {
return Promot(null, "越界", 0);
}
if (maze.canPass(startPosType, endPosType)) {
maze.conn[x_a][y_a] = ' ';
maze.conn[x_b][y_b] = ' ';
if (clearAll(maze)) {
FileUtils.fileLinesWrite("F:maze.json", "", false);
return Promot(maze, "胜利", 1);
}
FileUtils.fileLinesWrite("F:maze.json", JSON.toJSONString(maze), false);
return Promot(maze, "加油", 1);
}
FileUtils.fileLinesWrite("F:maze.json", JSON.toJSONString(maze), false);
return Promot(maze, "消除失败", 0);
}
/**
* @return : boolean
* @方法名 : clearAll
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/20 23:35
* @功能描述 : 已消除完毕
*/
public boolean clearAll(ConnMapCopy maze) {
for (int i = 0; i < maze.conn.length; i++) {
for (int j = 0; j < maze.conn.length; j++) {
if (maze.conn[i][j] != maze.EMPTY) {
return false;
}
}
}
return true;
}
/**
* 返回信息处理
*
* @param maze
* @param infomation
* @param state
* @return
*/
private Map<String, Object> Promot(ConnMapCopy maze, String infomation, int state) {
Map<String, Object> map = Maps.newHashMap();
map.put("maze", maze);
map.put("infomation", infomation);
map.put("state", state);
return map;
}
}
前端页面(springboot template)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<script th:src="${'/js/jquery.min.js'}" type="text/javascript"></script>
<script th:src="${'/js/bootstrap.min.js'}" type="text/javascript"></script>
<link th:href="${'/css/bootstrap.min.css'}" rel="stylesheet"/>
<script type="text/javascript">
<!--已点中几个-->
count = 0;
pos_x = -1;
pos_y = -1;
pos_a = -1;
pos_b = -1;
curObj_xy = null;
curObj_ab = null;
function pick(curObj, x, y) {
if (count == 0) {
pos_x = x;
pos_y = y;
curObj_xy = curObj;
$(curObj).css("font-size", "35px");
}
if (count == 1) {
pos_a = x;
pos_b = y;
curObj_ab = curObj;
$(curObj).css("font-size", "35px");
}
count++;
if (count == 2) {
var paramMap = {
"pos_x": pos_x,
"pos_y": pos_y,
"pos_a": pos_a,
"pos_b": pos_b
}
<!--点中两个就发送请求-->
$.ajax({
url: '/main/doLinkGame',
type: 'post',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(paramMap),
success: function (data) {
if (data.state == 1) {
if (data.infomation == "胜利") {
$("#btn").css("display", "inline");
} else {
$("#btn").css("display", "none");
}
//success
var maze = data.maze;
var row = maze.row;
var col = maze.col;
var conn = maze.conn;
var str = "";
str += (row - 2) + 'X' + (col - 2);
$("#default").html(str);
$("#info").html(data.infomation);
$("#info").css("color", "green");
str = "<table style='margin: auto'>";
for (var i = 1; i < row - 1; i++) {
str += "<tr>";
for (var j = 1; j < col - 1; j++) {
if (conn[i][j] == ' ') {
str += "<td >";
str += conn[i][j];
str += "</td>";
} else {
str += "<td οnclick='pick(this," + i + "," + j + ")'>";
str += conn[i][j];
str += "</td>";
}
}
str += "</tr>";
}
str += " </table>";
$("#maze").html(str);
} else if (data.state == 0) {
$("#info").html(data.infomation);
$("#info").css("color", "red");
} else {
alert("无状态码,真奇怪。")
}
count = 0;
$(curObj_xy).css("font-size", "25px");
$(curObj_ab).css("font-size", "25px");
},
error: function () {
alert('ERROR')
}
})
}
}
<!--初始化-->
function go() {
$.ajax({
url: '/main/initLinkGame',
type: 'post',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({'size': '10'}),
success: function (data) {
if (data.state == 1) {
//success
var maze = data.maze;
var row = maze.row;
var col = maze.col;
var conn = maze.conn;
var connTemp = maze.connTemp;
var str = "";
str += (row - 2) + 'X' + (col - 2);
$("#default").html(str);
$("#info").html(data.infomation);
$("#btn").css("display", "none");
str = "<table style='margin: auto' >";
for (var i = 1; i < row - 1; i++) {
str += "<tr>";
for (var j = 1; j < col - 1; j++) {
str += "<td οnclick='pick(this," + i + "," + j + ")'>";
str += conn[i][j];
str += "</td>";
}
str += "</tr>";
}
str += " </table>";
$("#maze").html(str);
} else {
alert("无状态码,真奇怪。")
}
},
error: function () {
alert('ERROR')
}
})
}
</script>
<head>
<meta charset='UTF-8'>
<title>LinkGame</title>
<style>
td{
font-family: Arial;
font-size: 20px;
text-align: center;
width: 60px;
height: 60px;
border: solid lightblue 2px;
}
</style>
</head>
<body style="background-color: bisque">
<div style="text-align: center;"><span style="font-size: 40px;color: #5bc0de">连连看</span></div>
<div style="text-align: center;"><span id="default" style="font-size: 15px;color: green;">默认</span></div>
<hr>
<div style="text-align: center;"><span id="info" style="font-size: 30px;color: green;">默认</span></div>
<div style="text-align: center;">
<button onclick='go()' id="btn" style="display: inline">开始</button>
</div>
<div id="maze"></div>
</body>
</html>
核心代码(判断可消)
/**
* @return : void
* @方法名 : randChar
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/20 2:51
* @功能描述 : 是否可消除
*/
public boolean canPass(PosType start, PosType end) {
this.start = start;
this.end = end;
return lean(start, end);
}
/**
* @return : boolean
* @方法名 : horizonalMid
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:42
* @功能描述 : 水平是否有障碍
*/
public boolean horizonalMid(PosType start, PosType end) {
for (int i = Common.min(start.y, end.y); i <= Common.max(start.y, end.y); i++) {
if (conn[start.x][i] != EMPTY) {
if (isStartOrEndPosition(start.x,i)) {
continue;
}
return false;
}
}
return true;
}
/**
* @return : boolean
* @方法名 : verticalUp
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:46
* @功能描述 : 垂直是否有障碍
*/
public boolean verticalMid(PosType start, PosType end) {
//中间水平
for (int i = Common.min(start.x, end.x); i <= Common.max(start.x, end.x); i++) {
if (conn[i][start.y] != EMPTY) {
if (isStartOrEndPosition(i,start.y)) {
continue;
}
return false;
}
}
return true;
}
/**
* @return : boolean
* @方法名 : lean
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:57
* @功能描述 : 斜线上 先水平后垂直
*/
public boolean leanHV(PosType start, PosType end) {
//先水平后垂直
for (int i = 0; i < row; i++) {
PosType posStart = new PosType(start.x, i);
PosType posEnd = new PosType(end.x, i);
//垂直通 水平通
if (verticalMid(posStart, posEnd) && horizonalMid(start, posStart) && horizonalMid(posEnd, end)) {
return true;
}
}
return false;
}
/**
* @return : boolean
* @方法名 : lean
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 20:57
* @功能描述 : 斜线上 先垂直后水平
*/
public boolean leanVH(PosType start, PosType end) {
//先垂直后水平
for (int i = 0; i < col; i++) {
PosType posStart = new PosType(i, start.y);
PosType posEnd = new PosType(i, end.y);
//垂直通 水平通
if (horizonalMid(posStart, posEnd) && verticalMid(start, posStart) && verticalMid(posEnd, end)) {
return true;
}
}
return false;
}
/**
* @return : boolean
* @方法名 : lean
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/21 21:50
* @功能描述 : 斜着
*/
public boolean lean(PosType start, PosType end) {
boolean leanhv = leanHV(start, end);
boolean leanvh = leanVH(start, end);
return leanhv || leanvh;
}
/**
* 判断是否是起点或终点 (解决bug)
* @return
*/
boolean isStartOrEndPosition(int x,int y){
return (x==this.start.x && y==this.start.y)||(x==this.end.x && y==this.end.y);
}
思路
从两个地方判断(先水平后垂直,先垂直之后水平)
反思
这个思路简单,我做成了页面,便于玩。自己的逻辑还是不行。
这个问题,是由于一个朋友面试遇到的,我想解决。
BUT,当我刚听到,我的直觉告诉我,
应该可以用迷宫算法解决(但这会让代码复杂化,逻辑也很简单)。
思路:(回溯法遍历,也就是走迷宫,找到所有的解)
只拿到拐点数在区间[0,2]上的解,如果找到则可消,直到遍历所有结果。
因为:
第二种思路代码
迷宫思路(回溯法)的核心代码
//递归遍历(效率低)
publiczhuyaooConn(char[][] co, PosType pos) {
//到达终点
if (pos.equals(end)) {
//拿到拐角数
num =Common.min(countConner(record), num);
} else{
//一旦拐角数在(2,+00),放弃这一次。进行下一次
if(countConner(record)<=2) {
//四个方向探索
for (int i = 1; i <= 4; i++) {
//找到下一个可走点
PosType rePos = canVisit(co, pos, i);
if (rePos != null) {
//记录两横坐标差(两个坐标之间关系)
int tempNum = ((rePos.x - pos.x) == 0) ? 0 : 1;
//装起来(可以用来统计拐点数)
record += tempNum;
//标记走过
mark(co, rePos, PASS, 0);
//继续找
doConn(co, rePos);
//走了,没走通,标记可走(这里如果标记PASSNOT
//,那么找不到所有可能解)
mark(co, rePos, EMPTY, 0);
//走了走不通,移除一个坐标差
record = record.substring(0, record.length() - 1);
}
}
}
}
}
主要
//调用
public void ConnGame(PosType start, PosType end) {
copyConn();
this.start = start;
this.end = end;
mark(connTemp, start, PASS, 0);
mark(connTemp, end, EMPTY, 1);
doConn(connTemp, start);
System.out.println("拐角数 : " + num);
}
//递归遍历(效率低)
publiczhuyaooConn(char[][] co, PosType pos) {
//到达终点
if (pos.equals(end)) {
//拿到拐角数
num =Common.min(countConner(record), num);
} else{
//一旦拐角数在(2,+00),放弃这一次。进行下一次
if(countConner(record)<=2) {
//四个方向探索
for (int i = 1; i <= 4; i++) {
//找到下一个可走点
PosType rePos = canVisit(co, pos, i);
if (rePos != null) {
//记录两横坐标差(两个坐标之间关系)
int tempNum = ((rePos.x - pos.x) == 0) ? 0 : 1;
//装起来(可以用来统计拐点数)
record += tempNum;
//标记走过
mark(co, rePos, PASS, 0);
//继续找
doConn(co, rePos);
//走了,没走通,标记可走(这里如果标记PASSNOT
//,那么找不到所有可能解)
mark(co, rePos, EMPTY, 0);
//走了走不通,移除一个坐标差
record = record.substring(0, record.length() - 1);
}
}
}
}
}
/**
* @return : boolean
* @方法名 : canVisit
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/20 0:23
* @功能描述 : 是否可走
*/
protected PosType canVisit(char[][] conn, PosType pos, int direction) {
PosType nextPosType = new PosType(pos.x, pos.y);
switch (direction) {
//DOWN = 1;
case 1:
nextPosType.x++;
break;
//RIGHT = 2;
case 2:
nextPosType.y++;
break;
//UP = 3;
case 3:
nextPosType.x--;
break;
// LEFT = 4;
case 4:
nextPosType.y--;
break;
default:
return null;
}
//越界
if (isOutOfXBounds(nextPosType)) {
return null;
}
//障碍或已走过
//BORDER PASSNOT PASS
if (conn[nextPosType.x][nextPosType.y] != EMPTY) {
return null;
}
pos = nextPosType;
return pos;
}
/**
* @return : boolean
* @方法名 : isOutOfXBounds
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/7 13:20
* @功能描述 : 坐标越界
*/
public boolean isOutOfXBounds(PosType pos) {
return !(pos.x < this.row && pos.x >= 0 && pos.y < this.col && pos.y >= 0);
}
/**
* @return : void
* @方法名 : mark
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/7 11:50
* @功能描述 : //todo 标记迷宫块
*/
public void mark(char[][] co, PosType pos, char markSymbol, int state) {
if (state != 0) {
co[pos.x][pos.y] = markSymbol;
} else {
if (!pos.equals(end)) {
co[pos.x][pos.y] = markSymbol;
}
}
}
拐角数统计
/**
* @return : int
* @方法名 : countConner
* @创建人 : 夜林蓝
* @创建日期 : 2020/3/7 16:45
* @功能描述 : 统计拐角次数
*/
public int countConner(String str) {
int count = 0;
char[] chars = str.toCharArray();
int len = chars.length;
for (int i = 0; i < len - 1; i++) {
if (chars[i] != chars[i + 1]) {
count++;
}
}
return count;
}
结束语
虽然饶了弯路,也还是可以。毕竟繁琐的方法,又加深了回溯法理解因为再看数据结构树,可以写迷宫,所以决定来实现连连看。可是,总会绕弯路的,不可避免。还是动手之前,先动脑,不然忙活了半天,思路不清晰,纸笔不离手,算法多画画,思路好一点。