#夏日挑战赛# 七夕快到了,送你们一个连连看小程序排解忧愁吧 原创 精华

发布于 2022-7-29 23:18
浏览
6收藏

本文正在参加星光计划3.0–夏日挑战赛

一.前言

继上次ETS版抓住神经猫游戏发布之后,反响平平,那么值此七夕节日到来之际,给各位单身朋友一点点心理安慰吧,一款豪华连连看游戏免费赠送,请看靓图:
#夏日挑战赛# 七夕快到了,送你们一个连连看小程序排解忧愁吧-开源基础软件社区

二.玩法

众所周知,连连看游戏是一款益智烧脑的高智商游戏,曾经曾小贤主播也对此流连忘返:
#夏日挑战赛# 七夕快到了,送你们一个连连看小程序排解忧愁吧-开源基础软件社区
这其中的魅力估计不用多说吧.
简单来说连连看就是把两个相同的图片分别点击一下,如果中间路径上没有其他图片拦住(有的版本规定弯折不能超过3次)的话,就可以将这两张图片同时消除.游戏胜利即是所有的图片全部消失不见.
当然也有意外情况产生,就是当前屏幕上的图片无论如何也无法消除了,这就触发死局,死局的解决办法通常有以下几种:
1.直接判负
2.给一个重随按钮将局面重随(可能有次数限制)
3.系统自动判定当前死局,自动重随
当然对于本豪华版的连连看游戏来说,这些最基础的功能当然是没有的啦,所以当前版本:
1.仅实现水果随机排布
2.可以实现练练看的玩法但是没有3次弯折的限制
3.可以做到游戏结束判定但是弹窗还没有做
对于为何是这么豪华的版本请容我狡辩一下:我就是懒你能拿我怎么着哈哈.

三.布局拆解

豪华连连看游戏的布局相当简单啦,上面是一行文字,下面是消水果的画布,目前是按6*6的布局,一共36个方格,布局的代码如下


@Entry
@Component
struct Index {

  @State blockArray:Array<Array<blockInfo>> = initBlockArray()

  build() {
    Column(){

      Flex({ direction: FlexDirection.Column, 
        alignItems: ItemAlign.Center, 
        justifyContent: FlexAlign.Center }) {
        Text("连连看豪华版")
        .fontSize(30)
          .fontColor(0xd06030)
        .textAlign(TextAlign.Center)

        Grid(){

        ForEach(this.blockArray, (cols) => {

        ForEach(cols,(item)=> {
          GridItem() {
          block({ blockData: item })

        }.forceRebuild(false)
          .columnStart(item.col)
          .rowStart(item.row)

        })
      })

      }
      .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr')
      .rowsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr')

      }
      .width('100%')
      .height('100%')

    }.backgroundImage("/media/llkbg.png",ImageRepeat.XY)

//    .backgroundColor(Color.Blue)
    .backgroundImageSize({width:"100%",height:"100%"})
    .backgroundImagePosition({x:0,y:0})
  }
}

四.水果类

游戏采用了面向对象的设计方法,我们把水果当做一个类,对应的有属性和方法:


@Observed
export class blockInfo{
  num:number
  selected:boolean =false
  show:boolean = true
  col:number
  row:number
  checked:boolean
  path:number

  constructor(row,col,num:number){
    this.num = num
    this.row = row
    this.col = col
  }

  setClicked(yes:boolean){
    this.selected = yes
  }

  unShow(){
    this.show = false
    this.selected = false
  }


}

然后再制作block的组件,此处采用数组的方式描述每种水果对应的图片


@Component
export struct block{
  @ObjectLink blockData:blockInfo

  @State color:Color[]=[Color.Yellow,
  Color.Brown,
  Color.Gray,
  Color.Orange,
  Color.Pink]

  @State images:string[]=[
  "/media/apple.png",
    "/media/banana.png",
    "/media/cherry.png",
    "/media/grape.png",
    "/media/hami.png",
    "/media/lemon.png",
    "/media/peach.png",
    "/media/pear.png",
    "/media/persimmon.png",
    "/media/watermelon.png"
  ]

  build(){
    Stack(){
//      Rect().fill(this.color[this.blockData.num])
//      .size({width:"100%",height:"100%"})
//      Text(`${this.blockData.num}`)
      Image(this.images[this.blockData.num])
//              .height('10')
//              .width('10')
      .objectFit(ImageFit.Contain)
      .sourceSize({height:78,width:78 })
          .onComplete((event)=>{
            console.log("onComplete event :"+event.componentHeight+" "+event.componentWidth)
          }    )
          .onError((event)=>{
            console.log("onError event :"+event.componentHeight+" "+event.componentWidth)
          })

    }
    .size({width:"30",height:"30"})
//    .backgroundImage()
//    .backgroundImageSize(ImageSize.Cover)
//    .backgroundColor(Color.Blue)
    .borderColor(this.blockData.selected?Color.Red:Color.White)
     .border({width: 2})
    .visibility(this.blockData.show? Visibility.Visible:Visibility.Hidden)
    .onClick(()=>onBlockClick(this.blockData.row,this.blockData.col))

  }
}

五.游戏初始化

游戏的初始化其实就是一个二维数组的初始化算法,这里采用随机数的方式给每个水果的格子分配水果:


let blockArray:blockInfo[][] = []
// 获取指定区间内的随机整数
const randomNum = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
//生成指定区间和数量的随机数组
function buildNumberArray(numArray, start, end, n){
  //生成2n个数 两两相等
  while(true) {
    // 获取随机数
    var rnd = randomNum(start, end);
//    // 检查是否使用
//    if(numArray.includes(rnd)) {
//      continue;
//    } else {
      // 未使用,则填入
      numArray.push(rnd);
      numArray.push(rnd);

      // 达到长度,则退出获取
      if(numArray.length==n*2) {
        break;
      }
//    }
  }

  //打乱数组
  numArray.sort(()=>.5 - Math.random());
  return numArray;
}

export function initBlockArray():Array<Array<blockInfo>>{

  let nArray:Array<number>=[]
  buildNumberArray(nArray,0,9,defaultBlockNumber*defaultBlockNumber/2)
  console.log("nArray:"+nArray)

  for(var i=0;i<defaultBlockNumber;i++){
    let col:Array<blockInfo> = []
    for(var j=0;j<defaultBlockNumber;j++) {
//      let num = Math.round(Math.random() * 10)

      col.push(new blockInfo(i,j,nArray[i*defaultBlockNumber+j]))
//      col.push(new blockInfo(i,j+1,num%10))
    }
//    col.sort(()=>.5-Math.random())
    blockArray.push(col)
  }


  return blockArray

}

此处借用了乔帮主的随机算法函数

六.点击事件的处理

由前面的游戏玩法知道,游戏的发展是根据玩家的点击推动的,随着玩家在水果方块上点击,会产生几种情况:
1.假如未有水果被选中,则当前水果被选中
2.如果当前水果点击的时候已经有水果被选中了,则判断这两个水果能否连线
3.如果能连线则将两个水果消除
4.如果不能连线则有两种处理方式:a.没有任何变化,b.将之前选中的块取消选中
以下为代码:


export function onBlockClick(row:number,col:number){

  if(blockArray[row][col].selected){
    blockArray[row][col].setClicked(false)
    return
  }

    let selectedBlock:blockInfo = null
    for(var i=0;i<defaultBlockNumber;i++){

      for(var j=0;j<defaultBlockNumber;j++) {

          if(blockArray[i][j].selected){
            selectedBlock = blockArray[i][j]
          }
          blockArray[i][j].checked = false
      }
    }

    console.log("selectedBlock="+selectedBlock)
  if(selectedBlock == null){
    blockArray[row][col].setClicked(true)
  }else{
    //judge if can line
    let canLine = false
    let canAttach = blocksCanAttach(selectedBlock,blockArray[row][col])
    if(canAttach){

      if(blockArray[row][col].num == selectedBlock.num){
        canLine = true
      }
    }



    if(canLine){
      blockArray[row][col].unShow()
      selectedBlock.unShow()
    }else{
      //如果不能连线是保持不动还是将之前的选中去除?
      //从实际体验添加以下为去除之前选中的块
      selectedBlock.setClicked(false)
    }

    let gameOver = true

    blockArray.forEach((item)=>{
      if(item.every(item=>{item.show===true})){
        console.log("game over ==== true")
        gameOver = false
      }

    })

    if( gameOver == true){
      console.log("game over "+gameOver)

    }
  }


}

经过几局游戏体验,最终选择了取消之前的选中,如果大家感觉这种方式不好的话,注释掉其中一行代码即可

七.寻路算法

可以说整个游戏的精髓就是寻路算法了,当然相比之前的ETS版抓住神经猫游戏是简单不少的:
1.神经猫有6个方向,连连看只有4个方向
2.神经猫为了尽快逃跑需要广度搜索和深度深度两种联合,而连连看基本只需要深度搜索
所以此版本的算法就比较简单了,注意:当前并没有做三折限制


class Neighbor{
  row:number
  col:number

  child:Neighbor[]
}

enum Direction{
  UP,
  LEFT,
  DOWN,
  RIGHT
}


function getNeighbors(row:number,col:number):Array<blockInfo>{
  let neighbors:Array<blockInfo> = []

  for(var d=Direction.UP;d<Direction.RIGHT+1;d++){

    switch(d){

      case Direction.UP:
        if(row == 0){
          continue
        }
        if(blockArray[row-1][col].checked == true) {
          console.log("block:"+(row-1)+(col)+"checked")
          continue
        }
        neighbors.push(blockArray[row-1][col])
        blockArray[row-1][col].checked = true
          break ;

      case Direction.LEFT:
        if(col == 0){
          continue
        }
        if(blockArray[row][col-1].checked == true) {
          console.log("block:"+(row)+(col-1)+"checked")

          continue
        }
        neighbors.push(blockArray[row][col-1])
        blockArray[row][col-1].checked = true
      break ;
      case Direction.DOWN:
        if(row == (defaultBlockNumber-1)){
          continue
        }
        if(blockArray[row+1][col].checked == true) {
          console.log("block:"+(row+1)+(col)+"checked")
          continue
        }

        neighbors.push(blockArray[row+1][col])
        blockArray[row+1][col].checked = true
      break ;
      case Direction.RIGHT:
        if(col == (defaultBlockNumber-1)){
          continue
        }
        if(blockArray[row][col+1].checked == true) {
          console.log("block:"+(row)+(col+1)+"checked")
          continue
        }
        neighbors.push(blockArray[row][col+1])
        blockArray[row][col+1].checked = true

      break ;

    }


  }

  return neighbors
}

function blocksCanAttach(src:blockInfo,dst:blockInfo){

  console.log("==>src:"+src.row+src.col+" ==>dst:"+dst.row+dst.col)
  if((src.row==dst.row) && (Math.abs(src.col - dst.col)==1)){
    console.log("neighbor row")
    return true
  }

  if((src.col==dst.col) && (Math.abs(src.row - dst.row)==1)){

    console.log("neighbor col")
    return true
  }


  let neighbors = getNeighbors(dst.row,dst.col)
  console.log("neighbors len:"+neighbors.length)
  for(var i=0;i<neighbors.length;i++){
    let neighbor = neighbors[i]
    console.log("==>nei :"+neighbor.row+neighbor.col)

    if(neighbors[i].show == true){
      console.log("==>show :")
      blockArray[neighbor.row][neighbor.col].checked = true
      continue
    }else{
//      if(neighbors[i].checked){
//        console.log("==>show :")
//        continue
//      }
      if(blocksCanAttach(src,blockArray[neighbor.row][neighbor.col])){
        console.log("==>attach :"+src.row+src.col+"==="+neighbor.row+neighbor.col)
        return true
      }
    }
  }

  return false

}

八.游戏结束判定

由于我们采用的模块化开发,block块已经保存了是否显示的数据,所以判断游戏是否结束只需要在每次点击事件处理完之后判断当前还有没有块在显示就行,此处用到了js里面array的高级用法,单独记录一下:


    let gameOver = true

    blockArray.forEach((item)=>{
      if(item.every(item=>{item.show===true})){
        console.log("game over ==== true")
        gameOver = false
      }

    })
//    console.log("game over "+gameOver)
    if( gameOver == true){
      console.log("game over "+gameOver)

    }

九.试玩体验

由于我们是豪华版连连看,所以省略了开机动画,开始按钮,华丽的动画,动听的音效,一切都是为了给大家沉浸式体验,所以下面的体验那是相当好看啊,不要错过喔:
#夏日挑战赛# 七夕快到了,送你们一个连连看小程序排解忧愁吧-开源基础软件社区
附件可以下载效果图片.

十.关于代码与总结

基本上所有的代码都已经贴出来了,希望能对大家有用,另外还有几个想法没有实现,比如上面列举的:
开机动画,开始按钮,华丽的动画,动听的音效,计时,记分,双人PK玩法等等
大家如果想参与代码优化的,地址在此:
https://gitee.com/yegren/lianliankan-ets
开源不易,欢迎大家共同帮忙优化体验,打造咱们鸿蒙系统真正"豪华版"连连看.

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
豪华连连看试玩体验.zip 596.26K 6次下载
已于2022-8-1 15:25:24修改
7
收藏 6
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐