基于ArkUI框架的舒爾特方格游戲
1. 效果圖直接先上:
B站蠟筆小新介紹游戲規則: https://www.bilibili.com/video/BV1E3411t7cK?spm_id_from=333.999.0.0
動圖主界面游戲界面




2. 項目結構圖


3. 項目開發介紹
舒爾特方格游戲有主界面和游戲界面兩個頁面組成,主界面拆開為title和body兩個自定義組件組成,游戲界面拆開為title,body和footer三個自定義組件組成,utils為隨機生成數字公共類。下面我們來一個一個界面和組件介紹:
3.1 主界面代碼,只是一個程序入口,具體頁面布局在自定義組件實現:
3.1.1 Index代碼
- import { Title } from '../common/home/title'
- import { Body } from '../common/home/body'
- @Entry
- @Component
- struct Index {
- build() {
- Column() {
- // 標題
- Title();
- // 游戲主界面
- Body();
- }
- .alignItems(HorizontalAlign.Center)
- }
- }
3.1.2 Title自定義組件代碼:
- @Component
- export struct Title {
- build() {
- // 主界面標題
- Column() {
- Text("舒爾特方格")
- .fontSize(34).margin({top: 30})
- .fontWeight(FontWeight.Bold)
- Text("SchulteGrid")
- .fontSize(20).margin({top: 3, bottom: 60})
- .fontWeight(FontWeight.Bold)
- }
- .width('100%')
- }
- }
3.1.3 Body自定義組件代碼
- import router from '@system.router'
- @Component
- export struct Body {
- build() {
- Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
- // 3x3, 4x4, 5x5 按鈕布局
- Row() {
- Button("3X3", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .onClick(() => { this.startGame(3) })
- Button("4X4", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .margin({left: 30, right: 30})
- .onClick(() => { this.startGame(4) })
- Button("5X5", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .onClick(() => { this.startGame(5) })
- }.alignItems(VerticalAlign.Center).margin({bottom: 30})
- // 6x6, 7x7 按鈕布局
- Row() {
- Button("6X6", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .onClick(() => { this.startGame(6) })
- Button("7X7", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .margin({left: 30}).onClick(() => { this.startGame(7) })
- }.alignItems(VerticalAlign.Center).margin({bottom: 30})
- // 8x8, 9x9 按鈕布局
- Row() {
- Button("8X8", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .onClick(() => { this.startGame(8) })
- Button("9X9", { type: ButtonType.Circle, stateEffect: true })
- .width(70).height(70).backgroundColor(0x317aff).fontSize(20)
- .margin({left: 30})
- .onClick(() => { this.startGame(9) })
- }.alignItems(VerticalAlign.Center)
- }
- .width('100%')
- .height('100%')
- }
- // 開始游戲
- startGame(idx:number) {
- router.push({
- uri: 'pages/game',
- params: {index: idx}
- })
- }
- }
3.2. 游戲界面代碼,具體頁面布局在自定義組件實現:
3.2.1 Game代碼:
- import router from '@system.router'
- import { Title } from '../common/game/title'
- import { Body } from '../common/game/body'
- import { Footer } from '../common/game/footer'
- import { getRandomData } from '../utils/utils'
- @Entry
- @Component
- struct Game {
- // 接收主界面傳遞過來的陣列數字
- private idx: number = router.getParams().index
- @State index: number = this.idx
- // 調用函數隨機生成相應的字符數字數組
- @State numArray: String[] = getRandomData(this.idx)
- // 與body和footer子組件綁定, 變化時, body和footer子組件也會跟著變化
- @State time: number = 0
- build() {
- Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) {
- // 標題和返回按鈕
- Title()
- // 游戲界面
- Body({idx: $index, numArray: $numArray, time: $time})
- // 狀態框
- Footer({idx: $index, time: $time})
- }
- .width('100%')
- .height('100%')
- }
- }
3.3.2 游戲Title自定義組件代碼:
- import router from '@system.router'
- @Component
- export struct Title {
- build() {
- Row() {
- // 返回游戲主界面
- Image($r("app.media.back"))
- .objectFit(ImageFit.Contain)
- .width(50)
- .height(50)
- .margin({ right: 10 })
- .onClick(()=>{ this.onBack() })
- Text("游戲開始")
- .fontSize(24)
- .fontColor(Color.White)
- .fontWeight(FontWeight.Bold)
- }
- .width('100%')
- .padding({ top: 10, bottom: 10})
- .backgroundColor(0x317aff)
- }
- // 回退
- onBack() {
- router.back();
- }
- }
3.2.3 游戲Body自定義組件代碼:
- @Component
- export struct Body {
- // 與游戲父組件綁定, 記錄當前的陣列數字
- @Link idx: number;
- // 與游戲父組件綁定, 顯示相應的數字按鈕
- @Link numArray: String[];
- // 與游戲父組件綁定, 變化時, 父組件time變量也跟著變化, 同時footer子組件也會跟著變化
- @Link time: number;
- // 根據不同的陣列, 按鈕寬高顯示不同的大小
- private btnSize: number[] = [32, 18, 12, 8, 6, 4, 4]
- // 根據不同的陣列, 按鈕字段顯示不同大小
- private btnFont: number[] = [32, 24, 22, 12, 7, 8, 6]
- // 根據不同的陣列, 顯示不同界面高度
- private gridHeight: number[] = [48, 48, 48, 44, 46, 50, 66]
- // 根據不同的陣列, 顯示不同的行列
- private template: string[] = ['1fr 1fr 1fr', '1fr 1fr 1fr 1fr', '1fr 1fr 1fr 1fr 1fr', '1fr 1fr 1fr 1fr 1fr 1fr', '1fr 1fr 1fr 1fr 1fr 1fr 1fr', '1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr', '1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr']
- // 記錄當前點擊的數字
- private flagNum: number = 1
- // 開始計時
- private startTime: number = new Date().getTime()
- build() {
- Grid() {
- // 循環顯示陣列數字按鈕
- ForEach(this.numArray, (day: string) => {
- GridItem() {
- Button(day, { type: ButtonType.Circle, stateEffect: true })
- .width(this.btnSize[this.idx-3] * this.idx)
- .height(this.btnSize[this.idx-3] * this.idx)
- .backgroundColor(0x317aff).fontSize(this.btnFont[this.idx-3])
- .onClick(() => { this.startGame(Number(day)) })
- }
- }, day => day)
- }
- // 根據相應的陣列數字,顯示相應的列數字
- .columnsTemplate(this.template[this.idx-3])
- // 根據相應的陣列數字,顯示相應的行數字
- .rowsTemplate(this.template[this.idx-3])
- .columnsGap(10)
- .rowsGap(10)
- .width(96+'%')
- .height(this.gridHeight[this.idx-3]+'%')
- }
- // 開始游戲
- startGame(num:number) {
- // 如果當前點擊的數字等于陣列數組長度, 說明點擊到最后一個數字, 彈出挑戰成功, 計算出總共耗時
- if (num == this.numArray.length && this.flagNum == this.numArray.length ) {
- AlertDialog.show({ message: '恭喜您挑戰成功'})
- this.time = (new Date().getTime() - this.startTime) * 1.0 / 1000
- }
- // 如果點擊的數字大于累計的數字,彈出提醒信息
- if (num > this.flagNum) {
- AlertDialog.show({ message: '請點擊小于此數字'})
- // 如果點擊的數字小于累計的數字,彈出提醒信息
- } else if (num < this.flagNum) {
- AlertDialog.show({ message: '當前點擊的數字,已點擊過'})
- // 否則累計數字加1
- } else {
- this.flagNum++
- }
- }
- }
3.2.4 游戲Footer自定義組件代碼:
- @Component
- export struct Footer {
- // 與game父組件綁定, 記錄當前的陣列數字
- @Link idx: number;
- // 與game父組件綁定, 變化時, 父組件time變量也跟著變化, 同時footer子組件也會跟著變化
- @Link time: number;
- build() {
- Stack({ alignContent: Alignment.Bottom }) {
- Row() {
- // 耗時
- Button({ type: ButtonType.Capsule, stateEffect: false }) {
- Row() {
- Image($r('app.media.trophy')).width(20).height(20).margin({ left: 12 })
- Text(this.time + '"').fontSize(16).fontColor(0xffffff).margin({ left: 5, right: 12 })
- }.alignItems(VerticalAlign.Center).width(100)
- }.backgroundColor(0x317aff).opacity(0.7).width(100)
- // 顯示計時中
- Button({ type: ButtonType.Capsule, stateEffect: false }) {
- Row() {
- Image($r('app.media.time')).width(20).height(20).margin({ left: 12 })
- Text('計時中').fontSize(16).fontColor(0xffffff).margin({ left: 5, right: 12 })
- }.alignItems(VerticalAlign.Center).width(100)
- }.backgroundColor(0x317aff).opacity(0.7).width(100)
- .margin({left: 20, right: 20})
- // 幫助功能
- Button({ type: ButtonType.Capsule, stateEffect: true }) {
- Row() {
- Image($r('app.media.help')).width(20).height(20).margin({ left: 12 })
- Text('幫助').fontSize(16).fontColor(0xffffff).margin({ left: 5, right: 12 })
- }.alignItems(VerticalAlign.Center).width(100)
- }.backgroundColor(0x317aff).width(100)
- .onClick(() => { this.showHelp() })
- }
- }.width('100%').height(100).margin({ top: 5, bottom: 10 })
- }
- // 提示游戲幫助
- showHelp() {
- AlertDialog.show({ message: '以最快速度從 1 選到 ' + (this.idx*this.idx) })
- }
- }
3.3. Utils公共函數實現:
- /**
- * 隨機生成1-count參數的整數
- * @param idx
- */
- export function getRandomData(idx:number): Array<String> {
- // 生成count個數字
- let count:number = idx * idx;
- // 存儲生成的字符數字
- let result:Array<String> = [];
- do {
- // 隨機生成一個指定范圍的數字
- let num = Math.floor(Math.random() * count + 1);
- // 如果數字不在數組里, 存儲到數組
- if (-1 == result.indexOf(num+'')) {
- result.push(num+'');
- }
- // 如果隨機生成的數字存儲到數組的長度等于陣列數, 跳出死循環
- if (count == result.length) {
- break;
- }
- }while(true)
- // 返回數組
- return result;
- };
**總結:**看到主界面和游戲界面代碼,是不是很簡潔,聲明式開發范式之美,那你還等什么?跟上步伐開始聲明式開發吧!!!