首页 > 其他分享 >九宫格自由流转拼图游戏

九宫格自由流转拼图游戏

时间:2024-10-18 15:09:48浏览次数:5  
标签:case index emptyIndex pictures 九宫格 流转 let 拼图游戏 分布式

作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

知识点

  1. 游戏介绍
  2. 游戏规则
  3. 跨设备文件访问
  4. 分布式数据对象跨设备数据同步

效果图

视频请移步到B站观看https://www.bilibili.com/video/BV1ZrvSeDE8Z/?spm_id_from=333.999.0.0

具体实现

此实例是基于上一篇 九宫格切图 实例开发,九宫格切图完成了从图库选择图片,点击按钮切割出九张图片,并保存在图库里,拼图游戏切图后,可以不用保存到图库里,这里改为保存到分布式目录下,实现跨设备文件访问。

哈哈

游戏介绍

九宫格拼图游戏,作为一种经典的益智游戏,其游戏规则主要围绕在3×3的方格盘上,通过移动八块拼图(其中一个格子为空),最终将拼图全部归位至正确位置。以下是九宫格拼图游戏规则的详细解释:

游戏目标

  • 将八块拼图在3×3的方格盘上正确排列,使得每行、每列都填满,且没有拼图重叠或遗漏。

游戏准备

  • 准备一个3×3的方格盘,其中八个位置放置拼图,剩下一个位置留空作为移动空间。

游戏技巧

  • 从外围开始:由于外围的拼图更容易移动和归位,因此玩家可以从外围的拼图开始入手,逐步向中心推进。
  • 利用空格:空格是移动拼图的关键所在,玩家需要巧妙地利用空格来创造移动的机会和条件。
  • 观察与预判:在移动拼图之前,玩家需要仔细观察整个方格盘的布局和拼图的位置关系,并预判移动后的结果和可能产生的影响。

游戏规则

  1. 初始布局:游戏开始时,八块拼图在方格盘上随机分布,留有一个空格作为移动区域。
  2. 移动规则
    • 玩家每次只能移动一个拼图,且只能将其移动到与其相邻的空格中(上下左右四个方向)。
    • 拼图不能跳过其他拼图直接移动到更远的空格,也不能斜向移动。
  3. 归位要求
    • 玩家需要通过一系列的移动,将八块拼图逐一归位到正确的位置上,使得整个方格盘呈现出一个完整的图案或数字序列(根据不同的游戏版本而定)。
    • 在归位过程中,玩家需要不断观察并思考最佳的移动策略,以减少移动次数并避免陷入无法解开的局面。

游戏代码讲解

游戏代码逻辑参考官方案例 拼图 更详细内容请查看官方案例,这里通过基于拼图游戏,用上跨设备文件访问知识和分布式对象跨设备数据同步知识。

游戏初始化
gameInit(i: number, pictures: PictureItem[]): PictureItem[] {
    let emptyIndex = this.findEmptyIndex(pictures);
    let isGameStart: boolean = AppStorage.get('isGameStart') as boolean;
    if (isGameStart) {
      switch (emptyIndex) {
        case 0:
          if (i === 1 || i === 3) {
            pictures = this.itemChange(i, pictures);
          }
          break;
        case 2:
          if (i === 1 || i === 5) {
            pictures = this.itemChange(i, pictures);
          }
          break;
        case 6:
          if (i === 3 || i === 7) {
            pictures = this.itemChange(i, pictures);
          }
          break;
        case 8:
          if (i === 5 || i === 7) {
            pictures = this.itemChange(i, pictures);
          }
          break;
        case 3:
          switch (i) {
            case emptyIndex + 1:
            case emptyIndex - 3:
            case emptyIndex + 3:
              pictures = this.itemChange(i, pictures);
          }
          break;
        case 1:
          switch (i) {
            case emptyIndex + 1:
            case emptyIndex - 1:
            case emptyIndex + 3:
              pictures = this.itemChange(i, pictures);
          }
          break;
        case 5:
          switch (i) {
            case emptyIndex + 3:
            case emptyIndex - 3:
            case emptyIndex - 1:
              pictures = this.itemChange(i, pictures);
          }
          break;
        case 7:
          switch (i) {
            case emptyIndex + 1:
            case emptyIndex - 3:
            case emptyIndex - 1:
              pictures = this.itemChange(i, pictures);
          }
          break;
        case 4:
          switch (i) {
            case emptyIndex + 1:
            case emptyIndex - 3:
            case emptyIndex - 1:
            case emptyIndex + 3:
              pictures = this.itemChange(i, pictures);
          }
          break;
      }
    }
    return pictures;
  }
查找空图片下标
  findEmptyIndex(pictures: PictureItem[]): number {
    for (let i = 0; i < pictures.length; i++) {
      if (pictures[i].index === this.EMPTY_PICTURE.index) {
        return i;
      }
    }
    return -1;
  }
更改图片
  itemChange(index: number, pictures: PictureItem[]): PictureItem[] {
    let emptyIndex = this.findEmptyIndex(pictures);
    let temp: PictureItem = pictures[index];
    pictures[index] = this.EMPTY_PICTURE;
    pictures[emptyIndex] = new PictureItem(temp.index, temp.fileName);
    return pictures;
  }
开始游戏
  gameBegin(pictures: PictureItem[]): PictureItem[] {
    console.info(`testTag 随机打乱位置 ${pictures?.length}`)
    AppStorage.set<boolean>('isGameStart', true);
    let len = pictures.length;
    let index: number, temp: PictureItem;
    while (len > 0) {
      index = Math.floor(Math.random() * len);
      temp = pictures[len - 1];
      pictures[len - 1] = pictures[index];
      pictures[index] = temp;
      len--;
    }
    return pictures;
  }

跨设备文件访问

分布式文件系统为应用提供了跨设备文件访问的能力,开发者在多个设备安装同一应用时,通过基础文件接口,可跨设备读写其他设备该应用分布式文件路径(/data/storage/el2/distributedfiles/)下的文件。例如:多设备数据流转的场景,设备组网互联之后,设备A上的应用可访问设备B同应用分布式路径下的文件,当期望应用文件被其他设备访问时,只需将文件移动到分布式文件路径即可。

权限添加

配置文件module.json5里添加读取图片及视频权限和修改图片或视频权限。

      {
        "name": "ohos.permission.DISTRIBUTED_DATASYNC",
        "reason": "$string:distributed_data_sync",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
切割图片

这里切割图片时,和上一篇九宫格切图,有小小不同,就是切割到最后一张时,使用空白代替,方便拼图游戏时,做为移动位置。

// 切换为 3x3 张图片
      for (let i = 0; i < this.splitCount; i++) {
        for (let j = 0; j < this.splitCount; j++) {
          let picItem: PictureItem;
          // 如果是切到最后一张
          if (i === this.splitCount - 1 && j === this.splitCount -1) {
            // 最后一张使用空白图片
            picItem = new PictureItem(this.splitCount * this.splitCount, '');
            imagePixelMap.push(picItem);
          } else {
            let width = imageInfo.size.width / this.splitCount;
            // 设置解码参数DecodingOptions,解码获取pixelMap图片对象
            let decodingOptions: image.DecodingOptions = {
              desiredRegion: {
                size: {
                  height: height, // 切开图片高度
                  width: width  // 切开图片宽度
                },
                x: j * width,   // 切开x起始位置
                y: i * height     // 切开y起始位置
              }
            }
            let img: image.PixelMap = await imageSource.createPixelMap(decodingOptions);


            let context = getContext() as common.UIAbilityContext;
            // 保存到图片到分布式目录
            let fileName = await savePixelMap(context, img);
            console.info(`xx [splitPic]Save Picture ${fileName}`)
            // 保存到内存数组里
            imagePixelMap.push(new PictureItem(i * this.splitCount + j, fileName));
          }
        }
      }
图片存储到分布式目录
export async function savePixelMap(context: Context, pm: PixelMap): Promise<string> {
  if (pm === null) {
    return '';
  }
  const imagePackerApi: image.ImagePacker = image.createImagePacker();
  const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 30 };
  try {
    const data: ArrayBuffer = await imagePackerApi.packing(pm, packOpts);
    return await saveFile(context, data);
  } catch (err) {
    return '';
  }
}
async function saveFile(context: Context, data: ArrayBuffer): Promise<string> {
  let fileName: string = new Date().getTime() + ".jpg";

  let dduri: string = context.distributedFilesDir + '/' + fileName;
  let ddfile = fileIo.openSync(dduri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);

  fileIo.writeSync(ddfile.fd, data);
  fileIo.closeSync(ddfile);
  // 只返回文件名,到时分布式对象,只存储文件名,使用时就和分布式目录路径拼接
  return fileName;
}

分布式数据对象跨设备数据同步

分布式数据对象是一个JS对象型的封装。每一个分布式数据对象实例会创建一个内存数据库中的数据表,每个应用程序创建的内存数据库相互隔离,对分布式数据对象的“读取”或“赋值”会自动映射到对应数据库的get/put操作。

分布式数据对象的生命周期包括以下状态:

  • 未初始化:未实例化,或已被销毁。
  • 本地数据对象:已创建对应的数据表,但是还无法进行数据同步。
  • 分布式数据对象:已创建对应的数据表,设备在线且组网内设置同样sessionId的对象数>=2,可以跨设备同步数据。若设备掉线或将sessionId置为空,分布式数据对象退化为本地数据对象。
页面使用@StorageLink存储拼图里图片数据
  1. 页面部分变量声明
  // 使用@StorageLink声明,与EntryAbility里使用分布式对象有关联
  @StorageLink('numArray') numArray: Array<PictureItem> = [];
  @StorageLink('isContinuation') isContinuation: string = 'false';
    // 标识目前是否在游戏
  @StorageLink('isGameStart') isGameStart: boolean = false;
  // 游戏时间,初始化为5分钟
  @StorageLink('gameTime') @Watch('onTimeOver') gameTime: number = 300;
  // 选择图库图片的下标
  @StorageLink('index') @Watch('onImageChange') index: number = 0;
  1. 页面拼图游戏关键代码
      Grid() {
        ForEach(this.numArray, (item: PictureItem, index:number) => {
          GridItem() {
            // 此处通过文件名,到分布式目录下获取图片
            Image(`${fileUri.getUriFromPath(this.context.distributedFilesDir + '/' + item.fileName)}`)
              .width('99%')
              .objectFit(ImageFit.Fill)
              .height(90)
          }
          .id(`image${index}`)
          .backgroundColor(item.fileName === '' ? '#f5f5f5' : '#ffdead')
          .onClick(() => {
            if (this.isRefresh) {
              return;
            }
            if (this.isGameStart) {
              this.isRefresh = true;
              this.numArray = this.game.gameInit(index, this.numArray);
              this.gameOver();
              this.isRefresh = false;
            }
          })
        }, (item: PictureItem) => JSON.stringify(item))
      }
      .id('IMAGE_GRID')
      .columnsTemplate('1fr 1fr 1fr')
      .columnsGap(2)
      .rowsGap(2)
      .backgroundColor('#ccc')
      .width('100%')
      .height('100%')
EntryAbility关键代码
  1. 相关权限检查
  async checkPermissions(): Promise<void> {
    const accessManager = abilityAccessCtrl.createAtManager();
    try {
      const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
      const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags);
      const grantStatus = await accessManager.checkAccessToken(bundleInfo.appInfo.accessTokenId, permissions[0]);
      if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) {
        accessManager.requestPermissionsFromUser(this.context, permissions);
      }
    } catch (err) {
      console.error('EntryAbility', 'checkPermissions', `Catch err: ${err}`);
      return;
    }
  }
  1. 自由流转前,数据存储
  onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
    wantParam.isGameStart = AppStorage.get('isGameStart') as string;
    wantParam.gameTime = AppStorage.get('gameTime') as string;
    wantParam.isContinuation = AppStorage.get('isContinuation') as string;
    wantParam.index = AppStorage.get('index') as string;
    try {
      let sessionId: string = distributedDataObject.genSessionId();

      if (this.localObject) {
        this.localObject.setSessionId(sessionId);
        let numArrayStr: string = JSON.stringify(AppStorage.get<Array<PictureItem>>('numArray'));
        hilog.info(0x0000, 'testTag', '%{public}s', '**onContinue numArrayStr: ' + numArrayStr);
        this.localObject['numArray'] = AppStorage.get<Array<PictureItem>>('numArray'); //numArrayStr;
        this.targetDeviceId = wantParam.targetDevice as string;
        this.localObject.save(wantParam.targetDevice as string).then(() => {
          hilog.info(0x0000, 'testTag', '%{public}s', 'onContinue localObject save success');
        }).catch((err: BusinessError) => {
          hilog.error(0x0000, 'testTag', '%{public}s', `Failed to save. Code:${err.code},message:${err.message}`);
        });
      }
      wantParam.distributedSessionId = sessionId;
    } catch (error) {
      hilog.error(0x0000, 'testTag', '%{public}s distributedDataObject failed', `code ${(error as BusinessError).code}`);
    }
    return AbilityConstant.OnContinueResult.AGREE;
  }
  1. 自由流转后,数据恢复
async restoringData(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
    // 检查相关权限
    this.checkPermissions();
    if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
      AppStorage.setOrCreate<string>('isContinuation', 'true');
      AppStorage.setOrCreate<boolean>('isGameStart', want.parameters?.isGameStart as boolean);
      AppStorage.setOrCreate<number>('gameTime', want.parameters?.gameTime as number);
      AppStorage.setOrCreate<number>('index', want.parameters?.index as number);
      let sessionId : string = want.parameters?.distributedSessionId as string;
      if (!this.localObject) {
        let imageInfo: ImageInfo = new ImageInfo(undefined);
        this.localObject = distributedDataObject.create(this.context, imageInfo);
        this.localObject.on('change', this.changeCall);
      }
      if (sessionId && this.localObject) {
        await this.localObject.setSessionId(sessionId);
        let numArrayStr: string = JSON.stringify(this.localObject['numArray']);
        hilog.info(0x0000, 'testTag', '%{public}s', '**restoringData numArrayStr: ' + numArrayStr);
        AppStorage.setOrCreate<Array<PictureItem>>('numArray', this.localObject['numArray']);
      }
      this.context.restoreWindowStage(new LocalStorage());
    }
  }
  1. 在onCreate生命周期时, 调用数据恢复
  async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    await this.restoringData(want, launchParam);
  }
  1. 在onNewWant生命周期时, 调用数据恢复
  async onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam) {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onNewWant');
    await this.restoringData(want, launchParam);
  }
  1. 在onWindowStageCreate生命周期,创建分布式对象
  onWindowStageCreate(windowStage: window.WindowStage): void {
	......
    if (!this.localObject) {
      let imageInfo: ImageInfo = new ImageInfo(undefined);
      this.localObject = distributedDataObject.create(this.context, imageInfo);
    }
  }
  1. 在onDestroy生命周期,保存对象,保存对象数据成功后,应用退出时对象数据不会释放,应用退出后,在保存的设备上恢复数据。
  async onDestroy() {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
    if (this.localObject && this.targetDeviceId) {
      await this.localObject.save(this.targetDeviceId).then(() => {
        hilog.info(0x0000, 'testTag', 'onDestroy localObject save success');
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, 'testTag', `Failed to save. Code:${err.code},message:${err.message}`);
      });
    }
  }

总结

通过此案例,可以回顾 九宫格切图 案例,同时学习到跨设备文件访问知识点和分布式对象跨设备数据同步知识点,通过简单的案例,运用上各方面的知识点,为之后的大项目做好准备,大家动手起来吧!!!

相关权限

读取图片及视频权限:ohos.permission.READ_IMAGEVIDEO

修改图片或视频权限:ohos.permission.WRITE_IMAGEVIDEO

允许不同设备间的数据交换权限:ohos.permission.DISTRIBUTED_DATASYNC

约束与限制

1.本示例仅支持标准系统上运行,支持设备:华为手机。

2.HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。

3.DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。

4.HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。

标签:case,index,emptyIndex,pictures,九宫格,流转,let,拼图游戏,分布式
From: https://www.cnblogs.com/army16/p/18474351

相关文章

  • 九宫格切图-创意分享新风尚
    作者:狼哥团队:坚果派团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原......
  • 一文详解:跨国医疗机构安全合规文件流转的跨境传输解决办法
    跨国医疗机构是指那些能够在不同国家之间提供医疗服务的机构,它们通常具有国际化的医疗网络、专业的医疗团队和先进的医疗设备。这些机构不仅能够帮助患者获取国外优质的医疗资源,还能提供包括医疗咨询、治疗安排、病历翻译、签证办理、海外陪同等在内的全方位服务。跨国医疗机构......
  • 九宫格(html css实现)---初识flex布局
    记录flex属性并实现一个九宫格flex属性Flex容器:需要注意的是:当时设置flex布局之后,子元素的float、clear、vertical-align的属性将会失效.container{display:flex;}//块状元素.container{inline-flex;}//行内元素块状元素1.***独占一行:块元素会自动......
  • ActivityManagerService app状态流转(4)
    ActivityManagerServiceapp状态流转简述做过应用开发应该会对Activity的生命周期很熟悉,Activity有onCreate、onStart、onResume…等生命周期,前面在介绍Activity启动流程时,我们提到过SystemServer会通过ClientTransaction来通知app更新生命周期状态变化,以前SystemServer和......
  • Java计算机毕业设计药库药品智能入库出库及流转管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着医疗技术的不断进步和人们对健康需求的日益增长,药品管理成为医疗机构运营中至关重要的一环。传统的手工或半自动化药品管理模式已难以满足现代医......
  • 细聊C# AsyncLocal如何在异步间进行数据流转--源码探究
    前言#    在异步编程中,处理异步操作之间的数据流转是一个比较常用的操作。C#异步编程提供了一个强大的工具来解决这个问题,那就是AsyncLocal。它是一个线程本地存储的机制,可以在异步操作之间传递数据。它为我们提供了一种简单而可靠的方式来共享数据,而不必担心线程切换或异步......
  • 最新LiveNVR版本优化解决大疆无人机推花屏问题,实现大疆无人机RTMP推流转GB28181级联输
    @目录1、无人机推流转国标2、获取RTMP推流地址2.1、RTMP推流地址格式2.2、推流地址示例2、设备RTMP推流3、配置拉转RTMP3.1、直播流地址格式3.2、直播流地地址示例3.3、通道配置直播流地址4、配置级联到GB28181国标平台5、更多问题5.1、大疆无人机推流花屏6、非国标直播流转GB2818......
  • 安全高效,一键搞定:Ftrans文件摆渡系统让数据流转更简单!
    随着互联网技术的不断发展,网络攻击手段也不断的更新,为了防止外部攻击和内部数据泄密,高科技企业一般会实施内外网隔离,甚至在内部网络中又划分出业务网、办公网、生产网等进行隔离。但基于业务的需求,隔离网间仍存在文件传输的需求。如何通过文件摆渡系统,在保证数据安全的前提下,高效......
  • L1-104 九宫格 分数 20
    #include<bits/stdc++.h>usingnamespacestd;intarr[10][10];intmain(){intn;cin>>n;for(intt=1;t<=n;++t){for(intj=1;j<=9;++j)for(intk=1;k<=9;++k) cin......
  • 【鸿蒙学习】HarmonyOS应用开发者高级认证 - 自由流转
    学完时间:2024年8月21日学完排名:第2253名一、基本概念1.流转在HarmonyOS中,将跨多设备的分布式操作统称为流转。流转能力打破设备界限,多设备联动,使用户应用程序可分可合、可流转,实现如邮件跨设备编辑、多设备协同健身、多屏游戏等分布式业务。流转为开发者提供更广的使......