Stories

Detail Return Return

鴻蒙元服務實戰-笑笑五子棋(5) - Stories Detail

鴻蒙元服務實戰-笑笑五子棋(5)

來到最後一章了,這一章節講兩個部分。一是笑笑五子棋的卡片製作,二就是發佈上架。

卡片介紹

Form Kit(卡片開發框架)提供了一種在桌面、鎖屏等系統入口嵌入顯示應用信息的開發框架和 API,可以將應用內用户關注的重要

信息或常用操作抽取到服務卡片(以下簡稱“卡片”)上,通過將卡片添加到桌面上,以達到信息展示、服務直達的便捷體驗效果。

image-20250105154516700

新建卡片

卡片類型分為兩種:

  1. 靜態卡片 功能稍弱
  2. 動態卡片 功能強一些

image-20250105154650672

  1. 選擇卡片的屬性

    image-20250105155715639

  2. 然後你就得到了以下文件

    image-20250105154810895

卡片文件解釋

EntryFormAbility.ets

entry/src/main/ets/entryformability/EntryFormAbility.ets

該文件可以定義卡片的生命週期,傳遞數據給卡片等

WidgetCard

entry/src/main/ets/widget/pages/WidgetCard.ets

該文件是卡片的主要展示和業務功能頁面。 卡片外觀、功能主要由它來提供

form_config.json

entry/src/main/resources/base/profile/form_config.json

該文件是卡片的配置文件,比如卡片的圖標、卡片的名字、卡片的種類等等都可以在這配置

獲取卡片寬度

卡片的 api 和元服務的 api 稍有區別,所以在開發的需要額外注意

這裏在 entry/src/main/ets/entryformability/EntryFormAbility.ets 內,可以設置卡片創建的時獲取卡片的寬度

因為卡片有不同的規格尺寸,所以可以動態來獲取。

  onAddForm(want: Want) {
    let formData: Record<string, number> = {
      "canwidth": px2vp((want.parameters?.[formInfo.FormParam.WIDTH_KEY] as number) * 2),
    };
    return formBindingData.createFormBindingData(formData);
  }

卡片中是無法使用 AppStorage,所以需要使用 Localstorage 來代替,進行數據傳遞

卡片中接收

@Entry
@Component
struct WidgetCard {
  @LocalStorageProp("canwidth")
  canwidth: number = 0
  @LocalStorageProp("canwidth")
  canheight: number = 0

完成卡片下棋邏輯

因為卡片的下棋邏輯和宿主-元服務本身幾乎一致。因此在實際開發中,可以將它們共同的邏輯抽離出來方便管理。這裏就 cv 複用了。

@Entry
@Component
struct WidgetCard {
  @LocalStorageProp("canwidth")
  canwidth: number = 0
  @LocalStorageProp("canwidth")
  canheight: number = 0
  settings: RenderingContextSettings = new RenderingContextSettings(true);
  ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  // 棋盤參數
  gridSize: number = 15;
  cellSize: number = this.canwidth / this.gridSize;
  radius: number = this.cellSize / 2 - 5; // 棋子的半徑
  // 棋盤數據
  board: number[][] = []
  currentPlayer: number = 1; // 當前玩家 (1: 黑子, 2: 白子)
  gameOver: boolean = false;
  @State
  textContent: string = ""
  // 處理玩家落子
  handleClick = async (event: ClickEvent) => {
    if (this.gameOver) {
      return;
    }

    const x = event.x;
    const y = event.y;

    const col = Math.floor(x / this.cellSize);
    const row = Math.floor(y / this.cellSize);

    if (this.board[row] && this.board[row][col] === 0) {
      this.board[row][col] = this.currentPlayer;
      this.drawBoard();

      if (this.checkWin(row, col)) {
        this.textContent = this.currentPlayer === 1 ? '黑子勝利!' : '白子勝利!';
        this.gameOver = true;
        // AlertDialog.show({ message: this.textContent })


      } else {
        this.currentPlayer = this.currentPlayer === 1 ? 2 : 1;
        this.textContent = this.currentPlayer === 1 ? '輪到黑子落子' : '輪到白子落子';
      }
    } else {
      // promptAction.showToast({ message: `請點擊中棋盤對位位置` })
    }
  }

  aboutToAppear(): void {

  }

  // 繪製棋盤
  drawBoard = () => {
    this.ctx.clearRect(0, 0, this.canwidth, this.canwidth);

    // 繪製網格
    this.ctx.strokeStyle = "#000";
    this.ctx.lineWidth = 1;
    for (let i = 0; i < this.gridSize; i++) {
      this.ctx.beginPath();
      this.ctx.moveTo(this.cellSize * i, 0);
      this.ctx.lineTo(this.cellSize * i, this.canwidth);
      this.ctx.stroke();

      this.ctx.beginPath();
      this.ctx.moveTo(0, this.cellSize * i);
      this.ctx.lineTo(this.canwidth, this.cellSize * i);
      this.ctx.stroke();
    }

    // 繪製已落的棋子
    for (let row = 0; row < this.gridSize; row++) {
      for (let col = 0; col < this.gridSize; col++) {
        if (this.board[row][col] !== 0) {
          this.ctx.beginPath();
          this.ctx.arc(col * this.cellSize + this.cellSize / 2, row * this.cellSize + this.cellSize / 2, this.radius, 0,
            2 * Math.PI);
          this.ctx.fillStyle = this.board[row][col] === 1 ? 'black' : 'white';
          this.ctx.fill();
          this.ctx.stroke();
        }
      }
    }
  }
  // 判斷是否有五子連珠
  checkWin = (row: number, col: number) => {
    interface abc {
      dr: number
      dc: number
    }

    const directions: abc[] = [
      { dr: 0, dc: 1 }, // 水平
      { dr: 1, dc: 0 }, // 垂直
      { dr: 1, dc: 1 }, // 主對角線
      { dr: 1, dc: -1 }// 副對角線
    ];

    for (let i = 0; i < directions.length; i++) {
      const dr = directions[i].dr
      const dc = directions[i].dc
      let count = 1;

      // 向一個方向檢查
      for (let i = 1; i < 5; i++) {
        let r = row + dr * i;
        let c = col + dc * i;
        if (r >= 0 && r < this.gridSize && c >= 0 && c < this.gridSize && this.board[r][c] === this.currentPlayer) {
          count++;
        } else {
          break;
        }
      }

      // 向另一個方向檢查
      for (let i = 1; i < 5; i++) {
        let r = row - dr * i;
        let c = col - dc * i;
        if (r >= 0 && r < this.gridSize && c >= 0 && c < this.gridSize && this.board[r][c] === this.currentPlayer) {
          count++;
        } else {
          break;
        }
      }

      // 如果連續五個相同的棋子,則勝利
      if (count >= 5) {
        return true;
      }
    }

    return false;
  }
  // 初始化遊戲
  initGame = () => {
    this.board = []
    for (let index = 0; index < this.gridSize; index++) {
      const arr: number[] = []
      for (let index2 = 0; index2 < this.gridSize; index2++) {
        arr.push(0)

      }
      this.board.push(arr)

    }
    this.currentPlayer = 1;
    this.gameOver = false;
    this.textContent = '輪到黑子落子';
    this.drawBoard();
  }

  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      Canvas(this.ctx)
        .width(this.canwidth)
        .height(this.canwidth)
        .backgroundColor(Color.Orange)
        .onReady(() => {
          this.cellSize = this.canwidth / this.gridSize;
          this.radius = this.cellSize / 2 - 5; // 棋子的半徑
          this.initGame()
        })
        .onClick(
          this.handleClick
        )

      Text(this.textContent)
        .fontSize(14)
        .padding(5)
        .fontColor(Color.White)
        .fontWeight(700)

    }
    .width("100%")
    .height("100%")
  }
}

調整卡片的圖標和名字

主要業務開發完畢了,可以調整卡片的展示信息

image-20250105160500387

這部分信息在 entry/src/main/resources/base/profile/form_config.json中配置:

  1. displayName 標題
  2. description 簡介
{
  "forms": [
    {
      "name": "widget",
      "displayName": "$string:widget_display_name",
      "description": "$string:widget_desc",
      "src": "./ets/widget/pages/WidgetCard.ets",
      "uiSyntax": "arkts",
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "colorMode": "auto",
      "isDynamic": true,
      "isDefault": true,
      "updateEnabled": false,
      "scheduledUpdateTime": "10:30",
      "updateDuration": 1,
      "defaultDimension": "4*4",
      "supportDimensions": [
        "2*2",
        "4*4"
      ]
    }
  ]
}

發佈上架

最後,如果要將卡片發佈上架,還需要做一些小處理

  1. 設置你的元服務的展示圖標
  2. 配置證書
  3. 打包成 Hap
  4. 在 AGC 平台上發佈上架等等
  5. 具體流程可以參考底部的文章

參考鏈接

  1. 卡片開發
  2. HarmonyOS Next 實戰卡片開發 01
  3. HarmonyOS Next 實戰卡片開發 02
  4. HarmonyOS Next 實戰卡片開發 03
  5. HarmonyOS Next 最新 元服務新建到上架全流程

代碼倉庫

https://gitee.com/ukSir/laughing-at-gomoku

總結

至此,笑笑五子棋的開發上架流程已經完畢。

如果你興趣想要了解更多的鴻蒙應用開發細節和最新資訊,歡迎在評論區留言或者私信或者看我個人信息,可以加入技術交流羣。

user avatar u_9449786 Avatar josie_68d213f999ae8 Avatar heimatengyun Avatar yanyanyan_ Avatar
Favorites 4 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.