这边是用unity和lua做的,但是数据处理方式都差不多
思路:先随机,再初始化界面和数据,判断能否移动,最后验证成功
1.先随机一串数字,比如3*3,就随机1-8,9固定空位,还得验证一下随机的数组是否能拼好(逆序数是基数就拼不好,偶数就可以,第一次写的时候还不知道...)
什么是逆序数:“1,4,3,2”的逆序数是3,这边我从后往前看,2的前面比它大的有2个,3的前面比他大的有1个, 然后4和1前面没有比他大的,所以2+1+0+0就是3,这个时候任意调换相邻的两个位置,基数就变偶数了
self.rowNum = 3 --总行数
self.colNum = 3 --总列数
self.pCount = self.rowNum * self.colNum
self.noneId = self.pCount --用来保存空的这个位置
self.randomArr = {} --用来保存打乱的数组
--- 打乱逻辑
function RandomPuzzleIndex()
self.randomArr = {}
for i = 1, self.pCount do --3*3,生成9个数据的数组
table.insert(self.randomArr, i)
end
for i=1, #self.randomArr do --乱随机
local index = math.random(i, #self.randomArr)
if i ~= index and index ~= self.pCount then --最后一张拼图空着不纳入随机
local temp = self.randomArr[index]
self.randomArr[index] = self.randomArr[i]
self.randomArr[i] = temp
end
end
if not self:IsReducible(self.randomArr) then
--这边可以调换相邻下标的任意两个,因为空块固定,所以不要调换空块
local temp1 = self.randomArr[self.pCount-1]
self.randomArr[self.pCount-1] = self.randomArr[self.pCount-2]
self.randomArr[self.pCount-2] = temp1
end
end
--- 判断随机出来的数组是否可还原(逆序数为偶数可还原)
function IsReducible(arr)
local count = 0
for i = #arr, 1, -1 do
if i ~= 1 then
for j = i-1, 1, -1 do
if arr[j] > arr[i] then
count = count + 1
end
end
end
end
local num1,num2 = math.modf(count/2)
if num2 == 0 then
return true
else
return false
end
end
2.然后是加载和赋值(初始化拼图和数据)
加载的方式就不贴代码了,每个项目加载的方式都不一样,我用的是Unity,初始化的时候加了个布局组件GridLayoutGroup,等初始化结束之后,就把这个组件关掉了,因为我用的是交换坐标的方式去做移动,这种方式后期加动画会很简单
代码段,初始化每个格子的数据,接口传了3个值,obj用来记录和交换位置用,index就是传过来的下标0-9顺序(用来计算行列),id就是上面随机出来的值(不会变的唯一Id),数据里面的row和col就是记录每个格子当前的行和列,index最后用作判断是否成功的
--- 初始化数据
function InitPuzzleData(obj, index, id)
table.insert(self.curPuzzleData, {
id = id,
obj = obj,
row = math.ceil(index/self.colNum),
col = index % self.colNum == 0 and self.colNum or index % self.colNum,
index = index,
})
end
3.判断能否移动和移动(交换两个块的位置和数据)
--- 判断能否移动
function CanMove(id)
local clickP = self:GetChuckDataById(id)
local noneP = self:GetChuckDataById(self.noneId)
if (math.abs(clickP.row - noneP.row) == 1 and clickP.col == noneP.col)
or (math.abs(clickP.col - noneP.col) == 1 and clickP.row == noneP.row) then
return true
end
return false
end
--- 这边是移动逻辑,改变两个块的位置,交换他们的行列和下标(下标是判断成功时候用)
function View:StartMove(id)
local clickP = self:GetChuckDataById(id)
--获取空块的数据,self.noneId初始化的时候就记录了
local noneP = self:GetChuckDataById(self.noneId)
local tempP = Clone(clickP)--这个是我们内部函数,赋值后改变clickP的值不会影响tempP
--TODO:这边后面也可以用动画
local x,y,z = clickP.obj.position.x, clickP.obj.position.y, clickP.obj.position.z
clickP.obj.position = noneP.obj.position
clickP.row = noneP.row
clickP.col = noneP.col
clickP.index = noneP.index
noneP.obj.position = Vector3(x,y,z)
noneP.row = tempP.row
noneP.col = tempP.col
noneP.index = tempP.index
end
--- 通过Id获取格子的数据
function GetChuckDataById(id)
for i = 1, #self.curPuzzleData, 1 do
if self.curPuzzleData[i].id == id then
return self.curPuzzleData[i]
end
end
end
4.最后验证结果
这块最后就很简单了,移动完之后判断是否成功,只需要判断每个块的index是否等于id就可以了
function JudgeIsSuccess()
for i = 1, #self.curPuzzleData, 1 do
if self.curPuzzleData[i].index ~= self.curPuzzleData[i].id then
return false
end
end
return true
end