这是我的第一篇博客,没什么目的,就随便写的,权当联系如何写blog了。
猜数字游戏
想起小时候在文曲星上玩的猜数字游戏,摸鱼的时候就自己做了一个。
目前没做UI上的美化,仅实现了基础功能。
游戏说明
游戏规则很简单,程序随机生成一个四位数的数字(不包含重复数字),然后由玩家进行猜测。
如果猜的数字对了但位置不对,记B+1;如果猜的数字对了且位置也对,记A+1。都猜对时结果即4A0B。
项目设计
采用了Jetpack的LiveData和ViewModel,实现数据和UI分离。
贴上代码,具体的内容都写在注释里了。
代码实现
- MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private TextView textView;
private TextView result;
private GuessViewModel guessViewModel;
private MutableLiveData<String> textLiveData; // 每次按键的内容
private MutableLiveData<String> resultLiveData; // 每次猜得结果的内容(结果记录板)
private MutableLiveData<Boolean> canTouchLiveData; //按键是否允许点击
private Button button_0, button_1, button_2, button_3, button_4, button_5, button_6,
button_7, button_8, button_9, button_del, button_guess;
private ScrollView scrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
textLiveData.observeForever(s -> {
//获取显示板上已有的文本内容
String t = textView.getText().toString();
//如果已显示四位数字了,则不在允许新的数字
if (t.length() < 4) {
textView.setText(t + s);
}
});
// 将猜得的结果显示在结果记录板上
resultLiveData.observeForever(s -> result.setText(s));
// 如果已猜中,则禁止点击除“重新开始”外的其他按键
canTouchLiveData.observeForever(b -> {
button_0.setEnabled(b);
button_1.setEnabled(b);
button_2.setEnabled(b);
button_3.setEnabled(b);
button_4.setEnabled(b);
button_5.setEnabled(b);
button_6.setEnabled(b);
button_7.setEnabled(b);
button_8.setEnabled(b);
button_9.setEnabled(b);
button_del.setEnabled(b);
button_guess.setEnabled(b);
});
}
/**
* 删除最后一位数字
*/
private void del() {
// 获取显示板上的数字
String t1 = textView.getText().toString();
// 如果无数字则return掉
if (t1.length() == 0 || t1 == null) return;
// 截取字符串,去掉最后一位
textView.setText(t1.substring(0, t1.length() - 1));
}
/**
* 清空结果记录板
*/
private void clear() {
textView.setText("");
}
/**
* 初始化View
*/
private void init() {
//获取ViewModel
guessViewModel = new ViewModelProvider(this,
new ViewModelProvider.AndroidViewModelFactory(getApplication()))
.get(GuessViewModel.class);
//获取各LiveData
textLiveData = guessViewModel.getTextLiveData();
resultLiveData = guessViewModel.getResultLiveData();
canTouchLiveData = guessViewModel.getCanTouchLiveData();
textView = findViewById(R.id.tv_input);
result = findViewById(R.id.tv_result);
button_0 = findViewById(R.id.number_0);
button_0.setOnClickListener(this);
button_1 = findViewById(R.id.number_1);
button_1.setOnClickListener(this);
button_2 = findViewById(R.id.number_2);
button_2.setOnClickListener(this);
button_3 = findViewById(R.id.number_3);
button_3.setOnClickListener(this);
button_4 = findViewById(R.id.number_4);
button_4.setOnClickListener(this);
button_5 = findViewById(R.id.number_5);
button_5.setOnClickListener(this);
button_6 = findViewById(R.id.number_6);
button_6.setOnClickListener(this);
button_7 = findViewById(R.id.number_7);
button_7.setOnClickListener(this);
button_8 = findViewById(R.id.number_8);
button_8.setOnClickListener(this);
button_9 = findViewById(R.id.number_9);
button_9.setOnClickListener(this);
button_del = findViewById(R.id.bt_delete);
button_del.setOnClickListener(this);
button_guess = findViewById(R.id.bt_guess);
button_guess.setOnClickListener(this);
findViewById(R.id.bt_again).setOnClickListener(this);
scrollView = findViewById(R.id.scroll_view);
// 才出正确答案的回调接口
guessViewModel.setOnSuccessListener(() -> Log.d(TAG, "success: !!!!"));
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.number_0:
case R.id.number_1:
case R.id.number_2:
case R.id.number_3:
case R.id.number_4:
case R.id.number_5:
case R.id.number_6:
case R.id.number_7:
case R.id.number_8:
case R.id.number_9:
// 获取点击的数字
String s = ((Button) view).getText().toString();
// 判断是否和之前的数字重复
if (Utils.hasRepeatNumber(textView.getText().toString(), s)) return;
// 将数字更新至显示板上
textLiveData.setValue(s);
break;
case R.id.bt_delete:
// 删除最后一位数字
del();
break;
case R.id.bt_guess:
// 执行ViewModel中的检查结果方法
guessViewModel.checkResult(textView.getText().toString());
// 清除显示板上的内容
clear();
// 将ScrollView自动翻至最底部
new Handler().post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN));
break;
case R.id.bt_again:
//重新开始
// 清除显示板上的内容
clear();
// 创建一个新的答案
guessViewModel.makeAnswer();
break;
default:
break;
}
}
}
- GuessViewModel.java
public class GuessViewModel extends AndroidViewModel {
private static final String TAG = "GuessViewModel";
private static final int NUMBER_LENGTH = 4; //因为游戏是四位数的猜数字,所以定义了常量4,避免魔法数
private MutableLiveData<String> textLiveData;
private MutableLiveData<String> resultLiveData;
private MutableLiveData<Boolean> canTouchLiveData;
private String answer; //生成的答案
private Context mContext;
private int count; //每局猜测的次数
public GuessViewModel(@NonNull Application application) {
super(application);
//使用的是AndroidViewModel,方便获取Context
mContext = getApplication().getApplicationContext();
}
public MutableLiveData<String> getTextLiveData() {
if (textLiveData == null) {
textLiveData = new MutableLiveData<>();
textLiveData.setValue("");
}
return textLiveData;
}
public MutableLiveData<String> getResultLiveData() {
if (resultLiveData == null) {
resultLiveData = new MutableLiveData<>();
resultLiveData.setValue("");
}
return resultLiveData;
}
public MutableLiveData<Boolean> getCanTouchLiveData() {
if (canTouchLiveData == null) {
canTouchLiveData = new MutableLiveData<>();
canTouchLiveData.setValue(true);
}
return canTouchLiveData;
}
/**
* 检查结果
* @param result 玩家猜测的结果
*/
public void checkResult(String result) {
//如果还没有答案,则生成一个答案
if (answer == null) {
answer = makeAnswer();
}
Log.d(TAG, "answer is : " + answer);
//创建一个HashSet,用来判断一共猜中了几个数字
HashSet set = new HashSet();
//将玩家猜的结果和答案都转换成char数组
char[] charResult = result.toCharArray();
char[] charAnswer = answer.toCharArray();
int a = 0; //位置和数字都正确
int b = 0; //仅数字正确
//输入数字位数不足
if (result == null || result.length() != NUMBER_LENGTH) {
Toast.makeText(mContext, "请输入正确数字", Toast.LENGTH_SHORT).show();
return;
}
//先将玩家猜出的结果装入set中
for (int i = 0; i < NUMBER_LENGTH; i++) {
set.add(charResult[i]);
}
//判断结果
for (int i = 0; i < NUMBER_LENGTH; i++) {
//1.比较有几个相同的数字
if (set.contains(charAnswer[i])) {
++b;
}
//2.比较有几个位置相同的数字
if (charResult[i] == charAnswer[i]) {
++a;
}
}
// b中包含了a,故需要减去a
b -= a;
//完成一次猜测,计数器加一
++count;
//更新记录板
addLog(String.format("%d . %s %dA%dB", count, result, a, b));
//完全猜中,游戏结束
if (a == 4) {
if (onSuccessListener != null) {
onSuccessListener.success();
}
//已经猜中了,游戏结果,禁止点击按键
canTouchLiveData.setValue(false);
}
}
/**
* 更新结果记录板
* param s 最后一个猜测的结果
*/
private String addLog(String s) {
String origin = resultLiveData.getValue();
String newText = origin + "\r\n" + s;
resultLiveData.setValue(newText);
return newText;
}
/**
* 生成题目,即开始新的一局
*/
public String makeAnswer() {
//允许按键
canTouchLiveData.setValue(true);
//计数器清空
count = 0;
//记录板清空
resultLiveData.setValue("");
//生成一个随机数 0 至 9876 的随机数。(大于9876的数字必然包含重复数字)
Random random = new Random();
int result = random.nextInt(9876);
//将随机数转成字符串形式
String answer = String.valueOf(result);
//数字小于三位数,重新生成。
if (result < 123) {
//递归调用
answer = makeAnswer();
}
//是三位数,需在结果前加 0
if (Utils.inRange(123, 987, result)) {
answer = "0" + answer;
}
//如果数字有重复,重新生成
if (Utils.hasRepeatNumber(answer)) {
//递归调用
answer = makeAnswer();
}
//将answer给到外面,以便其他方法使用
this.answer = answer;
return answer;
}
// 游戏猜中的回调
public interface OnSuccessListener {
void success();
}
private OnSuccessListener onSuccessListener;
public void setOnSuccessListener(OnSuccessListener onSuccessListener) {
this.onSuccessListener = onSuccessListener;
}
}
- Utils.java
//工具类
public class Utils {
/**
* 判断要选择的数字是否和已输入的数字重复
* @param s0 原有的数字串
* @param s1 新输入的数字
* @return
*/
public static boolean hasRepeatNumber(String s0, String s1) {
char[] chars = s0.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (chars[i] == s1.toCharArray()[0]) {
return true;
}
}
return false;
}
public static boolean inRange(int start, int end, int number) {
return Math.max(start, number) == Math.min(end, number);
}
public static boolean hasRepeatNumber(String s) {
HashSet set = new HashSet();
char[] chars = s.toCharArray();
int len = chars.length;
for (int i = 0; i < len; i++) {
set.add(chars[i]);
}
return set.size() != len;
}
}
- activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_input"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginTop="20dp"
android:gravity="center"
android:letterSpacing="1"
android:textSize="30dp"
app:layout_constraintBottom_toTopOf="@id/ll_input_numbers"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/bt_delete"
app:layout_constraintTop_toTopOf="parent"
tools:text="1234" />
<Button
android:id="@+id/bt_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DEL"
android:textSize="26dp"
app:layout_constraintBottom_toBottomOf="@id/tv_input"
app:layout_constraintLeft_toRightOf="@id/tv_input"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_input" />
<LinearLayout
android:id="@+id/ll_input_numbers"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="30dp"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_input">
<Button
android:id="@+id/number_0"
style="@style/NumberButton"
android:text="0" />
<Button
android:id="@+id/number_1"
style="@style/NumberButton"
android:text="1" />
<Button
android:id="@+id/number_2"
style="@style/NumberButton"
android:text="2" />
<Button
android:id="@+id/number_3"
style="@style/NumberButton"
android:text="3" />
<Button
android:id="@+id/number_4"
style="@style/NumberButton"
android:text="4" />
<Button
android:id="@+id/number_5"
style="@style/NumberButton"
android:text="5" />
<Button
android:id="@+id/number_6"
style="@style/NumberButton"
android:text="6" />
<Button
android:id="@+id/number_7"
style="@style/NumberButton"
android:text="7" />
<Button
android:id="@+id/number_8"
style="@style/NumberButton"
android:text="8" />
<Button
android:id="@+id/number_9"
style="@style/NumberButton"
android:text="9" />
</LinearLayout>
<Button
android:id="@+id/bt_guess"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Guess !"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/bt_again"
app:layout_constraintTop_toBottomOf="@id/ll_input_numbers" />
<Button
android:id="@+id/bt_again"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Again !"
app:layout_constraintLeft_toRightOf="@id/bt_guess"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/ll_input_numbers" />
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:paddingHorizontal="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/bt_guess">
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp"
tools:text="1A1B" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>