鸿湖万联“竞”开发板体验:基于eTSUI框架的2048小游戏
前言 2048是一款比较流行的数字游戏,本demo基于ets ui框架,在grid组件基础上实现。
过程
打开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 - //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应用开发。 |