前言
2048是一款比较流行的数字游戏,本demo基于ets ui框架,在grid组件基础上实现。
过程
从以下地址下载代码:https://gitee.com/qinyunti/knowledge_demo_smart_home.git
打开DevEco Studio 3.0.0.993
打开工程:
更新:
点击Run,有如下提示按图配置即可:
代码分析
程序入口:src/main/ets/MainAbility/app.ets
import Logger from '../MainAbility/model/Logger'
const TAG: string = `[App]`
export default {
onCreate() {
Logger.info(TAG, `Application onCreate`)
},
onDestroy() {
Logger.info(TAG, `Application onDestroy`)
},
}
(左右移动查看全部内容)
逻辑代码位于:src/main/ets/MainAbility/model/GameRule.ts
//gameStatus
enum GameStatus {
BEFORE = -1,
RUNNING = 1,
OVER = 0
}
export class GameRule {
private row: number = 4
private column: number = 4
private index(i: number, j: number) {
return i * this.row + j
}
dataNumbers: number[]
status: number = GameStatus.BEFORE
score: number = 0
constructor(dataNumbers: number[]) {
this.dataNumbers = dataNumbers
}
//random
randomNum() {
do {
let a = Math.floor(Math.random() * this.dataNumbers.length)
if (this.dataNumbers[a] === 0) {
let num = Math.random() > 0.3 ? 2 : 4
this.dataNumbers[a] = num
break
}
} while (this.dataNumbers.some((val) => {
return val === 0
}))
}
//init
init() {
this.dataNumbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
this.status = GameStatus.RUNNING
this.score = 0
this.randomNum()
this.randomNum()
}
//move
move(direction: string) {
let before = String(this.dataNumbers)
let length = (direction === 'left' || direction === 'right') ? this.row : this.column
for (let a = 0;a < length; a++) {
this.moveInRow(direction, a)
}
let after = String(this.dataNumbers)
if (before !== after) {
this.randomNum()
if (this.isGameOver()) {
this.status = GameStatus.OVER
}
}
}
//prepare to move
moveInRow(direction: string, a: number) {
if (direction === 'left') {
for (let b = 0;b < this.column - 1; b++) {
let next = this.moveNext(a, b, direction)
b = this.dataChange(a, b, next, direction).b
if (next === -1) break
}
} else if (direction === 'right') {
for (let b = this.column - 1;b > 0; b--) {
let next = this.moveNext(a, b, direction)
b = this.dataChange(a, b, next, direction).b
if (next === -1) break
}
} else if (direction === 'up') {
for (let b = 0;b < this.row - 1; b++) {
let next = this.moveNext(b, a, direction)
b = this.dataChange(b, a, next, direction).a
if (next === -1) break
}
} else if (direction === 'down') {
for (let b = this.row - 1;b > 0; b--) {
let next = this.moveNext(b, a, direction)
b = this.dataChange(b, a, next, direction).a
if (next === -1) break
}
}
}
//new number moveStatus
moveNext(a: number, b: number, direction: string) {
if (direction === 'left') {
for (let i = b + 1;i < this.column; i++) {
if (this.dataNumbers[this.index(a, i)] !== 0) {
return i
}
}
} else if (direction === 'right') {
for (let i = b - 1;i >= 0; i--) {
if (this.dataNumbers[this.index(a, i)] !== 0) {
return i
}
}
} else if (direction === 'up') {
for (let i = a + 1;i < 4; i++) {
if (this.dataNumbers[this.index(i, b)] !== 0) {
return i
}
}
} else if (direction === 'down') {
for (let i = a - 1;i >= 0; i--) {
if (this.dataNumbers[this.index(i, b)] !== 0) {
return i
}
}
}
return -1
}
//get gameStatus
isGameOver() {
for (let a = 0;a < this.row; a++) {
for (let b = 0;b < this.column; b++) {
let tempA = this.index(a, b)
if (this.dataNumbers[tempA] === 0) {
return false
}
if (a < this.row - 1) {
if (this.dataNumbers[tempA] === this.dataNumbers[this.index(a + 1, b)]) {
return false
}
}
if (b < this.column - 1) {
if (this.dataNumbers[tempA] === this.dataNumbers[tempA+1]) {
return false
}
}
}
}
return true
}
//move and merge
dataChange(a: number, b: number, next: number, direction: string) {
let tempA = this.index(a, b)
let tempB = 0
if (direction === 'left' || direction === 'right') {
tempB = this.index(a, next)
} else {
tempB = this.index(next, b)
}
if (next !== -1) {
if (this.dataNumbers[tempA] === 0) {
this.dataNumbers[tempA] = this.dataNumbers[tempB]
this.dataNumbers[tempB] = 0
direction === 'left' && b--
direction === 'right' && b++
direction === 'up' && a--
direction === 'down' && a++
} else if (this.dataNumbers[tempA] === this.dataNumbers[tempB]) {
this.dataNumbers[tempA] *= 2
this.score += this.dataNumbers[tempA]
this.dataNumbers[tempB] = 0
}
}
return {
a, b
}
}
(左右移动查看全部内容)
绘图位于:src/main/ets/MainAbility/pages/Game2048.ets
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdded(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChanged(index)
})
}
}
class MyDataSource extends BasicDataSource {
public dataArray: string[] = []
constructor(ele) {
super()
for (var index = 0;index < ele.length; index++) {
this.dataArray.push(ele[index])
}
}
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0)
this.notifyDataAdd(index)
}
}
enum GameStatus {
BEFORE = -1,
RUNNING = 1,
OVER = 0
}
import display from '@ohos.display'
import Logger from '../model/Logger'
import dataStorage from '@ohos.data.storage'
import { GameRule } from '../model/GameRule'
import featureAbility from '@ohos.ability.featureAbility'
const TAG = '[Game2048]'
@Entry
@Component
struct Game2048 {
@State dataNumbers: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
private gameRule: GameRule = new GameRule(this.dataNumbers)
@State maxScore: number = 123456
@State curScore: number = 0
@State @Watch("onGameOver") gameStatus: number = GameStatus.BEFORE
@State textColor: string[] = ['#f0fff0', '#eee3da', '#ede0c8', '#f2b179', '#f59563', '#f67c5f', '#f65e3b', '#edcf72', '#edcc61', '#9c0', '#33b5e5', '#09c', '#a6c', '#93c']
dialogController: CustomDialogController = new CustomDialogController({
builder: ScorePannel({
curScore: this.curScore,
maxScore: this.maxScore,
gameStart: this.gameStart.bind(this)
}),
autoCancel: false
})
@State screenSize: {
x: number,
y: number
} = { x: px2vp(1080), y: px2vp(0) }
//gameStatus listener
onGameOver() {
if (this.gameStatus === GameStatus.OVER) {
this.curScore = this.gameRule.score
this.dialogController.open()
}
}
//restart game
gameStart() {
this.gameRule.init()
this.dataNumbers = this.gameRule.dataNumbers
this.gameStatus = GameStatus.RUNNING
this.handleLocalData('put')
}
//dataView
dataView() {
this.dataNumbers = this.gameRule.dataNumbers
this.gameStatus = this.gameRule.status
this.curScore = this.gameRule.score
}
aboutToAppear() {
display.getDefaultDisplay((err, data) => {
if (data.height > data.width) {
this.screenSize = { x: px2vp(data.width), y: px2vp(data.height) }
} else {
this.screenSize = { x: px2vp(750), y: px2vp(data.width) }
}
Logger.info(TAG, `宽 ${this.screenSize.x}`)
Logger.info(TAG, `高 ${this.screenSize.y}`)
})
this.handleLocalData('has')
}
//handle local data
handleLocalData(method: string) {
let context = featureAbility.getContext()
context.getFilesDir((err, path) => {
let storage = dataStorage.getStorageSync(path + '/mystore')
if (method === 'put') {
storage.putSync('gameData', JSON.stringify(this.dataNumbers))
let score: string = this.gameRule.score.toString()
storage.putSync('score', score)
storage.putSync('gameStatus', this.gameRule.status.toString())
storage.flushSync()
} else if (method === 'has') {
if (storage.hasSync('gameData')) {
this.gameRule.score = this.curScore = Number(storage.getSync('score', 'string'))
this.gameStatus = this.gameRule.status = Number(storage.getSync('gameStatus', 'string'))
this.dataNumbers = this.gameRule.dataNumbers = JSON.parse(storage.getSync('gameData', 'string').toString())
}
}
})
}
build() {
Column() {
Column() {
Row() {
Image($r('app.media.logo2048'))
.width(this.screenSize.x * 0.25)
.height(this.screenSize.x * 0.25)
Column() {
Text('Score')
.fontSize('30px')
.fontColor('#efe1d3')
.fontWeight(FontWeight.Bolder)
Text(`${this.gameRule.score}`)
.fontSize('30px')
.fontColor('#fcf8f5')
.fontWeight(FontWeight.Bolder)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.backgroundColor('#bbada0')
.width(this.screenSize.x * 0.25)
.height(this.screenSize.x * 0.25)
.borderRadius(15)
Column() {
Text('Max')
.fontSize('50px')
.fontColor('#efe1d3')
.fontWeight(FontWeight.Bolder)
Text(`${this.maxScore}`)
.fontSize('30px')
.fontColor('#fcf8f5')
.fontWeight(FontWeight.Bolder)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.backgroundColor('#bbada0')
.width(this.screenSize.x * 0.25)
.height(this.screenSize.x * 0.25)
.borderRadius(15)
}
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceAround)
.margin({ bottom: 20 })
.width(this.screenSize.x)
Grid() {
LazyForEach(new MyDataSource(this.dataNumbers), (item) => {
GridItem() {
Text(`${item === 0 ? '' : item}`)
.fontSize('85px')
.fontColor(item <= 4 ? '#000' : '#fcf8f5')
.fontWeight(FontWeight.Bolder)
.backgroundColor('#f0fff0')
.width('100%')
.height('100%')
.textAlign(TextAlign.Center)
.borderRadius(10)
.backgroundColor(this.textColor[(Math.log(item) / Math.log(2))])
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width(this.screenSize.x)
.padding(10)
.backgroundColor('rgba(80,69,46,0.26)')
.height(this.screenSize.x)
.borderRadius(10)
.gesture(GestureGroup(GestureMode.Exclusive,
PanGesture({ direction: PanDirection.Left }).onActionEnd(() => {
this.gameRule.status === 1 && this.gameRule.move('left')
this.dataView()
this.handleLocalData('put')
}),
PanGesture({ direction: PanDirection.Right }).onActionEnd(() => {
this.gameRule.status === 1 && this.gameRule.move('right')
this.dataView()
this.handleLocalData('put')
}),
PanGesture({ direction: PanDirection.Up }).onActionEnd(() => {
this.gameRule.status === 1 && this.gameRule.move('up')
this.dataView()
this.handleLocalData('put')
}),
PanGesture({ direction: PanDirection.Down }).onActionEnd(() => {
this.gameRule.status === 1 && this.gameRule.move('down')
this.dataView()
this.handleLocalData('put')
})
))
if (this.gameStatus === -1) {
Button('Start', { type: ButtonType.Normal })
.borderRadius(5)
.margin({ top: 50 })
.width(200)
.key('startGame')
.onClick(() => {
this.gameStart()
})
}
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Start)
.width('100%')
.height('100%')
.backgroundImage($r('app.media.gridBackground'))
.backgroundImageSize(ImageSize.Cover)
}
}
@CustomDialog
struct ScorePannel {
controller: CustomDialogController
gameStart: () => void
curScore: number
maxScore: number
build() {
Column() {
Text('Game Over')
.fontSize(30)
.fontWeight(FontWeight.Medium)
.margin({ top: 10 })
Text('Score')
.fontColor('#C8A584')
.fontSize(20)
.margin({ top: 10 })
Text(`${this.curScore}`)
.fontColor('#5D5D5D')
.fontSize(40)
.margin({ top: 10 })
Text(`maxScore:${this.maxScore}`)
.fontSize(20)
.width('90%')
.borderRadius(20)
.margin({ top: 10 })
.height(40)
.textAlign(TextAlign.Center)
Row() {
Button('Reset', { type: ButtonType.Normal })
.borderRadius(5)
.margin({ top: 10 })
.width(200)
.onClick(() => {
this.gameStart()
this.controller.close()
})
}.justifyContent(FlexAlign.SpaceAround)
.margin({ top: 10, bottom: 10 })
}
.backgroundColor('#f0f0f0')
.borderRadius(25)
}
}
(左右移动查看全部内容)
总结
基于eTS UI框架能够,基于各种组件进行快速的UI应用开发。
更多热点文章阅读
提示:本文由电子发烧友社区发布,转载请注明以上来源。如需社区合作及入群交流,请添加微信EEFans0806,或者发邮箱liuyong@huaqiu.com。
原文标题:鸿湖万联“竞”开发板体验:基于eTSUI框架的2048小游戏
文章出处:【微信公众号:电子发烧友开源社区】欢迎添加关注!文章转载请注明出处。
全部0条评论
快来发表一下你的评论吧 !