九宫格自由流转拼图游戏

news/2024/10/18 15:21:37/文章来源:https://www.cnblogs.com/army16/p/18474351
作者:狼哥
团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有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及以上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/816898.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据预处理-DataFrame切片

此Blog仅作为日常学习工作中记录使用,Blog中有不足之处欢迎指出 以kaggle中房屋预测的训练集为例,说明DataFrame切片常用操作 一、读入数据 import numpy as np import pandas as pdfile_path = ***\kaggle_house_pred_train.csv data = pd.read_csv(file_path)data.columns …

轻松上手-Navigation路由 H5

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

Audio

音频文件导入 常用格式wav mp3 aiff音频文件属性设置Force To Mono:多声道转单声道; Normalize:强制为单声道时,混合过程中被标准化 Load In Background:在后台加载时,不阻塞主线程 Ambisonic:立体混响声,非常适合360度视频和XR应用程序,如果音频文件包含立体混响声编…

RenderTexture

基础概念 RenderTexture 在Unity中,RenderTexture是一种纹理,它可以在运行时动态地生成图像数据,并且可以作为着色器的输入或者显示在游戏世界的任何材质上。简单来说,RenderTexture让你能够将场景或特定相机的渲染结果捕获到一个纹理中,然后这个纹理可以被用作其他渲染过…

九点共圆及其圆心证明(证明过程已更新)

九点共圆及其圆心证明主要思路:固定 \(P,L\),证明其它七个点均在以 \(PL\) 为直径的圆上。 条件的来源会备注在括号内。背景可能影响观感,建议打开极简模式阅读。这是一个三角形 \(\triangle ABC\),设 \(BC,AC,AB\) 边上垂足分别为 \(D,E,F\),其边上中点分别为 \(L,M,N\),…

【喜讯】全球电商大数据平台推出F类免费API接口服务!

好消息,全球电商大数据平台再次上线多个类别接口,首次推出F类免费API接口服务,为所有追求数据赋能、寻求创新突破的企业和个人,带来了前所未有的机遇。好消息,值此之际;全球电商大数据平台再次上线多个类别接口,首次推出F类免费API接口服务,为所有追求数据赋能、寻求创…

c# winform在线升级clickonce

说明:在线升级前提 1,一个可以访问在线的地址,2,发布前要在项目属性发布里配置好相关设置 一,可以在IIS上布署一个可以访问的地址 二,发布前配置

SQLSEVER 实现货币数字转中文汉字

SQLSEVER 实现数字转换成中文(货币) -- ============================================= -- Author: LearnerPing -- Create date: 2024/10/18 -- Description: Change Number to Chinese -- ============================================= Create FUNCTION GetNumberToChi…

javascript渲染OFD的库

目前使用javascript开发的OFD的渲染库主要有两个: ofd.js和liteofd,其中ofd.js开发比较早,liteofd是最近刚出现的js库。首先结论是ofd.js渲染效果没有liteofd好,因为ofd.js目前有一些效果没有支持,比如对字体没有比较好的解析和支持。 liteofd相对ofd.js效果更好,并且提供…

高等数学 6.1 定积分的元素法

在定积分的应用中,经常采用所谓的元素法。为了说明这种方法,先回顾一下曲边梯形的面积问题。 设 \(f(x)\) 在区间 \([a, b]\) 上连续且 \(f(x) \geqslant 0\) ,求以曲线 \(y = f(x)\) 为曲边、底为 \([a, b]\) 的曲边梯形的面积 \(A\) 。把这个面积 \(A\) 表示为定积分 \[A …

详解 JuiceFS 在多云架构下的数据同步与一致性

随着大模型流行,GPU 算力资源正变得日益稀缺,传统的“算力跟着存储跑”的策略需要转变为“存储跟着算力跑”。为了确保数据一致性和管理的便捷性,企业通常在特定地区的公有云上选择对象存储作为所有模型数据的集中存储点。当进行计算任务调度时,往往需要人工介入,手动进行…

Renderer

Renderer 模块 Renderer 模块的设置决定了粒子的图像或网格如何被其他粒子变换、着色和过度绘制。 粒子系统Unity中创建粒子系统渲染器模块视图 细节 使用渲染模式在多种 2D Billboard 图形模式和网格模式之间进行选择。当粒子代表固体游戏对象(例如岩石)时,3D 网格赋予粒子…