『牛角书』鸿蒙实现简单的华容道
在项目中我们要实现的内容为数字华容道 APP 的开发。
①打开引用首先为数字华容道的初始界面,点击开始游戏即会切换到数字华容道的游戏界面。
②进入数字华容道的游戏界面显示 4*4 的方阵,方阵中分布有随意打乱的 1 至 15 的数字和一个空白方格。
方阵下方显示一个“重新开始”的按钮和一个“返回”按钮,点击“重新开始”按钮即会重新生成随意打乱的 1 至 15 的数字和一个空白方格的方阵。
点击“返回”按钮即会切换到数字华容道的初始界面,最下方有四个指示不同方向箭头的按钮,点击任一按钮或向上、下、左、右任一方向滑动,空白方格周围对应位置的方格便会随之向对应的方向移动一格。
③经过若干次滑动或点击后,当所有的数字按顺序排列后,则会弹出游戏成功的界面,再滑动或点击也不会有任何变化。
创建项目
DevEco Studio 下载安装成功后,打开 DevEco Studio,点击左上角的 File,点击 New,再选择 New Project,选择 Phone 选项,选择默认的模板(Java 版),然后选择保存路径,将文件命名为 MyPhoneApplication(文件名不能出现中文或者特殊字符,否则将无法成功创建项目文件),最后点击 Finish。
实现初始界面布局
首先,我们要先实现数字华容道的初始界面,点击开始游戏即会切换到另一个空白的界面。
①先在 entry>src>main>config.json 文件中最下方"launchType": "standard"的后面添加以下代码。
并且将上方的“label”:“MyPhoneApplication”修改成"label": “数字华容道”,这样就实现去掉应用上方的标题栏和将应用名称改为数字华容道了。
config.json 最下面部分代码:
"orientation": "unspecified", "name": "com.example.myphoneapplication.MainAbility", "icon": "$media:icon", "description": "$string:mainability_description", "label": "数字华容道", "type": "page", "launchType": "standard", "metaData": { "customizeData": [ { "name": "hwc-theme", "value": "androidhwext:style/Theme.Emui.Light.NoTitleBar", "extra": "" } ] }
②先将我们事先准备好的图片复制粘贴到 entry>src>main>resources>base>media 文件夹中(ctrl+c、ctrl+v 复制粘贴),并且命名为 game,点击 OK。
在 entry>src>main>resources>base>layout>ability_main.xml 中添加布局。
先将事先存在的 Text 组件删去,添加 Image 图片组件,引入我们刚才复制粘贴的图片,再添加一个 Button 按钮组件,加入唯一标识符 id 并配置好其他相应的属性:
<?xml version=“1.0” encoding=“utf-8”?><DirectionalLayout xmlns:ohos=“http://schemas.huawei.com/res/ohos” ohos:height=“match_parent” ohos:width=“match_parent” ohos:orientation=“vertical”> <Image ohos:height=“match_parent” ohos:width=“match_parent” ohos:image_src=“$media:game” ohos:layout_alignment=“center” /> <Button ohos:id=“$+id:button_game” ohos:height=“150” ohos:width=“515” ohos:text_alignment=“center” ohos:top_margin=“-810” ohos:left_margin=“250” /></DirectionalLayout>
③在 entry>src>main>java>com.example.myphoneapplication>slice 中右键选择 New>Java Class 增加一个空白的类以用来后面编写数字华容道的游戏界面,并且命名为 SecondAbilitySlice。
将 entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice 中的代码修改成如下:
package com.example.myphoneapplication.slice;import com.example.myphoneapplication.ResourceTable;import ohos.aafwk.ability.AbilitySlice;import ohos.aafwk.content.Intent;public class SecondAbilitySlice extends AbilitySlice { public void onStart(Intent intent) { super.onStart(intent); } @Override public void onActive() { super.onActive(); } @Override public void onForeground(Intent intent) { super.onForeground(intent); }}
④entry>src>main>java>com.example.myphoneapplication>slice>MainAbilitySlice 中的 onStart 函数中添加一个按钮指向我们(2)中添加的按钮。
添加一个响应点击事件的函数,用 parsent 函数跳转到 SecondAbilitySlice:
package com.example.myphoneapplication.slice;import com.example.myphoneapplication.ResourceTable;import ohos.aafwk.content.Intent;import ohos.agp.components.Button;import ohos.agp.components.Component;public class MainAbilitySlice extends ohos.aafwk.ability.AbilitySlice { @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); Button button = (Button) findComponentById(ResourceTable.Id_button_game); button.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { present(new SecondAbilitySlice(),new Intent()); } }); } @Override public void onActive() { super.onActive(); } @Override public void onForeground(Intent intent) { super.onForeground(intent); }}
至此,这一部分就完成了。
实现数字的随机打乱
然后我们要在数字华容道的游戏界面生成随意打乱的1至15的数字和一个空白方格的方阵。
在 entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice 编写代码。
先定义个一个位置布局 layout 和一个二维数组 grids,创建函数 initializeinitialize() 分别对其初始化,在 onStart 函数中调用函数 initializeinitialize():
private float starX, starY, distanceX, distanceY; private DirectionalLayout layout; private int[][] grids; public void onStart(Intent intent) { super.onStart(intent); initialize(); } public void initialize(){ layout = new DirectionalLayout(this); grids = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}}; }
然后定义函数 drawGrids(int[][] grids) 用于绘制 44 方阵和其二维数组对应的数字:
public void drawGrids(int[][] grids){ layout.setLayoutConfig((new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,ComponentContainer.LayoutConfig.MATCH_PARENT))); Component.DrawTask task=new Component.DrawTask() { public void onDraw(Component component, Canvas canvas) { Paint mPaint = new Paint(); mPaint.setColor(Color.GRAY); RectFloat rect=new RectFloat(2,230,1078,1306); canvas.drawRect(rect,mPaint); for(int row = 0; row < 4; row++){ for(int column = 0; column < 4; column++){ mPaint.setColor(Color.CYAN); RectFloat rectFloat=new RectFloat(22+column262,250+row262,272+column262,500+row262); canvas.drawRect(rectFloat,mPaint); mPaint.setColor(Color.YELLOW); mPaint.setTextSize(125); if(grids[row][column]!=0){ if(grids[row][column]<10){ canvas.drawText(mPaint, String.valueOf(grids[row][column]),105+column262,425+row262); } else{ canvas.drawText(mPaint, String.valueOf(grids[row][column]),65+column262,425+row*262); } } } } } }; layout.addDrawTask(task); setUIContent(layout); }
再定义函数 changeGrids(int[][] grids,int direction),每次接收一个方向,2 表示上移,-1 表示左移,1 表示右移,-2 表示下移,找出空白方格所在位置对应的二维数组下标,对应的方格和空白方格对应的二维数组的数值对调:
public void changeGrids(int[][] grids,int direction){ int row_0 = 3; int column_0 = 3; int temp; for(int row = 0; row < 4; row++) { for (int column = 0; column < 4; column++) { if(grids[row][column] == 0){ row_0 = row; column_0 = column; } } } if(direction == -1 && (column_0 + 1) <= 3){ temp = grids[row_0][column_0 + 1]; grids[row_0][column_0 + 1] = grids[row_0][column_0]; grids[row_0][column_0] = temp; }else if (direction == 1 && (column_0 - 1) >= 0) { temp = grids[row_0][column_0 - 1]; grids[row_0][column_0 - 1] = grids[row_0][column_0]; grids[row_0][column_0] = temp; } else if (direction == 2 && (row_0 + 1) <= 3) { temp = grids[row_0 + 1][column_0]; grids[row_0 + 1][column_0] = grids[row_0][column_0]; grids[row_0][column_0] = temp; } else if (direction == -2 && (row_0 - 1) >= 0) { temp = grids[row_0 - 1][column_0]; grids[row_0 - 1][column_0] = grids[row_0][column_0]; grids[row_0][column_0] = temp; } }
定义函数 createGrids(int[][] grids) 用于随机生成一个表示方向的数字,循环调用函数 changeGrids(grids,direction) 用于随机打乱二维数组对应的数字:
public void createGrids(int[][] grids){ int[] array = {-1,-2,1,2}; for(int i = 0; i < 100; i++){ int random = (int)Math.floor(Math.random()*4); int direction = array[random]; changeGrids(grids,direction); } }
最后在 initialize() 函数中调用 createGrids(grids) 函数和 drawGrids(grids) 函数:
public void initialize(){ layout = new DirectionalLayout(this); grids = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}}; createGrids(grids); drawGrids(grids); }
至此,这一部分完成了。
实现滑动或点击调换数字
添加“重新开始”和“返回”按钮,在最下方添加四个指示不同方向箭头的按钮,点击任一按钮或向上、下、左、右任一方向滑动,空白方格周围对应位置的方格便会随之向对应的方向移动一格。
在 entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice 编写代码。
先定义一个函数 drawButton() 用于绘制所有的按钮,四个指示不同方向箭头的按钮分别添加四个响应点击事件的函数。
分别调用对应的 changeGrids(grids,direction) 函数实现空白方格周围对应位置的方格便会随之向对应的方向移动一格,并调用 drawGrids(grids) 函数用于绘制新的方阵:public void drawButton(){ Button button=new Button(this); button.setText(“重新开始”); button.setTextSize(100); button.setTextAlignment(TextAlignment.CENTER); button.setTextColor(Color.WHITE); button.setMarginTop(1400); button.setMarginLeft(80); button.setPadding(20,20,20,20); ShapeElement background = new ShapeElement(); background.setRgbColor(new RgbColor(174, 158, 143)); background.setCornerRadius(100); button.setBackground(background); layout.addComponent(button); Button button0=new Button(this); button0.setText(“返回”); button0.setTextSize(100); button0.setTextAlignment(TextAlignment.CENTER); button0.setTextColor(Color.WHITE); button0.setMarginTop(-170); button0.setMarginLeft(680); button0.setPadding(20,20,20,20); button0.setBackground(background); layout.addComponent(button0); ShapeElement background0 = new ShapeElement(); background0.setRgbColor(new RgbColor(174, 158, 143)); background0.setCornerRadius(100); Button button1=new Button(this); button1.setText(“↑”); button1.setTextAlignment(TextAlignment.CENTER); button1.setTextColor(Color.WHITE); button1.setTextSize(100); button1.setMarginLeft(500); button1.setMarginTop(70); button1.setPadding(10,0,10,0); button1.setBackground(background0); button1.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { changeGrids(grids,2); } }); layout.addComponent(button1); Button button2=new Button(this); button2.setText(“←”); button2.setTextAlignment(TextAlignment.CENTER); button2.setTextColor(Color.WHITE); button2.setTextSize(100); button2.setMarginTop(10); button2.setMarginLeft(400); button2.setPadding(10,0,10,0); button2.setBackground(background0); button2.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { changeGrids(grids,-1); } }); layout.addComponent(button2); Button button3=new Button(this); button3.setText(“→”); button3.setTextAlignment(TextAlignment.CENTER); button3.setTextColor(Color.WHITE); button3.setTextSize(100); button3.setMarginLeft(600); button3.setMarginTop(-130); button3.setPadding(10,0,10,0); button3.setBackground(background0); button3.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { changeGrids(grids,1); } }); layout.addComponent(button3); Button button4=new Button(this); button4.setText(“↓”); button4.setTextAlignment(TextAlignment.CENTER); button4.setTextColor(Color.WHITE); button4.setTextSize(100); button4.setMarginLeft(500); button4.setMarginTop(10); button4.setPadding(10,0,10,0); button4.setBackground(background0); button4.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { changeGrids(grids,-2); } }); layout.addComponent(button4); drawGrids(grids); }
然后添加一个函数 slideGrids() 为布局 layout 添加一个滑动事件,并获取滑动开始与结束的坐标,并计算出大致的滑动方向,分别调用对应的 changeGrids(grids,direction) 函数实现空白方格周围对应位置的方格便会随之向对应的方向移动一格,并调用 drawGrids(grids) 函数用于绘制新的方阵,并在开头添加相应的变量:
private float starX, starY, distanceX, distanceY;public void slideGrids(){ layout.setTouchEventListener(new Component.TouchEventListener() { @Override public boolean onTouchEvent(Component component, TouchEvent touchEvent) { MmiPoint point = touchEvent.getPointerScreenPosition(0); switch (touchEvent.getAction()) { case TouchEvent.PRIMARY_POINT_DOWN: starX = point.getX(); starY = point.getY(); break; case TouchEvent.PRIMARY_POINT_UP: distanceX = point.getX() - starX; distanceY = point.getY() - starY; break; } if (gameover() == false){ if (Math.abs(distanceX) > Math.abs(distanceY)) { if (distanceX > 200) { changeGrids(grids,1); } else if (distanceX < -200) { changeGrids(grids,-1); } } else if (Math.abs(distanceX) < Math.abs(distanceY)){ if (distanceY > 200) { changeGrids(grids,-2); } else if (distanceY < -200) { changeGrids(grids,2); } } } drawGrids(grids); return false; } }); }
最后在 initialize() 函数中调用 slideGrids() 函数和 drawButton() 函数:
public void initialize(){ layout = new DirectionalLayout(this); grids = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8,}, {9, 10, 11, 12}, {13, 14, 15, 0}}; createGrids(grids); slideGrids(); drawButton(); drawGrids(grids); }
至此,这一部分完成了
实现游戏成功界面
点击“重新开始”按钮即会重新生成随意打乱的 1 至 15 的数字和一个空白方格的方阵,点击“返回”按钮即会切换到数字华容道的初始界面,经过若干次滑动或点击后,当所有的数字按顺序排列后,则会弹出游戏成功的界面,再滑动或点击也不会有任何变化。
在 entry>src>main>java>com.example.myphoneapplication>slice>SecondAbilitySlice 编写代码。
首先定义一个函数 drawText() 用于绘制游戏成功字样:
public void drawText(){ Text text=new Text(this); text.setText(“游戏成功”); text.setTextSize(100); text.setTextColor(Color.BLUE); text.setTextAlignment(TextAlignment.CENTER); text.setMarginsTopAndBottom(-2000,0); text.setMarginsLeftAndRight(350,0); layout.addComponent(text); setUIContent(layout); }
然后定义一个函数 gameover() 用于判断二维数组的数字是否按顺序排列,当二维数组的数字按顺序排列时返回 true,否则返回 false:
public boolean gameover() { int[][] gameoverGrids = {{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] != gameoverGrids[row][column]) { return false; } } } return true; }
再在 drawButton() 函数中重新开始按钮中添加一个响应点击事件的函数,用于调用函数 initialize() 实现重新生成随意打乱的 1 至 15 的数字和一个空白方格的方阵,返回按钮中添加一个响应点击事件的函数,用 parsen 函数返回数字华容道的初始界面,四个指示不同方向箭头的按钮的响应点击事件的函数中增加一个判断,当函数 gameover() 返回为 false 时才调用各自的 changeGrids(grids,direction) 函数,最后增加一个判断,当函数 gameover() 返回为 true 时调用函数 drawText():
public void drawButton(){//部分代码没有贴出,可自行下载源代码查看 button.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { initialize(); } }); button0.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { present(new SecondAbilitySlice(),new Intent()); } }); button1.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { if (gameover() == false){ changeGrids(grids,2); } } }); button2.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { if (gameover() == false){ changeGrids(grids,-1); } } }); button3.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { if (gameover() == false){ changeGrids(grids,1); } } }); button4.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { if (gameover() == false){ changeGrids(grids,-2); } } }); if(gameover()){ drawText(); } }
在函数 slideGrids() 函数中增加一个判断,当函数 gameover() 返回为 false 时才调用 changeGrids(grids,direction) 函数,最后增加一个判断,当函数 gameover() 返回为 true 时调用函数 drawText():
public void slideGrids(){//部分代码没有贴出,可自行下载源代码查看 if (gameover() == false){ //{…} } if(gameover()){ drawText(); } }
至此,整个 demo 全部完成了。