原子化服務(wù)卡片還原經(jīng)典小游戲—數(shù)字華容道
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
服務(wù)卡片也能玩游戲了,今天就來(lái)還原經(jīng)典小游戲——數(shù)字華容道。詳細(xì)講述了數(shù)字華容道在服務(wù)卡片上的開(kāi)發(fā)思路,內(nèi)含詳細(xì)注釋。趕緊動(dòng)手來(lái)開(kāi)發(fā)一張服務(wù)卡片,休閑時(shí)刻來(lái)一局!
概述
老規(guī)矩,先上效果圖。
分別調(diào)出兩種類(lèi)型的服務(wù)卡片,效果如下:

點(diǎn)擊開(kāi)始按鈕,游戲頁(yè)面服務(wù)卡片的文本會(huì)隨機(jī)打亂,開(kāi)始進(jìn)行計(jì)時(shí)。點(diǎn)擊上下左右四個(gè)按鈕,文本會(huì)向?qū)?yīng)的方向移動(dòng)一格。

當(dāng)游戲結(jié)束時(shí)或者游戲頁(yè)面服務(wù)卡片被刪除時(shí),控制頁(yè)面服務(wù)卡片會(huì)停止計(jì)時(shí)。

正文
一、創(chuàng)建一個(gè)空白的工程
1. 安裝和配置DevEco Studio 2.1 Release
DevEco Studio 2.1 Release下載、DevEco Studio 2.1 Release安裝
2. 創(chuàng)建一個(gè)Empty JS Phone應(yīng)用
DevEco Studio下載安裝成功后,打開(kāi)DevEco Studio,點(diǎn)擊左上角的File,點(diǎn)擊New,再選擇New Project,選擇Empty Ability(JS)選項(xiàng),點(diǎn)擊Next按鈕。

將文件命名為MyCardGame(文件名不能出現(xiàn)中文或者特殊字符,否則將無(wú)法成功創(chuàng)建項(xiàng)目文件),選擇保存路徑,選擇API5,設(shè)備勾選Phone,最后點(diǎn)擊Finish按鈕。

3. 創(chuàng)建兩個(gè)空白的服務(wù)卡片
在MyCarGame->entry->src->main->js中點(diǎn)擊右鍵,在彈出的菜單項(xiàng)中選擇New,再在彈出的子菜單項(xiàng)中選擇Service Widget。

選擇Image With Information。

1)在配置列表中,Service Widget Name中命名為ControlPage,Description中命名為T(mén)his is a control widget,Type選擇JS,JS Component Name中命名為ControlPage,Support Dimensions勾選2 * 4。
2)再重復(fù)上述操作創(chuàng)建第二個(gè)新的服務(wù)卡片,在配置列表中,Service Widget Name中命名為GamePage,Description中命名為T(mén)his is a game widget,Type選擇JS,JS Component Name中命名為GamePage,Support Dimensions勾選2 * 4。


4. 設(shè)置首卡片尺寸
在MyCarGame->entry->src->main->config.json中,將服務(wù)卡片ControlPage中defaultDimension的屬性值修改為2 * 4,將服務(wù)卡片GamePage中defaultDimension的屬性值修改為2 * 4。
- "forms": [
- {
- "jsComponentName": "ControlPage",
- "isDefault": true,
- "scheduledUpdateTime": "10:30",
- "defaultDimension": "2*4",
- ......
- },
- {
- "jsComponentName": "GamePage",
- "isDefault": false,
- "scheduledUpdateTime": "10:30",
- "defaultDimension": "2*4",
- ......
- }
- ]
二、完善兩個(gè)服務(wù)卡片頁(yè)面布局
1. 完善控制服務(wù)卡片頁(yè)面布局
在MyCarGame->entry->src->main->js->ControlPage->pages->index->index.hml中將原有的容器代碼全部刪除。
添加一個(gè)基礎(chǔ)容器div,增加類(lèi)選擇器為class。添加一個(gè)基礎(chǔ)容器div,類(lèi)選擇器為class_text,內(nèi)部添加兩個(gè)text組件,類(lèi)選擇器均為text,第一個(gè)text組件的文本為“時(shí)間:”,第二個(gè)text組件的文本為{{time}},即動(dòng)態(tài)綁定變量time,以顯示游戲的時(shí)間。
最后依次添加五個(gè)按鈕,按鈕上的文本分別為“上”、“左”、“右”、“開(kāi)始”和“下”,類(lèi)選擇器分別為btn_up、btn_left、btn_right、btn_start和btn_down,分別添加一個(gè)點(diǎn)擊事件click_up、click_left、click_right、start和click_down。其中“左”和“右”兩個(gè)按鈕置于類(lèi)選擇器為class_button的基礎(chǔ)容器div中,“開(kāi)始”和“下”兩個(gè)按鈕置于類(lèi)選擇器為class_button的基礎(chǔ)容器div中。
- <div class="class">
- <div class="class_text">
- <text class="text">時(shí)間:</text>
- <text class="text">{{time}}</text>
- </div>
- <button class="btn_up" onclick="click_up">上</button>
- <div class="class_button">
- <button class="btn_left" onclick="click_left">左</button>
- <button class="btn_right" onclick="click_right">右</button>
- </div>
- <div class="class_button">
- <button class="btn_start" onclick="btn_start">開(kāi)始</button>
- <button class="btn_down" onclick="click_down">下</button>
- </div>
- </div>
在MyCarGame->entry->src->main->js->ControlPage->pages->index->index.css中將原有的類(lèi)選擇器全部刪除。
各個(gè)類(lèi)選擇器配置如下:
- .class{
- width: 100%;
- height: 100%;
- flex-direction: column;
- align-items: flex-start;
- background-color: antiquewhite;
- }
- .class_text{
- flex-direction: row;
- }
- .text{
- font-size: 30px;
- margin-top:3%;
- text-align: center;
- }
- .btn_up{
- width: 20%;
- height: 20%;
- margin-top: 3%;
- margin-left: 40%;
- align-items: center;
- }
- .class_button{
- flex-direction: row;
- }
- .btn_left{
- width: 20%;
- height: 20%;
- margin-top: 3%;
- margin-left: 20%;
- align-items: center;
- }
- .btn_right{
- width: 20%;
- height: 20%;
- margin-top: 3%;
- margin-left: 20%;
- align-items: center;
- }
- .btn_start{
- width: 20%;
- height: 20%;
- margin-top: 3%;
- margin-left: 10%;
- align-items: center;
- }
- .btn_down{
- width: 20%;
- height: 20%;
- margin-top: 3%;
- margin-left: 10%;
- align-items: center;
- }
在MyCarGame->entry->src->main->js->ControlPage->pages->index->index.json中將data原有的數(shù)據(jù)全部刪除,在data中將time初始化為“00:00:00”。
添加actions數(shù)組,actions數(shù)組是所有事件的集合,數(shù)組的每個(gè)屬性包括每個(gè)事件的名稱,名稱里面又含有事件的類(lèi)型“action”和攜帶的參數(shù)“params”?,F(xiàn)在為上述五個(gè)點(diǎn)擊事件分別配置屬性:
- {
- "data": {
- "time": "00:00:00"
- },
- "actions": {
- "click_up": {
- "action": "message",
- "params": {
- "message": "click_up"
- }
- },
- "click_left": {
- "action": "message",
- "params": {
- "message": "click_left"
- }
- },
- "click_right": {
- "action": "message",
- "params": {
- "message": "click_right"
- }
- },
- "click_down": {
- "action": "message",
- "params": {
- "message": "click_down"
- }
- },
- "click_start": {
- "action": "message",
- "params": {
- "message": "click_start"
- }
- }
- }
- }
2. 完善控制服務(wù)卡片頁(yè)面布局
在MyCarGame->entry->src->main->js->GamePage->pages->index->index.hml中將原有的容器代碼全部刪除。
添加一個(gè)基礎(chǔ)容器div,增加類(lèi)選擇器為class。依次添加16個(gè)text組件,類(lèi)選擇器均為text,文本分別為{{text1}}、{{text2}}、……、{{text16}},即分別動(dòng)態(tài)綁定一個(gè)變量。其中,每四個(gè)text組件置于類(lèi)選擇器為class_text的基礎(chǔ)容器div中。
- <div class="class">
- <div class="class_text">
- <text class="text">{{text1}}</text>
- <text class="text">{{text2}}</text>
- <text class="text">{{text3}}</text>
- <text class="text">{{text4}}</text>
- </div>
- <div class="class_text">
- <text class="text">{{text5}}</text>
- <text class="text">{{text6}}</text>
- <text class="text">{{text7}}</text>
- <text class="text">{{text8}}</text>
- </div>
- <div class="class_text">
- <text class="text">{{text9}}</text>
- <text class="text">{{text10}}</text>
- <text class="text">{{text11}}</text>
- <text class="text">{{text12}}</text>
- </div>
- <div class="class_text">
- <text class="text">{{text13}}</text>
- <text class="text">{{text14}}</text>
- <text class="text">{{text15}}</text>
- <text class="text">{{text16}}</text>
- </div>
- </div>
在MyCarGame->entry->src->main->js->GamePage->pages->index->index.css中將原有的類(lèi)選擇器全部刪除。
各個(gè)類(lèi)選擇器配置如下:
- .class{
- width: 100%;
- height: 100%;
- flex-direction: column;
- align-items: flex-start;
- }
- .class_text{
- flex-direction: row;
- }
- .text{
- width: 25%;
- height: 25%;
- text-color: black;
- font-size: 30px;
- background-color: aquamarine;
- text-align: center;
- }
在MyCarGame->entry->src->main->js->GamePage->pages->index->index.json中將data原有的數(shù)據(jù)全部刪除,在data中將text1、text2、……、text16初始化為“1”、“2”、……、“15”、“”。
- {
- "data": {
- "text1": "1",
- "text2": "2",
- "text3": "3",
- "text4": "4",
- "text5": "5",
- "text6": "6",
- "text7": "7",
- "text8": "8",
- "text9": "9",
- "text10": "10",
- "text11": "11",
- "text12": "12",
- "text13": "13",
- "text14": "14",
- "text15": "15",
- "text16": ""
- }
- }
三、響應(yīng)點(diǎn)擊事件
在MyCarGame->entry->src->main->java->com->test->mycargame->MainAbility.java中修改以下代碼。
1. 修改onCreateForm()方法
onCreateForm()方法在兩種情況被調(diào)用。第一種情況是上滑呼出卡片的時(shí)候,這時(shí)候上滑卡片的動(dòng)作就會(huì)調(diào)用一次onCreateForm()方法生成一張卡片;
第二種情形是長(zhǎng)按應(yīng)用,然后點(diǎn)擊"服務(wù)卡片",此時(shí)會(huì)顯示該應(yīng)用的所有卡片,每一張卡片都會(huì)回調(diào)一次onCreateForm()方法并生成一張對(duì)應(yīng)的卡片。當(dāng)選擇了其中一張卡片添加到桌面后,其他卡片回調(diào)onDeleteForm()方法來(lái)刪除該卡片。
為了保證控制頁(yè)面服務(wù)卡片和游戲頁(yè)面服務(wù)卡片只對(duì)對(duì)應(yīng)類(lèi)型卡片的第一張起作用,分別定義兩個(gè)long類(lèi)型的全局變量ControlPanelFormId和GamePanelFormId,并初始化為0。在onCreateForm()方法中,根據(jù)服務(wù)卡片的名字formName來(lái)判斷是哪種類(lèi)型的服務(wù)卡片,如果對(duì)應(yīng)的變量為0,即表示該種類(lèi)型的卡片還沒(méi)有生成,則使該卡片的id賦值給對(duì)應(yīng)的變量。
- public static long ControlPanelFormId = 0;
- public static long GamePanelFormId = 0;
- protected ProviderFormInfo onCreateForm(Intent intent) {
- ......
- if(formName.equals("ControlPage")) {
- if(ControlPanelFormId == 0) {
- ControlPanelFormId = formId;
- }
- }
- if(formName.equals("GamePage")) {
- if(GamePanelFormId == 0) {
- GamePanelFormId = formId;
- }
- }
- return formController.bindFormData();
- }
2. 修改onDeleteForm()方法
為了使卡片刪除后再生成卡片時(shí),游戲仍然能夠繼續(xù)進(jìn)行,我們需要對(duì)onDeleteForm()方法進(jìn)行修改。刪除卡片時(shí),判斷卡片的id和ControlPanelFormId、GamePanelFormId是否相同。如果相同,則說(shuō)明刪除的是響應(yīng)游戲功能的卡片,需要將對(duì)應(yīng)的變量賦值為0,等待下一次生成該種類(lèi)型的卡片。
- protected void onDeleteForm(long formId) {
- .....
- if(ControlPanelFormId == formId){
- ControlPanelFormId = 0;
- }
- if(GamePanelFormId == formId){
- GamePanelFormId = 0;
- }
- }
3. 添加文本更新方法
為了響應(yīng)控制頁(yè)面服務(wù)卡片的上、下、左、右的點(diǎn)擊事件,從而使游戲頁(yè)面服務(wù)卡片的文本進(jìn)行更新操作,因此在響應(yīng)點(diǎn)擊事件前,先增加文本更新方法。
添加兩個(gè)int類(lèi)型的全局變量row_0和column_0,并初始化為4,以記錄空文本的位置。添加一個(gè)int[][]類(lèi)型的變量grids,并初始化為{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,0}},以記錄游戲頁(yè)面服務(wù)卡片各位置上的文本。
添加一個(gè)名為upText的函數(shù),形參為(String direction)。根據(jù)傳入的參數(shù)的值(click_up、click_left、click_right和click_down),通過(guò)zsonObject.put(key, value)和updateForm(formId, formBindingData)對(duì)游戲頁(yè)面服務(wù)卡片的文本進(jìn)行更新。并且對(duì)變量grids也進(jìn)行同步更新。
- private static int row_0 = 4;
- private static int column_0 = 4;
- private static int[][] grids = {{1, 2, 3, 4},
- {5, 6, 7, 8},
- {9, 10, 11, 12},
- {13, 14, 15, 0}};
- public void upText(String direction){
- if(direction == "click_up"){
- if(row_0 != 4){
- try{
- ZSONObject zsonObject = new ZSONObject();
- zsonObject.put("text" + ((row_0 - 1) * 4 + column_0), Integer.toString(grids[row_0][column_0 - 1]));
- zsonObject.put("text" + ((row_0) * 4 + column_0), "");
- FormBindingData formBindingData = new FormBindingData(zsonObject);
- updateForm(GamePanelFormId, formBindingData);
- int temp = grids[row_0][column_0 - 1];
- grids[row_0][column_0 - 1] = 0;
- grids[row_0 - 1][column_0 - 1] = temp;
- row_0++;
- } catch (Exception e){
- }
- }
- }else if(direction == "click_left"){
- if(column_0 != 4){
- try{
- ZSONObject zsonObject = new ZSONObject();
- zsonObject.put("text" + ((row_0 - 1) * 4 + column_0), Integer.toString(grids[row_0 - 1][column_0]));
- zsonObject.put("text" + ((row_0 - 1) * 4 + column_0 + 1), "");
- FormBindingData formBindingData = new FormBindingData(zsonObject);
- updateForm(GamePanelFormId, formBindingData);
- int temp = grids[row_0 - 1][column_0];
- grids[row_0 - 1][column_0] = 0;
- grids[row_0 - 1][column_0 - 1] = temp;
- column_0++;
- }catch (Exception e){
- }
- }
- }else if(direction == "click_right"){
- if(column_0 != 1){
- try{
- ZSONObject zsonObject = new ZSONObject();
- zsonObject.put("text" + ((row_0 - 1) * 4 + column_0), Integer.toString(grids[row_0 - 1][column_0 - 2]));
- zsonObject.put("text" + ((row_0 - 1) * 4 + column_0 - 1), "");
- FormBindingData formBindingData = new FormBindingData(zsonObject);
- updateForm(GamePanelFormId, formBindingData);
- int temp = grids[row_0 - 1][column_0 - 2];
- grids[row_0 - 1][column_0 - 2] = 0;
- grids[row_0 - 1][column_0 - 1] = temp;
- column_0--;
- }catch (Exception e){
- }
- }
- }else if(direction == "click_down"){
- if(row_0 != 1){
- try{
- ZSONObject zsonObject = new ZSONObject();
- zsonObject.put("text" + ((row_0 - 1) * 4 + column_0), Integer.toString(grids[row_0 - 2][column_0 - 1]));
- zsonObject.put("text" + ((row_0 - 2) * 4 + column_0), "");
- FormBindingData formBindingData = new FormBindingData(zsonObject);
- updateForm(GamePanelFormId, formBindingData);
- int temp = grids[row_0 - 2][column_0 - 1];
- grids[row_0 - 2][column_0 - 1] = 0;
- grids[row_0 - 1][column_0 - 1] = temp;
- row_0--;
- }catch (Exception e){
- }
- }
- }
- }
4. 響應(yīng)上下左右四個(gè)按鈕的點(diǎn)擊事件
點(diǎn)擊事件的回調(diào)方法為onTriggerFormEvent()。在onTriggerFormEvent()方法中,通過(guò)zsonObject.getString接受事件的參數(shù),先判斷GamePanelFormId不等于0,即已經(jīng)生成游戲頁(yè)面服務(wù)卡片,再根據(jù)參數(shù)的不同來(lái)調(diào)用函數(shù)upText。
- protected void onTriggerFormEvent(long formId, String message) {
- ......
- ZSONObject zsonObject = ZSONObject.stringToZSON(message);
- String direction = zsonObject.getString("message");
- if(GamePanelFormId != 0){
- if (direction.equals("click_up")) {
- upText("click_up");
- }else if(direction.equals("click_left")){
- upText("click_left");
- }else if(direction.equals("click_right")){
- upText("click_right");
- }else if(direction.equals("click_down")){
- upText("click_down");
- }
- }
- }
5. 添加時(shí)間更新方法和文本打亂方法
為了響應(yīng)控制頁(yè)面服務(wù)卡片的開(kāi)始的點(diǎn)擊事件,從而使游戲頁(yè)面服務(wù)卡片的文本進(jìn)行打亂操作和控制頁(yè)面服務(wù)卡片的時(shí)間進(jìn)行更新操作,因此在響應(yīng)點(diǎn)擊事件前,先增加時(shí)間更新方法和文本打亂方法。
添加一個(gè)Timer類(lèi)型的全局變量timer,以進(jìn)行時(shí)間疊加更新。添加一個(gè)int類(lèi)型的全局變量time,以記錄當(dāng)前時(shí)間進(jìn)度。添加一個(gè)boolean類(lèi)型的全局變量k并初始化為false,以記錄當(dāng)前時(shí)間是否開(kāi)始累加。
添加一個(gè)名為run的函數(shù)。在函數(shù)體內(nèi)對(duì)變量time賦值為0,對(duì)變量k賦值為true。初始化變量timer,并添加字線程,使時(shí)間累加量time每隔一秒加1,并通過(guò)zsonObject.put(key, value)和updateForm(formId, formBindingData)對(duì)控制頁(yè)面服務(wù)卡片的時(shí)間進(jìn)行更新。
添加一個(gè)名為initialize的函數(shù)。對(duì)變量row_0和column_0賦值為0,grids賦值為{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,0}},定義一個(gè)String[]類(lèi)型的變量array,并初始化為{“click_up”,“click_left”,“click_right”,“click_down”}。重復(fù)50次隨機(jī)抽取一個(gè)字符串,并調(diào)用函數(shù)upText對(duì)游戲頁(yè)面服務(wù)卡片的文本進(jìn)行打亂操作。然后判斷k為true時(shí),則取消時(shí)間任務(wù)timer。最后調(diào)用函數(shù)run。
- private static Timer timer;
- private static int time;
- private static boolean k = false;
- private void initialize(){
- row_0 = 4;
- column_0 = 4;
- grids = new int[][]{{1, 2, 3, 4},
- {5, 6, 7, 8},
- {9, 10, 11, 12},
- {13, 14, 15, 0}};
- String[] array = {"click_up","click_left","click_right","click_down"};
- for(int i = 0; i < 50; i++){
- double random = Math.floor(Math.random() * 4);
- String direction = array[(int) random];
- upText(direction);
- }
- if(k){
- timer.cancel();
- }
- run();
- }
- private void run(){
- time = 0;
- k = true;
- timer = new Timer();
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- getUITaskDispatcher().asyncDispatch(()->{
- time++;
- int hou = time / 3600;
- int min = (time % 3600) / 60;
- int sec = (time % 3600) % 60;
- String str_hour = "";
- String str_min = "";
- String str_sec = "";
- if(hou < 10){
- str_hour = "0" + hou;
- }else{
- str_hour = Integer.toString(hou);
- }
- if(min < 10){
- str_min = "0" + min;
- }else{
- str_min = Integer.toString(min);
- }
- if(sec < 10){
- str_sec = "0" + sec;
- }else{
- str_sec = Integer.toString(sec);
- }
- try{
- ZSONObject zsonObject = new ZSONObject();
- zsonObject.put("time", str_hour + ":" + str_min + ":" + str_sec);
- FormBindingData formBindingData = new FormBindingData(zsonObject);
- updateForm(ControlPanelFormId, formBindingData);
- } catch (Exception e){
- }
- });
- }
- }, 0, 1000);
- }
6. 響應(yīng)開(kāi)始按鈕的點(diǎn)擊事件
點(diǎn)擊事件的回調(diào)方法為onTriggerFormEvent()。在onTriggerFormEvent()方法中,通過(guò)zsonObject.getString接受事件的參數(shù),在判斷GamePanelFormId不等于0的里面,根據(jù)參數(shù)為click_start來(lái)調(diào)用函數(shù)initialize。
- protected void onTriggerFormEvent(long formId, String message) {
- ......
- if(GamePanelFormId != 0){
- if (direction.equals("click_up")) {
- upText("click_up");
- }else if(direction.equals("click_left")){
- upText("click_left");
- }else if(direction.equals("click_right")){
- upText("click_right");
- }else if(direction.equals("click_down")){
- upText("click_down");
- }else if(direction.equals("click_start")) {
- initialize();
- }
- }
- }
四、完善其他功能
1. 當(dāng)游戲頁(yè)面服務(wù)卡片被刪除時(shí)停止計(jì)時(shí)
在onDeleteForm()方法中的判斷GamePanelFormId==formId中添加一個(gè)判斷,如果k為true時(shí),通過(guò)canel()方法停止計(jì)時(shí),并且將k賦值為false。
- protected void onDeleteForm(long formId) {
- ......
- if(ControlPanelFormId == formId){
- ControlPanelFormId = 0;
- }
- if(GamePanelFormId == formId){
- GamePanelFormId = 0;
- if(k){
- timer.cancel();
- k = false;
- }
- }
- }
2. 游戲結(jié)束時(shí)停止計(jì)時(shí)
添加一個(gè)名為gameover的函數(shù),判斷girds的值是否為游戲成功的數(shù)值,若有一個(gè)不符合則返回false,否則返回true。
在函數(shù)upText中判斷函數(shù)gameover的返回值和k的值,如果均為真,則通過(guò)cancel()方法停止計(jì)時(shí),并將k賦值為false。
- public void upText(String direction){
- ......
- if(gameover()){
- timer.cancel();
- k = false;
- }
- }
- private boolean gameover(){
- int[][] Grids = new int[][]{{1, 2, 3, 4},
- {5, 6, 7, 8},
- {9, 10, 11, 12},
- {13, 14, 15, 0}};
- for(int row = 0; row < 4; row++){
- for(int column = 0; column < 4; column++){
- if(grids[row][column] != Grids[row][column]){
- return false;
- }
- }
- }
- return true;
- }
寫(xiě)在最后
至此,整個(gè)項(xiàng)目就完成了,使用模擬器Mate 30運(yùn)行即可。
文章相關(guān)附件可以點(diǎn)擊下面的原文鏈接前往下載
想了解更多內(nèi)容,請(qǐng)?jiān)L問(wèn):
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)