【木棉花】#打卡不停更#HarmonyOS小游戏项目——数独Sudoku(5) 原创 精华
前言
Hello,各位好久不见!
非常抱歉,这段时间作者在忙于其他事情,所以一直没有在社区更文。
时间过得很快,HarmonyOS与OpenHarmony也发展地很快,目前DevEco Studio的鸿蒙SDK也已经更新到API8的版本了。对于鸿蒙操作系统的蒸蒸日上,我感到非常喜悦。
最近的一段时间,我重新阅读了放在IDE中的数独游戏项目的代码,发现自己曾经写的代码其实挺烂的。事实上,我是在接触了鸿蒙的前端后,才开始尝试去学习Java语言并利用Java创建UI的。所以在写这个项目时,我的Java仍处于比较菜的水平,权限修饰符用的很乱,并且有些代码虽然能运行,但也写的不简洁或者不合理,所以近期我对这个项目的代码进行了许多优化。实际上,作为一个学习者,任何人在编程的初期都不可避免地会写一些烂代码,以及踩一些坑。我们进步的过程,不仅是学习一些未接触过的新知识,更重要的是在之前所写过的代码中不断优化,并在这个过程中不断提炼新的计算机思维。
另外,因为DevEco studo的SDK8版本已经将Java移除了,这意味着在IDE中用Java代码编译的程序只能运行在SDK7以前的华为机型,所以笔者在更新完关于数独项目的文章后,就暂时不继续分享有关Java的代码了,而是先尝试去学习与分享关于JavaScrip与ets的内容。
上期的内容回顾——>>https://ost.51cto.com/posts/15252
正文
在本期的分享中,笔者将继续完善项目的相关功能,并制作判定游戏是否通关的功能。
定义两个重要的数组
打开GameAbilitySlice,并在合适的位置定义两个二维数组——grids_win和grids_input。注意,这两个数组是作为全局变量定义的,他们不能定义在生命周期函数或其他函数内。
//定义两个二维数组
int[][] grids_win=new int[6][];
int[][] grids_input=new int[6][];
......
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
.......
在这两个新定义的数组变量,grids_win代表储存了数独答案的二维数组,而grids_input代表用户在游戏交互过程中将要改变的数组。这两个数组在项目中主要用于判定游戏的胜利与否,而判断逻辑是这样的:如果grid_input中每行每列的元素都等于grids_win中每行每列的元素,这就意味着用户在数独网格填入的数字与答案一致,此时游戏胜利;否则,游戏未成功。
在定义grids_win与grids_input前,我们在之前的文章中已经定义了grid_c0,而grid_c0用于生成网格区域中的数独题目的。接下来,我们再定义grid_c0数组所对应的答案数组——grid_v0(注意,grid_c0与grid_v0均是定义在onstart函数内的局部变量):
...
@Override
protected void onStart(Intent intent) {
super.onStart(intent);
grid_c0[0]=new int[]{0,0,0,0,1,0};
...
//定义grid_v0
int[][] grid_v0=new int[6][];
grid_v0[0]=new int[]{4,3,5,6,1,2};
grid_v0[1]=new int[]{1,5,2,4,6,3};
grid_v0[2]=new int[]{6,2,3,1,4,5};
grid_v0[3]=new int[]{5,4,1,2,3,6};
grid_v0[4]=new int[]{2,6,4,3,5,1};
grid_v0[5]=new int[]{3,1,6,5,2,4};
然后,我们再进行如下赋值操作(笔者也惊讶地发现,Java的数组赋值竟然可以如此轻松快捷qwq):
grids_input=grid_c0;
grids_win=grid_v0;
这样,grids_input和grids_win就被输入对应的数据了。
或许读者会问,为什么不直接把grid_c0和grid_v0分别作为用户交互的数组和答案数组来使用呢?原因是,grid_c0与grid_v0只是分别作为这个游戏的某一个一个关卡的题目与答案,在后文,笔者会导入更多的题目与答案,所以我们需要另外定义grid_input与grid_win作为两个全局变量,当关卡不同时,这两个变量也就分别负责储存不同的题目与答案,以及参与判断游戏是否胜利的逻辑判断过程。
创建三个功能按钮
我们先找到之前创建的6个圆形button对象(即用于在网格区域输入数字的Button组件),并在这6个圆形button对象的监听器内各加入一行指令(即下面代码中“//”前的部分):
//按键区域
Button button_input1=new Button(this);
button_input1.setText("1");
button_input1.setTextColor(Color.BLACK);
button_input1.setTextSize(75);
button_input1.setBackground(element2);
button_input1.setComponentSize(150, 150);
button_input1.setPosition(70,1600);
button_input1.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setTextColor(Color.BLACK);
button_temp.setText("1");
grids_input[j1][k1]=1; //为网格区域对应的题目矩阵的相应位置赋值1
}
});
layout1.addComponent(button_input1);
Button button_input2=new Button(this);
button_input2.setText("2");
button_input2.setTextColor(Color.BLACK);
button_input2.setTextSize(75);
button_input2.setBackground(element2);
button_input2.setComponentSize(150, 150);
button_input2.setPosition(70+160*1,1600);
button_input2.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setTextColor(Color.BLACK);
button_temp.setText("2");
grids_input[j1][k1]=2;//为网格区域对应的题目矩阵的相应位置赋值2
}
});
layout1.addComponent(button_input2);
Button button_input3=new Button(this);
button_input3.setText("3");
button_input3.setTextColor(Color.BLACK);
button_input3.setTextSize(75);
button_input3.setBackground(element2);
button_input3.setComponentSize(150, 150);
button_input3.setPosition(70+160*2,1600);
button_input3.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setTextColor(Color.BLACK);
button_temp.setText("3");
grids_input[j1][k1]=3;//为网格区域对应的题目矩阵的相应位置赋值3
}
});
layout1.addComponent(button_input3);
Button button_input4=new Button(this);
button_input4.setText("4");
button_input4.setTextColor(Color.BLACK);
button_input4.setTextSize(75);
button_input4.setBackground(element2);
button_input4.setComponentSize(150, 150);
button_input4.setPosition(70+160*3,1600);
button_input4.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setText("4");
grids_input[j1][k1]=4;//为网格区域对应的题目矩阵的相应位置赋值4
}
});
layout1.addComponent(button_input4);
Button button_input5=new Button(this);
button_input5.setText("5");
button_input5.setTextColor(Color.BLACK);
button_input5.setTextSize(75);
button_input5.setBackground(element2);
button_input5.setComponentSize(150, 150);
button_input5.setPosition(70+160*4,1600);
button_input5.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setText("5");
grids_input[j1][k1]=5;//为网格区域对应的题目矩阵的相应位置赋值5
}
});
layout1.addComponent(button_input5);
Button button_input6=new Button(this);
button_input6.setText("6");
button_input6.setTextColor(Color.BLACK);
button_input6.setTextSize(75);
button_input6.setBackground(element2);
button_input6.setComponentSize(150, 150);
button_input6.setPosition(70+160*5,1600);
button_input6.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setText("6");
grids_input[j1][k1]=6;//为网格区域对应的题目矩阵的相应位置赋值6
}
});
layout1.addComponent(button_input6);
setUIContent(layout1);
}
指令中的变量j1和变量k1,就是笔者在上期内容中利用Text组件转换所得的的每个网格的位置信息(详细原理详见上期内容)。这样以后,用户每次在某个区域上输入数字(1到6的整数)时,数组grids_input的对应存储位置也会被输入相应的数字元素。
完成上述操作后,我们开始另外创建三个新的按钮,每个按钮都负责不同的功能(如图箭头所指部分):
首先,我们为将要创建的Button对象定义基本的背景元素:
//背景元素
...
ShapeElement element4=new ShapeElement();
element4.setRgbColor(new RgbColor(0,125,225)); //设置Rgb颜色
element4.setCornerRadius(50); //设置圆角
然后通过代码布局创建这三个按钮(需要写入的Button对象的方法都差不多,只是一些参数不同):
//按键区—操作
Button button_clean=new Button(this);//“清除”按钮
//基本UI属性方法
button_clean.setText("清除");
button_clean.setTextColor(Color.WHITE);
button_clean.setTextSize(75);
button_clean.setBackground(element4);
button_clean.setComponentSize(280, 150);
button_clean.setPosition(50,1900);
//设置点击监听器
button_clean.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
button_temp.setText("");
grids_input[j1][k1]=0;
}
});
//设置状态改变监听器,让按钮拥有动态效果
button_clean.setComponentStateChangedListener(new Component.ComponentStateChangedListener() {
@Override
public void onComponentStateChanged(Component component, int i) {
if (ComponentState.isStateMatched(ComponentState.COMPONENT_STATE_PRESSED,i)){
button_clean.setComponentSize(290, 160); //组件处于被点击态时尺寸放大
}else {
button_clean.setComponentSize(280, 150); //组件不处于被点击态时尺寸恢复初始值
}
}
});
//将组件放入自定义布局
layout1.addComponent(button_clean);
Button button_replay=new Button(this);//"重新开始"按钮
//基本UI属性方法
button_replay.setText("重新开始");
button_replay.setTextColor(Color.WHITE);
button_replay.setTextSize(75);
button_replay.setBackground(element4);
button_replay.setComponentSize(370, 150);
button_replay.setPosition(50+307,1900);
//设置点击监听器
button_replay.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
Intent intent_replay=new Intent();
present(new GameAbilitySlice(),intent_replay);
}
});
//设置状态改变监听器,让按钮拥有动态效果
button_replay.setComponentStateChangedListener(new Component.ComponentStateChangedListener() {
@Override
public void onComponentStateChanged(Component component, int i) {
if (ComponentState.isStateMatched(ComponentState.COMPONENT_STATE_PRESSED,i)){
button_replay.setComponentSize(380,160);
}else {
button_replay.setComponentSize(370, 150);
}
}
});
//将组件放入自定义布局
layout1.addComponent(button_replay);
Button button_pr=new Button(this);//“提交“按钮
//基本UI属性方法
button_pr.setText("提交");
button_pr.setTextColor(Color.WHITE);
button_pr.setTextSize(75);
button_pr.setBackground(element4);
button_pr.setComponentSize(280, 150);
button_pr.setPosition(50+700,1900);
//设置点击监听器
button_pr.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
}
});
//设置状态改变监听器,让按钮拥有动态效果
button_pr.setComponentStateChangedListener(new Component.ComponentStateChangedListener() {
@Override
public void onComponentStateChanged(Component component, int i) {
if (ComponentState.isStateMatched(ComponentState.COMPONENT_STATE_PRESSED,i)){
button_pr.setComponentSize(290, 160);
}else {
button_pr.setComponentSize(280, 150);
}
}
});
//将组件放入自定义布局
layout1.addComponent(button_pr);
这样之后,我们就新创建了三个按钮。其中,第一个”清除“按钮用于在网格区域的获焦网格上输入空字符串,这意味着我们实现了清除白色网格内已输入数字的功能。在清除白色网格内的数字的同时,此按钮还将grid_input(即在游戏过程会被用户修改参数的矩阵)对应位置的元素赋值为0,确保grid_input内的对应元素可以在清除功能被执行时更新(否则游之后的游戏判断功能会出bug)。
第二个”重新开始“按钮用于让应用从当前的GameAbilitySlice跳转至另一个新生成的GameAbilitySlice,能够实现页面刷新的功能。由于在之前的文章中,我们在onBackground回调中加入了销毁指令,所以系统由旧的GameAbilitySlice导航至新的GameAbilitySlice时,旧的GameAbilitySlice由于进入background态后会启用onBackground回调,进而被销毁。假如之前我们没有添加销毁指令,那么用户每点击一次”重新开始“的按钮,AbilitySlice的实例栈就会堆放一个GameAbilitySlice,这是非常浪费系统内存资源的。
第三个按钮是”提交“键,我们已经写入了基本的方法,但他的监听器内是未添加任何代码的。接下来我们就在这个button对象的监听器内加入一些指令,以实现判断游戏是否成功的功能。
制作判定游戏成功的函数
首先,我们在合适的位置定义一个布尔型函数:
import ......
public class GameAbilitySlice extends AbilitySlice {
......
private boolean Gamesuccess(){
int a,b;
for(a=0;a<6;a++){
for (b=0;b<6;b++){
if (grids_win[a][b]!=grids_input[a][b]){
return false;
}
}
}
return true;
}
......
@Override
protected void onStart(Intent intent) {
......
这个函数是返回布尔值的函数,其封装的代码也非常简单,就是让grid_win矩阵与grid_input矩阵的每个位置的元素进行比较,如果经过循环判断为全部相等,那么这两个矩阵就完全相等,即满足了游戏胜利的条件。值得一提的是,由于此函数封装的代码包含的两个变量——grids_win与grids_input是全局变量,所以笔者并没有将执行不同功能的代码完全模块化,这意味着如果这两个全局变量在某些指令中被修改,Gamesuccess()是有几率受到影响的,这是项目中不足的地方。
接着,我们通过代码布局初始化两个弹窗(弹窗必须定义在 button_pr之前):
//对话框
CommonDialog Dialog_win=new CommonDialog(getContext());
Dialog_win.setSize(800,400);
Dialog_win.setTitleText(" 游戏解答成功!");
Dialog_win.setButton(IDialog.BUTTON1,"返回主菜单",(iDialog, i) -> Dialog_win.destroy());
CommonDialog Dialog_fail=new CommonDialog(getContext());
Dialog_fail.setSize(800,400);
Dialog_fail.setTitleText(" 啊哦~没有通过哦");
Dialog_fail.setContentText(" 游戏未成功解答");
Dialog_fail.setButton(IDialog.BUTTON1,"继续游戏",(iDialog, i) -> Dialog_fail.destroy());
最后,我们在“提交”按钮的监听器内加入指令:
Button button_pr=new Button(this);
......
button_pr.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
//加入指令
if (Gamesuccess()){
Dialog_win.show();//成功
}else {
//加入指令
Dialog_fail.setContentText(" 游戏未完成或答案不正确" );
Dialog_fail.show();//失败
}
}
});
......
layout1.addComponent(button_pr);
可以看到,如果Gamesuccess()函数返回的值是true,那么系统就会调用Dialog_win的show()方法,将Dialog_win展示在屏幕前以提示游戏成功;如果返回的值是false,系统则调用 Dialog_fail的show()方法,将Dialog_fail展示在屏幕前以提示游戏未成功。这样一来,游戏成功与否的判断逻辑便得以成功搭建。
经过上述的所有操作后,我们可以打开API为6的模拟机试玩,效果图如下:
因为SDK6版本的P40模拟机在近期被停用了,所以我只能用平板模拟机调试程序(/(ㄒoㄒ)/~~),图片看起来可能有点怪怪的。
结语
本期的内容就先分享到这里,更多关于数独小游戏项目精彩的内容我将在下期继续为大家揭晓。
不断的审视过去的自己,也是提升自己的好方法,向楼主学习
Java和ets都要会一点,开发更方便