【HarmonyOS】九宫格拼图游戏

基于HarmonyOS实现了一个基本的拼图游戏功能。其中包含用于选择图片、加载图片、切割图片成九宫格拼图块、随机打乱拼图块顺序以及构建用户界面来显示拼图的功能。

用户可以通过点击“选择图片→”按钮来从设备相册中选择一张图片,程序会将所选图片复制到应用的私有目录下,并显示出来。接着,程序将加载的图片分割成3x3的拼图块,并将这些拼图块以打乱的方式展示在界面上。用户可以点击拼图块进行交换,以恢复图片的原始顺序。当拼图正确完成时,会弹出一个提示对话框通知用户“拼图完成”。

【算法分析】

1. 随机打乱算法

用于打乱拼图块的顺序,使得初始时拼图不会是有序的,增加了游戏的难度与趣味性。

描述: 该算法通过 Fisher-Yates shuffle 算法来实现列表的随机化,确保每次生成的序列都是随机且不同的。
 

// 打乱拼图块顺序
for (let i = this.puzzlePieces.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    let temp: PuzzlePiece = this.puzzlePieces[i];
    this.puzzlePieces[i] = this.puzzlePieces[j];
    this.puzzlePieces[j] = temp;
}


2. 图像裁剪算法

用于将原始图片分割成九个部分,作为拼图游戏中的各个拼图块。

描述: 这是一个简单的图像分割过程,根据图像的尺寸将其平均分为三行三列的小块。

// 计算裁剪区域
const cutRegion: image.Region = {
    x: col * pieceSize.width,
    y: row * pieceSize.height,
    size: pieceSize,
};
// 裁剪像素地图
await mPixelMap.crop(cutRegion);

3. 图像交换算法

用于实现用户交互,即用户点击拼图块时能够交换拼图块的位置。

描述: 当用户点击拼图块时,如果之前没有选中任何块,则选中当前点击的块;如果之前已经有选中的块并且再次点击相同的块,则取消选中;否则就交换两个块的位置。

代码示例片段:

// 处理拼图交换逻辑
if (this.selectedPiece == -1) {
    this.selectedPiece = index;
} else if (this.selectedPiece == index) {
    this.selectedPiece = -1;
} else {
    let temp: PuzzlePiece = this.puzzlePieces[this.selectedPiece];
    this.puzzlePieces[this.selectedPiece] = this.puzzlePieces[index];
    this.puzzlePieces[index] = temp;
    this.selectedPiece = -1;
    // 检查拼图是否完成
    let isSucc: boolean = true;
    for (let i = 0; i < this.puzzlePieces.length; i++) {
        if (this.puzzlePieces[i].originalIndex !== i) {
            isSucc = false;
            break;
        }
    }
    // 如果拼图完成,弹出提示对话框
    if (isSucc) {
        promptAction.showDialog({
            message: '拼图完成!',
        });
    }
}

【完整代码】

import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { promptAction } from '@kit.ArkUI';
import { image } from '@kit.ImageKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';

// 定义拼图组件接口
interface PuzzlePiece {
  // 拼图块的像素地图
  pixelMap: image.PixelMap;
  // 原始图片中的索引位置
  originalIndex: number;
}

// 使用装饰器定义页面组件
@Entry
@Component
struct Page30 {
  // 状态变量:选中图片的URI
  @State selectedImageUrl: string = '';
  // 状态变量:原始图片的URI
  @State originalImageUrl: string = '';
  // 状态变量:存储拼图块的数组
  @State puzzlePieces: Array<PuzzlePiece> = [];
  // 状态变量:记录当前选中的拼图块索引
  @State selectedPiece: number = -1;

  // 弹出图片选择器方法
  async openPicker() {
    try {

      let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      PhotoSelectOptions.maxSelectNumber = 1;
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      let uris: photoAccessHelper.PhotoSelectResult = await  photoPicker.select(PhotoSelectOptions)

      if (!uris || uris.photoUris.length === 0) return;
      // 获取选中图片的第一张URI
      let uri: string = uris.photoUris[0];
      // 打开文件读取流
      let file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
      // 获取当前上下文
      let context = getContext(this) as common.UIAbilityContext;
      // 新建一个保存裁剪后图片的路径
      let newUrl = context.filesDir + '/test' + new Date().getTime() + '.jpg';
      // 复制图片到新的路径
      fs.copyFileSync(file.fd, newUrl);
      // 关闭文件读取流
      fs.closeSync(file);
      // 更新状态变量:设置显示图片的URI
      this.selectedImageUrl = newUrl;
      // 更新状态变量:保存原始图片的URI
      this.originalImageUrl = uri;
      // 图片更改时触发的方法
      this.imgChange();
    } catch (e) {
      console.error('openPicker', JSON.stringify(e));
    }
  }

  // 图片更改处理方法
  async imgChange() {
    try {
      // 创建图片源对象
      const imageSource: image.ImageSource = image.createImageSource(this.selectedImageUrl);
      // 图片解码选项
      let decodingOptions: image.DecodingOptions = {
        editable: true,
        desiredPixelFormat: 3,
      };
      // 创建像素地图
      let mPixelMap: image.PixelMap = await imageSource.createPixelMap(decodingOptions);
      // 获取图片信息
      let mImageInfo: image.ImageInfo = await mPixelMap.getImageInfo();
      // 计算每个拼图块的大小
      const pieceSize: image.Size = {
        width: mImageInfo.size.width / 3,
        height: mImageInfo.size.height / 3,
      };
      // 清空已有拼图块数据
      this.puzzlePieces.splice(0);
      // 遍历图片生成9个拼图块
      let count = 0;
      for (let row = 0; row < 3; row++) {
        for (let col = 0; col < 3; col++) {
          // 创建基于原图的新图片源
          const imageSource = image.createImageSource(this.selectedImageUrl);
          // 创建新像素地图
          let mPixelMap = await imageSource.createPixelMap(decodingOptions);
          // 计算裁剪区域
          const cutRegion: image.Region = {
            x: col * pieceSize.width,
            y: row * pieceSize.height,
            size: pieceSize,
          };
          // 裁剪像素地图
          await mPixelMap.crop(cutRegion);
          // 创建并添加拼图块至数组
          const piece: PuzzlePiece = {
            pixelMap: mPixelMap,
            originalIndex: count++,
          };
          this.puzzlePieces.push(piece);
        }
      }
      // 打乱拼图块顺序
      for (let i = this.puzzlePieces.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        let temp: PuzzlePiece = this.puzzlePieces[i];
        this.puzzlePieces[i] = this.puzzlePieces[j];
        this.puzzlePieces[j] = temp;
      }
    } catch (e) {
      console.error('imgChange', JSON.stringify(e));
    }
  }

  // 构建UI界面
  build() {
    Column() {
      // 添加选择图片按钮,点击后调用打开图片选择器方法
      Button('选择图片→').onClick(() => {
        this.openPicker();
      });
      // 显示原始图片(如果已选择)
      if (this.originalImageUrl) {
        Text('原始图片↓');
        Image(this.originalImageUrl)
          .width('180lpx')
          .height('180lpx')
          .objectFit(ImageFit.Contain);
      }
      // 如果有拼图块,则显示游戏区
      if (this.puzzlePieces.length > 0) {
        Text('游戏图片↓');
        // 游戏区域采用网格布局
        Grid() {
          // 遍历所有拼图块并创建网格项
          ForEach(this.puzzlePieces, (item: PuzzlePiece, index: number) => {
            GridItem() {
              // 显示拼图块图像
              Image(item.pixelMap)
                .width('200lpx')
                .height('200lpx')
                .margin('5lpx')
                  // 根据是否选中调整缩放比例
                .scale(this.selectedPiece == index ? { x: 0.5, y: 0.5 } : { x: 1, y: 1 })
                  // 添加点击事件处理
                .onClick(() => {
                  // 处理拼图交换逻辑
                  if (this.selectedPiece == -1) {
                    this.selectedPiece = index;
                  } else if (this.selectedPiece == index) {
                    this.selectedPiece = -1;
                  } else {
                    let temp: PuzzlePiece = this.puzzlePieces[this.selectedPiece];
                    this.puzzlePieces[this.selectedPiece] = this.puzzlePieces[index];
                    this.puzzlePieces[index] = temp;
                    this.selectedPiece = -1;
                    // 检查拼图是否完成
                    let isSucc: boolean = true;
                    for (let i = 0; i < this.puzzlePieces.length; i++) {
                      console.info('====item', this.puzzlePieces[i].originalIndex, i);
                      if (this.puzzlePieces[i].originalIndex !== i) {
                        isSucc = false;
                        break;
                      }
                    }
                    // 如果拼图完成,弹出提示对话框
                    if (isSucc) {
                      promptAction.showDialog({
                        message: '拼图完成!',
                      });
                    }
                  }
                });
            }
          }) // End of ForEach
        } // End of Grid
        .backgroundColor("#fafafa"); // 设置网格背景色
      }
    } // End of Column
    .width('100%'); // 设置列宽度为100%
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值