HarmonyOS NEXT Development Case Study: Jigsaw Puzzle
zhongcx

zhongcx @zhongcx

About: The best time to plant a tree was 10 years ago, the second-best time is now.

Joined:
Mar 6, 2025

HarmonyOS NEXT Development Case Study: Jigsaw Puzzle

Publish Date: May 11
0 0

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
Enter fullscreen mode Exit fullscreen mode

2. Data Structure

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

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
Enter fullscreen mode Exit fullscreen mode

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));
  }
}
Enter fullscreen mode Exit fullscreen mode

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));
  }
}
Enter fullscreen mode Exit fullscreen mode

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%');
}
Enter fullscreen mode Exit fullscreen mode

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.

Comments 0 total

    Add comment