HarmonyOS NEXT Development Case Study: Jigsaw Puzzle

This article demonstrates a jigsaw puzzle implementation using HarmonyOS NEXT, featuring image selection, puzzle piece generation, shuffling, and game logic. The solution leverages ArkUI components and HarmonyOS native capabilities. Project Structure 1. Module Imports // Core modules import { fileIo as fs } from '@kit.CoreFileKit'; // File operations module import { common } from '@kit.AbilityKit'; // Application capability module import { promptAction } from '@kit.ArkUI'; // UI prompt module import { image } from '@kit.ImageKit'; // Image processing module import { photoAccessHelper } from '@kit.MediaLibraryKit'; // Media library access module 2. Data Structure interface PuzzlePiece { pixelMap: image.PixelMap; // Pixel map of the puzzle piece originalIndex: number; // Original position index in the image } Core Implementation 3. Page Component Setup @Entry // Entry component @Component // Custom component struct Page30 { @State selectedImageUrl: string = ''; // Displayed image URI @State originalImageUrl: string = ''; // Source image URI @State puzzlePieces: Array = []; // Puzzle pieces array @State selectedPiece: number = -1; // Selected piece index 4. Image Selection Logic async openPicker() { try { const PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; PhotoSelectOptions.maxSelectNumber = 1; const photoPicker = new photoAccessHelper.PhotoViewPicker(); const uris = await photoPicker.select(PhotoSelectOptions); if (!uris?.photoUris.length) return; const uri = uris.photoUris[0]; // File operations const file = fs.openSync(uri, fs.OpenMode.READ_ONLY); const context = getContext(this) as common.UIAbilityContext; const newUrl = `${context.filesDir}/test${Date.now()}.jpg`; fs.copyFileSync(file.fd, newUrl); fs.closeSync(file); this.selectedImageUrl = newUrl; this.originalImageUrl = uri; this.imgChange(); } catch (e) { console.error('openPicker', JSON.stringify(e)); } } 5. Image Processing async imgChange() { try { const imageSource = image.createImageSource(this.selectedImageUrl); const decodingOptions: image.DecodingOptions = { editable: true, desiredPixelFormat: 3 }; const mPixelMap = await imageSource.createPixelMap(decodingOptions); const mImageInfo = await mPixelMap.getImageInfo(); // Calculate grid dimensions const pieceSize: image.Size = { width: mImageInfo.size.width / 3, height: mImageInfo.size.height / 3 }; this.puzzlePieces = []; let count = 0; // Generate puzzle pieces for (let row = 0; row this.openPicker()); if (this.originalImageUrl) { Text('Original Image ↓'); Image(this.originalImageUrl) .size({ width: '180lpx', height: '180lpx' }) .objectFit(ImageFit.Contain); } if (this.puzzlePieces.length) { Text('Puzzle Game ↓'); Grid() { ForEach(this.puzzlePieces, (item, index) => { GridItem() { Image(item.pixelMap) .size({ 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 { // Swap pieces [this.puzzlePieces[this.selectedPiece], this.puzzlePieces[index]] = [this.puzzlePieces[index], this.puzzlePieces[this.selectedPiece]]; // Check completion const isComplete = this.puzzlePieces.every( (piece, i) => piece.originalIndex === i ); this.selectedPiece = -1; if (isComplete) { promptAction.showDialog({ message: 'Puzzle Completed!' }); } } }); } }) } .backgroundColor('#fafafa'); } .width('100%'); } Key Features Media Library Integration: Uses photoAccessHelper for secure image selection Implements proper file handling with sandboxed storage Image Processing: Dynamic grid division based on image dimensions Non-destructive editing using pixel map operations Game Mechanics: Fisher-Yates shuffle algorithm Visual selection feedback with scaling animation Real-time completion check Performance: Asynchronous operations for image processing Memory management through proper resource cleanup This implementation demonstrates HarmonyOS NEXT's capabilities in building interactive media applications w

May 11, 2025 - 02:12
 0
HarmonyOS NEXT Development Case Study: Jigsaw Puzzle

Image description

This article demonstrates a jigsaw puzzle implementation using HarmonyOS NEXT, featuring image selection, puzzle piece generation, shuffling, and game logic. The solution leverages ArkUI components and HarmonyOS native capabilities.

Project Structure

1. Module Imports

// Core modules
import { fileIo as fs } from '@kit.CoreFileKit'; // File operations module
import { common } from '@kit.AbilityKit'; // Application capability module
import { promptAction } from '@kit.ArkUI'; // UI prompt module
import { image } from '@kit.ImageKit'; // Image processing module
import { photoAccessHelper } from '@kit.MediaLibraryKit'; // Media library access module

2. Data Structure

interface PuzzlePiece {
  pixelMap: image.PixelMap; // Pixel map of the puzzle piece
  originalIndex: number; // Original position index in the image
}

Core Implementation

3. Page Component Setup

@Entry // Entry component
@Component // Custom component
struct Page30 {
  @State selectedImageUrl: string = ''; // Displayed image URI
  @State originalImageUrl: string = ''; // Source image URI
  @State puzzlePieces: Array<PuzzlePiece> = []; // Puzzle pieces array
  @State selectedPiece: number = -1; // Selected piece index

4. Image Selection Logic

async openPicker() {
  try {
    const PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
    PhotoSelectOptions.maxSelectNumber = 1;

    const photoPicker = new photoAccessHelper.PhotoViewPicker();
    const uris = await photoPicker.select(PhotoSelectOptions);

    if (!uris?.photoUris.length) return;
    const uri = uris.photoUris[0];

    // File operations
    const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
    const context = getContext(this) as common.UIAbilityContext;
    const newUrl = `${context.filesDir}/test${Date.now()}.jpg`;

    fs.copyFileSync(file.fd, newUrl);
    fs.closeSync(file);

    this.selectedImageUrl = newUrl;
    this.originalImageUrl = uri;
    this.imgChange();
  } catch (e) {
    console.error('openPicker', JSON.stringify(e));
  }
}

5. Image Processing

async imgChange() {
  try {
    const imageSource = image.createImageSource(this.selectedImageUrl);
    const decodingOptions: image.DecodingOptions = {
      editable: true,
      desiredPixelFormat: 3
    };

    const mPixelMap = await imageSource.createPixelMap(decodingOptions);
    const mImageInfo = await mPixelMap.getImageInfo();

    // Calculate grid dimensions
    const pieceSize: image.Size = {
      width: mImageInfo.size.width / 3,
      height: mImageInfo.size.height / 3
    };

    this.puzzlePieces = [];
    let count = 0;

    // Generate puzzle pieces
    for (let row = 0; row < 3; row++) {
      for (let col = 0; col < 3; col++) {
        const cutRegion: image.Region = {
          x: col * pieceSize.width,
          y: row * pieceSize.height,
          size: pieceSize
        };

        const newSource = image.createImageSource(this.selectedImageUrl);
        const pieceMap = await newSource.createPixelMap(decodingOptions);
        await pieceMap.crop(cutRegion);

        this.puzzlePieces.push({
          pixelMap: pieceMap,
          originalIndex: count++
        });
      }
    }

    // Shuffle algorithm
    for (let i = this.puzzlePieces.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [this.puzzlePieces[i], this.puzzlePieces[j]] = 
        [this.puzzlePieces[j], this.puzzlePieces[i]];
    }
  } catch (e) {
    console.error('imgChange', JSON.stringify(e));
  }
}

UI Implementation

6. Game Interface

build() {
  Column() {
    Button('Select Image →')
      .onClick(() => this.openPicker());

    if (this.originalImageUrl) {
      Text('Original Image ↓');
      Image(this.originalImageUrl)
        .size({ width: '180lpx', height: '180lpx' })
        .objectFit(ImageFit.Contain);
    }

    if (this.puzzlePieces.length) {
      Text('Puzzle Game ↓');
      Grid() {
        ForEach(this.puzzlePieces, (item, index) => {
          GridItem() {
            Image(item.pixelMap)
              .size({ 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 {
                  // Swap pieces
                  [this.puzzlePieces[this.selectedPiece], this.puzzlePieces[index]] =
                    [this.puzzlePieces[index], this.puzzlePieces[this.selectedPiece]];

                  // Check completion
                  const isComplete = this.puzzlePieces.every(
                    (piece, i) => piece.originalIndex === i
                  );

                  this.selectedPiece = -1;
                  if (isComplete) {
                    promptAction.showDialog({ message: 'Puzzle Completed!' });
                  }
                }
              });
          }
        })
      }
      .backgroundColor('#fafafa');
  }
  .width('100%');
}

Key Features

  1. Media Library Integration:

    • Uses photoAccessHelper for secure image selection
    • Implements proper file handling with sandboxed storage
  2. Image Processing:

    • Dynamic grid division based on image dimensions
    • Non-destructive editing using pixel map operations
  3. Game Mechanics:

    • Fisher-Yates shuffle algorithm
    • Visual selection feedback with scaling animation
    • Real-time completion check
  4. Performance:

    • Asynchronous operations for image processing
    • Memory management through proper resource cleanup

This implementation demonstrates HarmonyOS NEXT's capabilities in building interactive media applications with proper resource management and responsive UI design.