原子化服務卡片還原經(jīng)典小游戲—數(shù)字華容道
前言
服務卡片也能玩游戲了,今天就來還原經(jīng)典小游戲——數(shù)字華容道。詳細講述了數(shù)字華容道在服務卡片上的開發(fā)思路,內含詳細注釋。趕緊動手來開發(fā)一張服務卡片,休閑時刻來一局!
概述
老規(guī)矩,先上效果圖。
分別調出兩種類型的服務卡片,效果如下:

點擊開始按鈕,游戲頁面服務卡片的文本會隨機打亂,開始進行計時。點擊上下左右四個按鈕,文本會向對應的方向移動一格。

當游戲結束時或者游戲頁面服務卡片被刪除時,控制頁面服務卡片會停止計時。

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

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

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

選擇Image With Information。

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


4. 設置首卡片尺寸
在MyCarGame->entry->src->main->config.json中,將服務卡片ControlPage中defaultDimension的屬性值修改為2 * 4,將服務卡片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",
- ......
- }
- ]
二、完善兩個服務卡片頁面布局
1. 完善控制服務卡片頁面布局
在MyCarGame->entry->src->main->js->ControlPage->pages->index->index.hml中將原有的容器代碼全部刪除。
添加一個基礎容器div,增加類選擇器為class。添加一個基礎容器div,類選擇器為class_text,內部添加兩個text組件,類選擇器均為text,第一個text組件的文本為“時間:”,第二個text組件的文本為{{time}},即動態(tài)綁定變量time,以顯示游戲的時間。
最后依次添加五個按鈕,按鈕上的文本分別為“上”、“左”、“右”、“開始”和“下”,類選擇器分別為btn_up、btn_left、btn_right、btn_start和btn_down,分別添加一個點擊事件click_up、click_left、click_right、start和click_down。其中“左”和“右”兩個按鈕置于類選擇器為class_button的基礎容器div中,“開始”和“下”兩個按鈕置于類選擇器為class_button的基礎容器div中。
- <div class="class">
- <div class="class_text">
- <text class="text">時間:</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">開始</button>
- <button class="btn_down" onclick="click_down">下</button>
- </div>
- </div>
在MyCarGame->entry->src->main->js->ControlPage->pages->index->index.css中將原有的類選擇器全部刪除。
各個類選擇器配置如下:
- .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ù)組的每個屬性包括每個事件的名稱,名稱里面又含有事件的類型“action”和攜帶的參數(shù)“params”。現(xià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. 完善控制服務卡片頁面布局
在MyCarGame->entry->src->main->js->GamePage->pages->index->index.hml中將原有的容器代碼全部刪除。
添加一個基礎容器div,增加類選擇器為class。依次添加16個text組件,類選擇器均為text,文本分別為{{text1}}、{{text2}}、……、{{text16}},即分別動態(tài)綁定一個變量。其中,每四個text組件置于類選擇器為class_text的基礎容器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中將原有的類選擇器全部刪除。
各個類選擇器配置如下:
- .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": ""
- }
- }
三、響應點擊事件
在MyCarGame->entry->src->main->java->com->test->mycargame->MainAbility.java中修改以下代碼。
1. 修改onCreateForm()方法
onCreateForm()方法在兩種情況被調用。第一種情況是上滑呼出卡片的時候,這時候上滑卡片的動作就會調用一次onCreateForm()方法生成一張卡片;
第二種情形是長按應用,然后點擊"服務卡片",此時會顯示該應用的所有卡片,每一張卡片都會回調一次onCreateForm()方法并生成一張對應的卡片。當選擇了其中一張卡片添加到桌面后,其他卡片回調onDeleteForm()方法來刪除該卡片。
為了保證控制頁面服務卡片和游戲頁面服務卡片只對對應類型卡片的第一張起作用,分別定義兩個long類型的全局變量ControlPanelFormId和GamePanelFormId,并初始化為0。在onCreateForm()方法中,根據(jù)服務卡片的名字formName來判斷是哪種類型的服務卡片,如果對應的變量為0,即表示該種類型的卡片還沒有生成,則使該卡片的id賦值給對應的變量。
- 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()方法
為了使卡片刪除后再生成卡片時,游戲仍然能夠繼續(xù)進行,我們需要對onDeleteForm()方法進行修改。刪除卡片時,判斷卡片的id和ControlPanelFormId、GamePanelFormId是否相同。如果相同,則說明刪除的是響應游戲功能的卡片,需要將對應的變量賦值為0,等待下一次生成該種類型的卡片。
- protected void onDeleteForm(long formId) {
- .....
- if(ControlPanelFormId == formId){
- ControlPanelFormId = 0;
- }
- if(GamePanelFormId == formId){
- GamePanelFormId = 0;
- }
- }
3. 添加文本更新方法
為了響應控制頁面服務卡片的上、下、左、右的點擊事件,從而使游戲頁面服務卡片的文本進行更新操作,因此在響應點擊事件前,先增加文本更新方法。
添加兩個int類型的全局變量row_0和column_0,并初始化為4,以記錄空文本的位置。添加一個int[][]類型的變量grids,并初始化為{{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,0}},以記錄游戲頁面服務卡片各位置上的文本。
添加一個名為upText的函數(shù),形參為(String direction)。根據(jù)傳入的參數(shù)的值(click_up、click_left、click_right和click_down),通過zsonObject.put(key, value)和updateForm(formId, formBindingData)對游戲頁面服務卡片的文本進行更新。并且對變量grids也進行同步更新。
- 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. 響應上下左右四個按鈕的點擊事件
點擊事件的回調方法為onTriggerFormEvent()。在onTriggerFormEvent()方法中,通過zsonObject.getString接受事件的參數(shù),先判斷GamePanelFormId不等于0,即已經(jīng)生成游戲頁面服務卡片,再根據(jù)參數(shù)的不同來調用函數(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. 添加時間更新方法和文本打亂方法
為了響應控制頁面服務卡片的開始的點擊事件,從而使游戲頁面服務卡片的文本進行打亂操作和控制頁面服務卡片的時間進行更新操作,因此在響應點擊事件前,先增加時間更新方法和文本打亂方法。
添加一個Timer類型的全局變量timer,以進行時間疊加更新。添加一個int類型的全局變量time,以記錄當前時間進度。添加一個boolean類型的全局變量k并初始化為false,以記錄當前時間是否開始累加。
添加一個名為run的函數(shù)。在函數(shù)體內對變量time賦值為0,對變量k賦值為true。初始化變量timer,并添加字線程,使時間累加量time每隔一秒加1,并通過zsonObject.put(key, value)和updateForm(formId, formBindingData)對控制頁面服務卡片的時間進行更新。
添加一個名為initialize的函數(shù)。對變量row_0和column_0賦值為0,grids賦值為{{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”}。重復50次隨機抽取一個字符串,并調用函數(shù)upText對游戲頁面服務卡片的文本進行打亂操作。然后判斷k為true時,則取消時間任務timer。最后調用函數(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. 響應開始按鈕的點擊事件
點擊事件的回調方法為onTriggerFormEvent()。在onTriggerFormEvent()方法中,通過zsonObject.getString接受事件的參數(shù),在判斷GamePanelFormId不等于0的里面,根據(jù)參數(shù)為click_start來調用函數(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. 當游戲頁面服務卡片被刪除時停止計時
在onDeleteForm()方法中的判斷GamePanelFormId==formId中添加一個判斷,如果k為true時,通過canel()方法停止計時,并且將k賦值為false。
- protected void onDeleteForm(long formId) {
- ......
- if(ControlPanelFormId == formId){
- ControlPanelFormId = 0;
- }
- if(GamePanelFormId == formId){
- GamePanelFormId = 0;
- if(k){
- timer.cancel();
- k = false;
- }
- }
- }
2. 游戲結束時停止計時
添加一個名為gameover的函數(shù),判斷girds的值是否為游戲成功的數(shù)值,若有一個不符合則返回false,否則返回true。
在函數(shù)upText中判斷函數(shù)gameover的返回值和k的值,如果均為真,則通過cancel()方法停止計時,并將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;
- }
寫在最后
至此,整個項目就完成了,使用模擬器Mate 30運行即可。
文章相關附件可以點擊下面的原文鏈接前往下載