今天,我给大家带了一个新的项目《变色方块》,下面我介绍一下它的实现过程。

介绍

变色方块是一款小游戏,进入关卡后,有n*n个小方块组成的方块,点击某个小方块,会将这个小方块及其上下左右方向的方块变一种颜色,需将所有浅蓝色方块变成粉色后方可进入下一关

环境搭建

安装DevEco Studio,详情请参考下载和安装软件

设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:• 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。• 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境

开发者可以参考以下链接,完成设备调试的相关配置: 使用真机进行调试• 使用模拟器进行调试

项目结构

【中工开发者】—— 变色方块小游戏_ArkTs

项目展示

【中工开发者】—— 变色方块小游戏_List_02

小游戏第一关如下

【中工开发者】—— 变色方块小游戏_ArkTs_03

点击方块,蓝色方块变为粉色,游戏进入下一关。点击重新开始按钮,提示玩家“会把你传送到第一关”。点击重玩本关按钮,提示玩家“会把你传送到刚进入本关的状态”,并将所有按钮恢复。点击游戏说明按钮,提示玩家游戏规则。如下图所示。

【中工开发者】—— 变色方块小游戏_ArkTs_04


实现代码

主要页面index

翻转turnover):

根据盒子的位置(行和列索引),翻转当前盒子及其上下左右的盒子。

调用 isAllOver 方法检查是否所有盒子都已翻转。

点击盒子时,会触发 turnover 方法,改变盒子的状态并检查游戏是否结束。

判断isAllOver):

遍历所有盒子,检查是否所有盒子都已翻转。

如果所有盒子都已翻转,显示过关对话框,并增加 startLine 以进入下一关。

调用 boxes.reload 方法重新加载新关卡的盒子。

构建build):

使用 Column 和 Row 布局组件构建页面布局。

包含三个按钮:重新开始、重玩本关和游戏说明,每个按钮都有相应的点击事件处理逻辑。

使用 GridRow 和 GridCol 组件创建一个网格布局,用于显示和点击盒子。

ForEach 循环遍历 boxes.boxes,为每个盒子创建一个 boxView 组件。


import { boxes } from "../viewmodel/boxes"
import boxList from "../viewmodel/boxes"
import { box } from "../viewmodel/box"
import { boxView } from '../view/boxview'


@Entry
@Component
struct Index {
  @State boxes: boxes = boxList
  @State startLine: number = 1

  aboutToAppear() {
    boxList.init(this.startLine)
  }

  //翻转
  turnover(box: box) {
    console.log(box.colIndex + " " + box.rowIndex + " " + box.index)
    if (box.rowIndex < this.startLine) {
      this.boxes.boxes[box.index+1].changeTurned()
      console.log("右")
    }
    if (box.rowIndex > 1) {
      this.boxes.boxes[box.index-1].changeTurned()
      console.log("左")
    }
    if (box.colIndex > 1) {
      this.boxes.boxes[box.index-this.startLine].changeTurned()
      console.log("上")
    }
    if (box.colIndex < this.startLine) {
      this.boxes.boxes[box.index+this.startLine].changeTurned()
      console.log("下")
    }

    box.changeTurned()
    this.isAllOver()
  }

  //判断
  isAllOver() {
    for (let i = 0; i < this.boxes.index; i++) {
      console.log(this.boxes.index.toString())
      console.log(this.boxes.boxes[i].turned ? "true" : "false")
      if (!this.boxes.boxes[i].turned) {
        return
      }
    }

    //过关信息
    AlertDialog.show({
      title: "恭喜过关",
      message: `恭喜你进入第${this.startLine + 1}关`,
      confirm: {
        value: "好",
        action: () => {
        }
      }
    })

    this.startLine = this.startLine + 1
    //再次进行加载
    this.boxes.reload(this.startLine)
  }

  build() {
    Column() {
      Row() {
        Text(`第${this.startLine}关`)
          .fontSize(40)
          .width("100%")
          .fontWeight(FontWeight.Bold)
          .textAlign(TextAlign.Center)
      }.width("100%")
      .height("15%")

      Row() {
        Button("重新开始").onClick(() => {
          AlertDialog.show({
            title: "重新开始?",
            message: "会把你传送到第一关",
            confirm: {
              value: "好",
              action: () => {
                this.startLine = 1
                this.boxes.reStart()
              }
            }
          })
        })

        Button("重玩本关").onClick(() => {
          AlertDialog.show({
            title: "重玩本关?",

            message: "会把你传送到刚进入本关的状态",
            confirm: {
              value: "好",
              action: () => {
                this.boxes.reLevel(this.startLine)
              }
            }
          })
        })
        Button("游戏说明").onClick(() => {
          AlertDialog.show({
            title: "游戏说明",
            message: "1.游戏玩法:点击色块,会改变其自身和上下左右相邻色块的颜色。\n" +
            "2.游戏规则:当全部色块变为粉色,即为胜利。",
            confirm: {
              value: "我知道了",
              action: () => {
              }
            }
          })
        })

      }.width("100%")
      .height("25%")
      .justifyContent(FlexAlign.SpaceEvenly)

      Column() {
        GridRow(
          { columns: this.startLine }
        ) {
          ForEach(this.boxes.boxes, (box: box) => {
            GridCol() {
              Row() {
                boxView({ b: box })
              }
              .onClick(() => {
                console.log(box.index.toString())
                this.turnover(box)
              })
            }.width("70%")
            .height(400/this.startLine)
          })
        }
      }.height("600")
      .width("100%")
    }.width("100%").height("100%")
    .backgroundColor("#F0FF00")
  }
}

entryability

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
  onCreate(want, launchParam) {
    (0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy() {
    (0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    // Main window is created, set main page for this ability
    (0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      (0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

  onWindowStageDestroy() {
    // Main window is destroyed, release UI related resources
    (0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground() {
    // Ability has brought to foreground
    (0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground() {
    // Ability has back to background
    (0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

box

这个类可以用于创建和管理游戏界面中的盒子元素,跟踪盒子翻转状态。

@Observed
export class box {

  colIndex:number
  rowIndex:number
  turned:boolean
  index:number


  constructor(colIndex:number,rowIndex:number,turned:boolean,index:number) {
    this.colIndex = colIndex
    this.rowIndex = rowIndex
    this.turned = turned
    this.index = index
  }

  //改变是否翻转
  changeTurned(){
    this.turned = !this.turned
  }

  //设置Box 各项属性
  setBox(colIndex:number,rowIndex:number,turned:boolean,index:number){
    this.colIndex = colIndex
    this.rowIndex = rowIndex
    this.turned = turned
    this.index = index
  }

}

boxes

这个类提供了初始化、重新加载关卡、重玩本关和重新开始游戏的功能。它通过管理盒子数组来控制游戏的状态,包括盒子的数量和位置。

import {box} from  "../viewmodel/box"

export  class boxes {

  boxes:Array<box> = []
  index :number = 0
  different :number = 0
  constructor() {
  }

  //初始化
  init (startLine:number){
    for (let i = 1; i <= startLine; i++) {
      for (let j = 1; j <= startLine; j++) {
        this.boxes.push(new box(i, j, false, this.index))
        this.index++
      }
    }
  }

  //重新加载
  reload(startLine:number){
    this.index = 0
    //判断需要增加多少长度
    this.different = startLine*startLine - (startLine-1)*(startLine-1)
    for (let i = 0; i < this.different; i++) {
      this.boxes.push(new box(0,0,false,0))
    }
    
    //将数组中的元素属性重新赋值
    for (let i = 1; i <= startLine; i++) {
      for (let j = 1; j <= startLine; j++) {
        this.boxes[this.index].setBox(i,j,false,this.index)
        this.index ++
      }
    }
  }

  //重玩本关
  reLevel(startLine:number){
    this.index = 0
    for (let i = 1; i <= startLine; i++) {
      for (let j = 1; j <= startLine; j++) {
        this.boxes[this.index].setBox(i,j,false,this.index)
        this.index ++
      }
    }
  }

  //重新开始
  reStart (){
    this.index = 0
    this.boxes.splice(1,this.boxes.length)
    this.boxes[0].setBox(1,1,false,0)
  }

}

const boxList = new boxes()
export default boxList as boxes