一.前言
本学期通过对鸿蒙开发的学习,做一个简单的鸿蒙小游戏作为此次学习的大作业。很常见的小游戏扫雷,由于时间有限,只实现了扫雷的玩法,长按事件,不同困难程度,游戏的终止。
二.环境
这个小游戏是基于华为鸿蒙操作系统(HarmonyOS)和鸿蒙应用开发工具(Huawei DevEco Studio)实现的。语言用的是ArkTS。
三.实现思路
1 .用二维数组来保存数据,-1表示雷
2. -1随机存储在二维数组中,先以简单难度5 * 5的二维数组展示,雷的个数为5个,根据难度的选择,二维数组的大小会随之改变,雷的个数也会改变。
3.算法实现雷周围的元素数字为雷的个数
4.构造二维数组的button对应创建好的二维数组
5 .点击0的时候将与0相邻的数字会被显示出来以及所有的0会变成空白的
6 .点击非0数字不包含-1只展示当前数字
7.长按可以选择旗帜或者问号
8.点击雷结束游戏,若成功则会显示成功
四.代码解析
1.创建一个新的项目
打开DevEco Studio,选择“Create HarmonyOS Project”,然后选择“Empty Ability”模板,点击“Next”。
2.创建项目构建
其中MincBean,MineClearData为实体类以及初始化数据的类,EntryAbility是程序入口类,index是页面。
3.创建MincBean实体类
在src->main->ets->common 下面创建bean文件夹,在文件夹下创建MincBean.ets文件
export class MineBean {
row: number;
col: number;
mineCounts: number;
time: number;
rowLayout: string;
colLayout: string;
gridHeight: string;
constructor(row: number, col: number, mineCounts: number, time: number, rowLayout: string, colLayout: string, gridHeight: string) {
this.row = row;
this.col = col;
this.mineCounts = mineCounts;
this.time = time;
this.rowLayout = rowLayout;
this.colLayout = colLayout;
this.gridHeight = gridHeight;
}
}
4.创建程序入口类
在src->main->ets下面创建entryability文件夹,在文件夹下创建EntryAbility.ts文件
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(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;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
5.创建二维数组
创建二维数组,生成并记录雷的位置,以及生成雷周边的数字,算法实现雷周围的元素数字为雷的个数
init() {
// 初始化二维数组
for (let i = 0; i < this.row; i++) {
this.data[i] = new Array(this.col);
this.data[i].fill(0);
}
// 记录雷的位置
let mineArr = new Array();
// 生成随机数表示雷的位置
for (var i = 0; i < this.mineCount; i++) {
let tempRow = this.getRandom(0, this.row - 1);
let tempCol = this.getRandom(0, this.col - 1);
if (this.data[tempRow][tempCol] == -1) {
i--;
continue;
}
mineArr.push([tempRow, tempCol]);
this.data[tempRow][tempCol] = -1;
}
this.clicked = this.col * this.row - this.mineCount;
console.log(JSON.stringify(this.data))
// 生成雷周围的数字
for (let i = 0; i < mineArr.length; i++) {
let temp = mineArr[i];
let tempRow = temp[0];
let tempCol = temp[1];
let arr = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]];
for (let i = 0; i < arr.length; i++) {
let nx = tempRow + arr[i][0];
let ny = tempCol + arr[i][1];
if (nx < 0 || nx >= this.row || ny < 0 || ny >= this.col || this.data[nx][ny] == -1) {
continue;
}
this.data[nx][ny]++;
}
}
console.log(JSON.stringify(this.data))
return this.data;
}
getRandom(minValue: number, maxValue: number): number {
let temp = maxValue - minValue + 1;
return Math.floor(Math.random() * temp) + minValue
}
getShowContent() {
this.init();
// 初始化二维数组
for (let i = 0; i < this.row; i++) {
this.showContent[i] = new Array(this.col);
this.showContent[i].fill(undefined);
}
}
判断点击的格子中的数字是否为0,为0的时候将与0相邻的数字会被显示出来以及所有的0会变成空白的,不为0则显示点击的数字
changeButtonContent(num: any): any {
if (num == 0) {
return undefined;
} else if (num === undefined) {
return undefined;
} else if (num == -1) {
return $r('app.media.boom')
}
return JSON.stringify(num);
}
getZero(i: number, j: number) {
let arr = [[1, 0], [-1, 0], [0, -1], [0, 1]]
let list = [];
list.push([i, j]);
while(list.length != 0) {
let temp = list.shift();
this.showContent[temp[0]][temp[1]] = 0;
for (let i = 0; i < arr.length; i++) {
let nx = temp[0] + arr[i][0];
let ny = temp[1] + arr[i][1];
if (nx < 0 || nx >= this.row || ny < 0 || ny >= this.col) {
continue;
}
if (this.data[nx][ny] == 0 && this.showContent[nx][ny] != 0) {
list.push([nx, ny]);
}
if ((this.data[nx][ny] !== 0 || this.data[nx][ny] !== -1) && this.showContent[nx][ny] == undefined) {
this.showContent[nx][ny] = this.data[nx][ny];
}
}
console.log(JSON.stringify(list.length + ' ' + list.toString()));
}
}
实现结果如下图
6.长按事件以及添加倒计时
长按动作的触发
.gesture(
LongPressGesture({ repeat: false })
// 由于repeat设置为true,长按动作存在时会连续触发,触发间隔为duration(默认值500ms)
.onAction((event: GestureEvent) => {
if (this.showContent[i][j] == undefined || this.showContent[i][j] == 'mark' || this.showContent[i][j] == 'flag') {
this.positionX = JSON.stringify(event.fingerList[0].globalX - 30);
this.positionY = JSON.stringify(event.fingerList[0].globalY - 30);
console.log(`${this.positionX} ${this.positionY}`)
this.isShowPick = true;
this.dataI = i;
this.dataJ = j;
}
console.log(JSON.stringify(this.positionX));
console.log(JSON.stringify(event.target.area.globalPosition.x));
console.log(JSON.stringify(event))
console.log('chang an');
})
// 长按动作一结束触发
.onActionEnd(() => {
this.timeOut();
console.log('chang an');
})
长按后可选择旗帜或者问号
GridItem() {
Button( {
type: ButtonType.Normal
}) {
if (this.showContent[i][j] == -1) {
Image($r('app.media.boom'))
.objectFit(ImageFit.Fill)
} else if (this.showContent[i][j] == 0) {
} else if (this.showContent[i][j] == this.fruits[1]) {
Image($r('app.media.flag'))
.objectFit(ImageFit.Fill)
} else if (this.showContent[i][j] == this.fruits[2]) {
Image($r('app.media.mark'))
.objectFit(ImageFit.Fill)
} else {
Text(JSON.stringify(num)).fontColor(Color.Black).fontSize('24fp')
}
}
.width('100%')
.height('100%')
.backgroundColor(this.changeButtonColor(i, j))
.onClick(() => {
if (this.showContent[i][j] !== undefined && this.showContent[i][j] != 'mark' && this.showContent[i][j] != 'flag') {
return
}
if (this.data[i][j] == 0) {
this.getZero(i, j)
}
this.showContent[i][j] = this.data[i][j];
if (this.showContent[i][j] == -1) {
this.isShow = !this.isShow;
this.alertDialogComponent('failed');
return;
}
let count = 0;
for (let i = 0; i < this.showContent.length; i++) {
for (let j = 0; j < this.showContent[i].length; j++) {
if (this.showContent[i][j] != undefined && this.showContent[i][j] != this.fruits[1]
&& this.showContent[i][j] != this.fruits[2]) {
count++;
}
}
}
添加倒计时
/**
* 控制长按弹出框3秒后消失
*/
timeOut() {
var timeoutID = setTimeout(() => {
this.isShowPick = false;
}, 3000);
}
/**
* 倒计时,当时间到0时失败
*/
实现结果如下图
7.困难程度选择
在src->main->ets 下面创建view文件夹,在文件夹下创建DifficultSelectionDialogView.ets文件
export struct DifficultySelectionDialogView {
@Link difficulty: number;
private level: string[];
controller: CustomDialogController
cancel: () => void
confirm: () => void
build() {
Column() {
Text('难度选择').fontSize(20).margin({ top: 10, bottom: 10 })
Row() {
Slider({
value: this.difficulty,
min: 0,
max: 3,
style: SliderStyle.InSet
})
.width('80%')
.blockColor('#df5a9f30')
.trackColor('#ADD8E6')
.selectedColor('#a96b7fc1')
.showTips(true)
.onChange((value: number, mode: SliderChangeMode) => {
this.difficulty = value
console.info('value:' + value + 'mode:' + mode.toString())
})
Text(this.level[this.difficulty])
.fontSize('12fp')
}
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('关闭')
.onClick(() => {
this.controller.close()
})
.backgroundColor(Color.Blue)
.fontColor(Color.Black)
}.margin({ bottom: 10 })
}
}
}
在src->main->ets 下面创建pages文件夹,在文件夹下创建index.ets文件
本段代码主要是设置困难选择页面的实现
build() {
Row() {
Stack() {
Column() {
Row({space: 5}) {
Text(`级别: ${this.level[this.difficulty]}`)
.fontSize('22vp')
.fontWeight(FontWeight.Bolder)
Button('开始')
.onClick(() => {
this.countDown();
})
Button('难度选择')
.fontSize(24)
.fontWeight(FontWeight.Medium)
.onClick(() => {
this.dialogController.open();
})
}
.margin('10vp')
Text('倒计时:' + JSON.stringify(this.time))
.fontSize(35)
.fontWeight(FontWeight.Medium)
if (this.isShow) {
this.gridItemContent();
} else {
this.gridItemContent();
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
if (this.isShowPick) {
Column() {
Image($r('app.media.flag'))
.width('70vp')
.height('75vp')
.objectFit(ImageFit.Fill)
.onClick(() => {
this.showContent[this.dataI][this.dataJ] = 'flag';
this.isShowPick = false;
this.isShow = !this.isShow;
})
Image($r('app.media.mark'))
.width('70vp')
.height('75vp')
.objectFit(ImageFit.Fill)
.onClick(() => {
this.showContent[this.dataI][this.dataJ] = 'mark';
this.isShowPick = false;
this.isShow = !this.isShow;
})
}
.width('70vp')
.height('150vp')
.position({x: this.positionX, y: this.positionY})
}
}
.width('100%')
.height('100%')
}
.height('100%')
.alignItems(VerticalAlign.Top)
}
实现结果如下
8.游戏终止
alertDialogComponent(message: string) {
clearInterval(this.countDownTime);
AlertDialog.show(
{
title: '',
message: message,
autoCancel: true,
alignment: DialogAlignment.Center,
offset: { dx: 0, dy: -20 },
gridCount: 3,
confirm: {
value: '重新开始',
action: () => {
this.getShowContent();
this.isShow = !this.isShow;
this.time = this.allMineData[this.difficulty].time;
this.countDown();
}
},
cancel: () => {
this.getShowContent();
this.isShow = !this.isShow;
console.info('Closed callbacks')
}
}
)
}//终止
实现结果如下
五.总结
本文是使用ArkTS语言编写的扫雷小游戏鸿蒙开发,主要参看网上相关的视频以及相关资料,本文尚有不足,比如这里的终止游戏界面,只实现了失败或者成功后继续开始游戏,并不能不继续开始游戏。望谅解,请指正。
文本到此完毕,有疑问的请在评论区留言交流,谢谢阅读。