一、案例:逢7过
-
朋友聚会的时候可能会玩一个游戏:逢7过
-
游戏规则:从任意一个数字开始报数,当你要报的数字是包含 7 或者 是7的倍数时都要说过:过
-
需求: 使用程序在控制台打印出 1-100 之间的满足逢7必过规则的数据
-
分析:
-
条件:包含7(个位是7、十位是7),7的倍数
-
举例寻找思路:
从1开始数:1 2 3 4 5 6 过(1) 8 9 10 11 12 13 过(2) 15 16 过(3) 18 19 20 过(4) ... // (1)第一个过:是7,不能喊7,喊过 // (2)第二个过:是14,7的2倍,不能喊14,喊过 // (3)第三个过:是17,个位包含7,不能喊17,喊过 // (4)第四个过:是21,7的3倍,不能喊21,喊过 数到69:69 过 过 过 过... // 这些过:十位数包含7,所以不能直接喊出来,必须喊过
-
程序实现思路:
- 得到 1-100 之间的每一个数字
- 判断这 1-100 之间的 每一个数字,如果符合规则,就打印输出过,如果不符合规则就打印真实的数字
package com.app.test; public class LoopTest1 { public static void main(String[] args) { /* - 朋友聚会的时候可能会玩一个游戏:逢7过 - 游戏规则:从任意一个数字开始报数,当你要报的数字是包含 7 或者 是7的倍数时都要说过:过 - 需求:使用程序在控制台打印出 1-100 之间的满足逢7必过规则的数据 - 分析: - 条件:包含7(个位是7、十位是7),7的倍数 - 游戏规则举例说明: 从1开始数:1 2 3 4 5 6 过(1) 8 9 10 11 12 13 过(2) 15 16 过(3) 18 19 20 过(4) ... (1)第一个过:是7,不能喊7,喊过 (2)第二个过:是14,7的2倍,不能喊14,喊过 (3)第三个过:是17,个位包含7,不能喊17,喊过 (4)第四个过:是21,7的3倍,不能喊21,喊过 数到69:69 过 过 过 过... 这些过:十位数包含7,所以不能直接喊出来,必须喊过 - 程序实现思路: 1.得到 1-100 之间的每一个数字 2.判断每一数字,如果符合规则,就打印输出:过;如果不符合,就打印输出真实数字 */ // 1.得到 1-100 之间的每一个数字 // for循环得到 1-100 之间的每一个数字 for (int i = 1; i <= 100; i++) { // 2.判断每一数字,如果符合规则,就打印输出:过;如果不符合,就打印输出真实数字 // 在循环内,对每一个数字进行判断: // 第一个条件:判断个位是否为:7——>i/10 余 7 // 第二个条件:判断十位是否为:7——>假如i = 70, 70/10=7, 7%10 等价于 7/10=0.7 余数为 7 // 第三个条件:判断这个数是否为:7的倍数——>也就是这个数除以7后余数是否为0,为0就是过;比如:14%7 等价于 14/7=2 余数为0 if (i % 10 == 7 || i / 10 % 10 == 7 || i % 7 == 0) { // 是,符合规则,就打印输出 System.out.print("过\t"); // 跳出当前循环的当次执行,进入下一次循环 continue; } // 否,不符合,就打印输出真实数字 System.out.print(i + "\t"); } } }
输出结果: 1 2 3 4 5 6 过 8 9 10 11 12 13 过 15 16 过 18 19 20 过 22 23 24 25 26 过 过 29 30 31 32 33 34 过 36 过 38 39 40 41 过 43 44 45 46 过 48 过 50 51 52 53 54 55 过 过 58 59 60 61 62 过 64 65 66 过 68 69 过 过 过 过 过 过 过 过 过 过 80 81 82 83 过 85 86 过 88 89 90 过 92 93 94 95 96 过 过 99 100
-
二、案例:求平方根
-
需求: 键盘录入一个大于等于2的整数 x,计算并返回 x 的平方根。
-
结果只保留整数部分,小数部分将被舍去。
-
分析:
-
平方根:就是数学里的开根号
-
举例找思路:
16的平方根:4 4的平方根:2 1.比如:10的平方根是多少? 从1开始算: 1 * 1 = 1 < 10 2 * 2 = 4 < 10 3 * 3 = 9 < 10 4 * 4 = 16 > 10 推断:10的平方根是:3~4之间,只保留整数部分,舍去小数部分得:3 2.比如:20的平方根是多少? 1*1 = 1 < 20 2*2 = 4 < 20 3*3 = 9 < 20 4*4 = 16 < 20 5*5 = 25 > 20 推断:20的平方根是:4~5之间,只保留整数部分,舍去小数部分得:4
-
程序实现思路:
-
先创建一个键盘录入Scanner对象类——>用于录入一个大于等于2的整数。
-
定义一个死循环,用于当录入的整数不符合要求时,不需要中断程序执行,只需要对该录入的整数进行判断;
-
在死循环内,提示:录入整数,并定义一个变量来接收这个录入的整数。
-
在录入整数后,定义一个if判断:如果该整数>=2,则继续对这个整数进行计算判断;
如果该整数小于2,则提示:数据错误! 重新录入一个整数,直到该整数>=2,才会继续计算判断该整数。
-
定义一个循环,循环条件是 i 小于等于 录入的这个整数,然后让 i 自增。
-
定义一个if判断,如果符合平方根条件,将输出这个整数的平方根,并结束死循环;否则继续循环判断后面的数字,直到符合条件才会结束程序:
(1) 条件1: 如果该整数的计算结果 == 录入的整数,将输出这个整数的平方根,并结束死循环;
(2) 条件2: 如果该整数的计算结果 > 录入的整数,将输出这个整数的平方根,并结束死循环;
(3) 条件3: 条件3: 如果该整数的计算结果 < 录入的整数,将继续循环判断后面的数字,直到符合条件才会结束程序执行。
package com.app.test; import java.util.Scanner; public class LoopTest2 { public static void main(String[] args) { /* 案例:求平方根 - 需求:键盘录入一个大于等于2的整数 x,计算并返回 x 的平方根。 - 结果只保留整数部分,小数部分将被舍去。 - 分析: - 平方根:就是数学里的开根号 - 举例找思路: 16的平方根:4 4的平方根:2 1.比如:10的平方根是多少? 从1开始算: 1 * 1 = 1 < 10 2 * 2 = 4 < 10 3 * 3 = 9 < 10 4 * 4 = 16 > 10 推断:10的平方根是:3~4之间,只保留整数部分,舍去小数部分得:3 2.比如:20的平方根是多少? 1*1 = 1 < 20 2*2 = 4 < 20 3*3 = 9 < 20 4*4 = 16 < 20 5*5 = 25 > 20 推断:20的平方根是:4~5之间,只保留整数部分,舍去小数部分得:4 */ // 1.创建一个键盘录入Scanner对象类——>用于录入一个大于等于2的整数 Scanner sc = new Scanner(System.in); OUT: // OUT: 用于最后结束死循环 // 2.定义一个死循环,提示:录入整数,并定义一个变量来接收这个录入的整数。 while (true) { // 3.在死循环内,提示:录入整数,并定义一个变量来接收这个录入的整数 System.out.println("请输入一个整数(该数>=2): "); int number = sc.nextInt(); // 4.在录入整数后,定义一个if判断:如果该整数>=2,则继续对这个整数进行计算判断; if (number >= 2) { // 5. 定义一个循环,循环条件是 i 小于等于 录入的整数,然后让 i 自增 for (int i = 1; i <= number; i++) { // 6. 定义一个if判断,如果符合平方根条件,将输出这个整数的平方根,并结束死循环;否则继续循环判断 后面的数字,直到符合条件才会结束程序: // 条件1: 如果该整数的计算结果 == 录入的整数,将输出这个整数的平方根,并结束死循环; // 条件2: 如果该整数的计算结果 > 录入的整数,将输出这个整数的平方根,并结束死循环; // 条件3: 如果该整数的计算结果 < 录入的整数,将继续循环判断后面的数字,直到符合条件才会结束程序 执行。 if (i*i == number) { System.out.println("整数" + number + "的平方根是:" + i); // 结束死循环 break OUT; // break OUT: 用于结束整个死循环 }else if (i*i > number){ // i-1表示: 结果只保留整数部分,小数部分将被舍去 System.out.println("整数" + number + "的平方根是:" + (i-1)); break OUT; } } } // 如果该整数小于2,则提示:数据错误! 重新录入一个整数,直到该整数>=2,才会继续计算判断该整数 System.out.println("录入数据错误!"); } } }
输出结果(便于读者理解,代码中不存在该输出语句): ------------------------------------------------- 测试1(方便读者理解,代码中不存在该输出语句): 请输入一个整数(该数>=2): 0 录入数据错误! 请输入一个整数(该数>=2): 1 录入数据错误! 请输入一个整数(该数>=2): 10 整数10的平方根是:3 ------------------------------------------------- 测试2(方便读者理解,代码中不存在该输出语句): 请输入一个整数(该数>=2): 20 整数20的平方根是:4 ------------------------------------------------- 测试3(方便读者理解,代码中不存在该输出语句): 请输入一个整数(该数>=2): 16 整数16的平方根是:4
-
-
三、案例:求质数
-
需求: 键盘录入一个正整数,判断该整数是否为一个质数
-
分析:
-
质数:就是除了 1 和 它本身 之外,再也没有整数能被它整除的数
-
举例寻找思路:
7 = 1*7 // 质数 8 = 1*8, 2*4 // 不是质数,是合数 9 = 1*9, 3*3 // 不是质数,是合数
-
程序实现思路:
- 创建一个键盘录入Scanner对象类,用于录入一个正整数。
- 定义一个死循环,用于录入正整数后,做判断后可以不中断程序执行,可以继续判断。
- 在死循环内,开始录入,提示:开始录入一个正整数,并定义一个变量用于接收该录入的正整数。
- 程序员标记思想(重点) ——>随后定义一个标记,一开始就认为录入的正整数是质数。
- 录入正整数后,对该录入的正整数做判断,如果录入的数 > 0,将继续判断;否则,提示:否则提示:数据错误!——>将重新录入。
- 如果录入的数 > 0,判断:该数是否等于1,等于,提示:该数既不是质数,也不是合数。但是不结束程序的执行(提示:continue),只跳出当前循环的当次执行,进入下一次循环。
- 如果该数不等于1,则:定义一个循环,从2开始判断,范围是小于 录入的正整数。
- 在循环内,判断:看这个范围内,有没有数字可以被 录入的正整数整除的,如果有,标记为false,表示不是质数,并跳出循环,结束程序执行。
- 当循环结束了,表示这个范围内所有的数字都判断完毕了,此时才能判断标记,为true是质数;并跳出循环,结束程序执行。否则为false,不是质数,是合数;并跳出循环,结束程序执行。
package com.app.test; import java.util.Scanner; public class LoopTest3 { public static void main(String[] args) { /* - 需求: 键盘录入一个正整数,判断该整数是否为一个质数 - 分析: - 质数: 就是除了 1 和 它本身 之外,再也没有整数能被它整除的数 - 举例寻找思路: 7 = 1*7 // 质数 8 = 1*8, 2*4 // 不是质数,是合数 9 = 1*9, 3*3 // 不是质数,是合数 */ // 1. 创建一个键盘录入Scanner对象类,用于录入一个正整数 Scanner sc = new Scanner(System.in); // 2.定义一个死循环,用于录入正整数后,做判断后可以不中断程序执行,可以继续判断。 while (true) { // 3.在死循环内,开始录入,提示:开始录入一个正整数,并定义一个变量用于接收该录入的正整数。 System.out.println("请您录入一个正整数(该数>=1): "); int number = sc.nextInt(); // 4.程序员标记思想(重点)——>随后定义一个标记,一开始就认为录入的正整数是质数 boolean flag = true; // 5.录入正整数后,对该录入的正整数做判断,如果录入的数 > 0,将继续判断; if (number > 0){ // 6.如果录入的数 > 0,判断:该数是否等于1, if (number == 1) { // 等于,提示:该数既不是质数,也不是合数。但是不结束程序的执行(提示:continue), System.out.println("正整数" + number + "既不是质数,也不是合数"); // 只跳出当前循环的当次执行,进入下一次循环。 continue; } // 7.如果该数不等于1,则:定义一个循环,从2开始判断,范围是小于 录入的正整数。 for (int i = 2; i < number; i++) { // 8.在循环内,判断:看这个范围内,有没有数字可以被 录入的正整数整除的, if (number % i == 0){ // 如果有,标记为false,表示不是质数, flag = false; // 并跳出循环,结束程序执行 break; } } // 9.当循环结束了,表示这个范围内所有的数字都判断完毕了,此时才能判断标记, if (flag) { // 为true是质数; System.out.println("正整数" + number + "是质数"); // 并跳出循环,结束程序执行 break; }else { // 否则为false,不是质数,是合数 System.out.println("正整数" + number + "是合数"); // 并跳出循环,结束程序执行 break; } } // 否则提示:数据错误!——>将重新录入。 System.out.println("数据错误!"); } } }
输出结果(便于读者理解,代码中不存在该输出语句): ------------------------------------------ 测试1(便于读者理解,代码中不存在该输出语句): 请您录入一个正整数(该数>=1): 0 数据错误! 请您录入一个正整数(该数>=1): -100 数据错误! 请您录入一个正整数(该数>=1): 1 正整数1既不是质数,也不是合数 请您录入一个正整数(该数>=1): 2 正整数2是质数 ------------------------------------------ 测试2(便于读者理解,代码中不存在该输出语句): 请您录入一个正整数(该数>=1): 7 正整数7是质数 ------------------------------------------ 测试3(便于读者理解,代码中不存在该输出语句): 请您录入一个正整数(该数>=1): 9 正整数9是合数 ------------------------------------------
-
简化思路
- 只需要明白这样简化代码的思路即可,无需写出代码,无需执行,因为往后的知识,会讲解到如何直接获取到一个数的平方根
package com.app.test;
public class LoopTest4 {
public static void main(String[] args) {
// 简化思路:
// 例如:求 100000 是不是质数
// 如果还是用之前的求质数的代码来验证的话,循环次数:将近10万次
// 如果用简化的思路
// 例如:81,不是质数,因为:
// 1 * 81, 3 * 27, 9 * 9
/*
那么以81的平方根9,为中心
而且假设 a*b = 81
那么a 和 b 中,其中有一个必定是小于等于9,另一个必定是大于等于9
为什么?
假设,都是大于9 --- 9.1 * 9.1 必定 > 81
假设,都是小于9 --- 8.9 * 8.9 必定 < 81
结论:
其中一个数字必定是小于等于平方根
其中一个数字必定是大于等于平方根
*/
// 所以,100000 的平方根 用计算器算出是:316
int number = 100000;
/*
如果这个范围内,所有数字都不能被number整除
那么number就一定是质数
i 小于 number的平方根,因此,只需要循环 316次,代码得到了进一步的简化
*/
/*for (int i = 2; i < number的平方根; i++) {
}*/
}
}
四、案例:猜数字小游戏
-
需求: 程序自动生成一个 1~100 之间的随机数字,使用程序实现猜出这个数字是多少?
-
分析:
-
获取随机数:Java帮我们写好了一个类叫做Random,这个类可以生成一个随机数。
(1) 导入Random包——>Random这个类在哪里
// 导包的操作必须出现在类定义的上边 import java.util.Random;
(2) 创建Random对象——>表示我们要开始使用这个类了
// 这个格式,只有rd是变量名,可以变,其他的不允许乱改 Random rd = new Random();
(3) 定义变量接收生成的随机数——>开始干活了
// 这个格式,只有number是变量名,可以变,其他的不允许乱改 int number = rd.nextInt(随机数范围);
-
做完以上操作后,输出这个随机数即可
package com.app.test; // 1.导入Random包 import java.util.Random; public class LoopTest5 { public static void main(String[] args) { /* 此程序告诉你,如何生成任意数到任意数之间的随机数 */ // 2.创建Random随机数对象 Random rd = new Random(); // 减加法秘诀: // 用来生成任意数到任意数之间的随机数:比如7~15之间的随机数 // 1.让这个范围的头尾部都减去 头部的值,让这个范围从0开始:7~15都减头部值后得——>0~8 // 2.再让尾部+1——>8+1=9 // 3.最终的结果,再加上第一步减去的值 // int number = rd.nextInt(9) + 7; // 表示生成7~15之间的随机数 // 3.为了方便判断有没有生成1-100之间的随机数,定义个循环来扩大生成范围 for (int i = 0; i < 120; i++) { // 4.调用随机数功能,随机生成一个1-100之间的随机数 int number = rd.nextInt(100) + 1; // 也可写成 // int number = rd.nextInt(1, 101); System.out.println(number); } } }
输出结果: 37 27 79 96 1 89 46 48 69 54 42 64 49 12 84 88 35 75 58 82 28 31 64 25 94 75 20 28 20 27 37 88 81 18 68 42 75 93 90 100 72 49 27 26 65 55 31 37 59 35 38 16 7 78 46 71 49 90 85 9 20 57 11 13 62 32 45 57 83 58 47 96 29 16 57 35 79 69 48 67 10 88 45 5 93 30 35 86 5 86 30 74 25 31 40 5 81 78 63 61 38 17 54 79 45 31 53 5 40 5 80 13 38 87 3 73 68 85 72 95
-
-
如果你想要1-100之间,不要100本身这个随机数,那就不用+1,如果你想要100这个随机数,就必须用减加法来取尾数,如果不记得减加法,就看看之前的随机数文章,有说过细节!
-
实现猜数字小游戏
-
创建随机数Random对象
-
调用随机数功能,生成一个1-100之间的随机数,作为中奖号码
-
猜这个数据是多少?
(3-1) 定义个死循环,让大家不断猜数,猜中才会跳出循环,结束游戏
(3-2) 在循环外,创建一个键盘录入Scanner对象,用于录入大家猜的数字
(3-3) 调用键盘录入功能,开始录入,用变量接收大家猜的数字
(3-4) 定义if判断:
如果猜的数字 小于等于0 或者 大于100 ,则提示:”数据错误!“,重新猜数,猜中为止;
如果猜的数据 符合 1-100 范围内,则继续判断:
假如大家猜的数 等于 中奖数,则提示:“猜中了~恭喜!”,并跳出循环,结束程序;
假如大家猜的数 大于 中奖数,则提示:“数大了!继续~”;
假如大家猜的数 小于 中奖数,则提示:“数小了!继续~”。
-
package com.app.test;
import java.util.Random;
import java.util.Scanner;
public class LoopTest6 {
public static void main(String[] args) {
/*
案例:猜数字小游戏
- 需求:程序自动生成一个 1~100 之间的随机数字,使用程序实现猜出这个数字是多少?
*/
// 1.创建随机数对象
Random rd = new Random();
// 2.调用随机数功能,生成1-100之间的随机数
int number = rd.nextInt(100) + 1;
// 3.猜这个随机数字是多少?
// (3-2) 在循环外创建键盘录入对象,用于输入你要猜的数字
Scanner sc = new Scanner(System.in);
// (3-1) 定义个死循环,让你不断猜数,猜中才会跳出循环,结束游戏
while (true) {
// (3-3) 调用键盘录入功能,开始录入,并用变量接收你猜的数字
System.out.println("请您猜个数(1-100): ");
int guessNumber = sc.nextInt();
// (3-4) if判断你输入的数字
if (guessNumber <= 0 || guessNumber > 100) {
// 假如输入的数小于等于0 或者 大于100,提示:错误猜数!
System.out.println("数据错误!");
}else {
// (3-5) if判断你要猜的数字:
if (guessNumber == number) { // 假如等于答案的数字,提示:猜中了~恭喜!
System.out.println("猜中了~恭喜!");
// 并跳出循环,结束程序
break;
}else if (guessNumber > number){ // 假如比答案大了,提示:数大了!继续~
System.out.println("数大了!继续~");
}else { // 假如比答案小了,提示:数小了!继续~
System.out.println("数小了!继续~");
}
}
}
}
}
输出结果:
请您猜个数(1-100):
0
数据错误!
请您猜个数(1-100):
101
数据错误!
请您猜个数(1-100):
100
数大了!继续~
请您猜个数(1-100):
50
数小了!继续~
请您猜个数(1-100):
60
数小了!继续~
请您猜个数(1-100):
70
数大了!继续~
请您猜个数(1-100):
65
数大了!继续~
请您猜个数(1-100):
64
猜中了~恭喜!
五、案例:抽奖保底机制
-
需求: 假设游戏场景:用点券抽水晶,保底次数是:3次。抽奖过程中,如果玩家抽奖3次猜不中,直接提示:“恭喜玩家~抽中1枚荣耀水晶!”。
-
分析:
-
创建随机数Random对象,用于生成一个1-20之间的随机数
-
调用随机数功能, 生成一个1-20之间的随机数,作为抽中奖励
-
定义一个死循环,让玩家不断抽奖,直到抽中才会跳出循环,结束抽奖
-
在死循环外,定义一个计数器变量,用于记录玩家抽奖次数
-
在死循环外,创建键盘录入Scanner对象,用于给玩家抽奖
-
在死循环内,开始抽奖,用变量接收玩家的抽奖号码
-
玩家每一次抽奖后,累加抽奖次数,判断:当玩家的未抽中次数达到3次时,直接提示:“恭喜玩家~抽中1枚荣耀水晶!”,并跳出循环,结束抽奖。
-
判断:如果玩家直接抽中,提示:“恭喜玩家~抽中1枚荣耀水晶!”,并跳出循环,结束抽奖;
如果玩家没有抽中,提示:“可惜了~ 继续加油~”,累加玩家抽奖次数;
-
package com.app.test;
import java.util.Random;
import java.util.Scanner;
public class LoopTest7 {
public static void main(String[] args) {
// 扩展
/*
案例:抽奖机制
- 需求:假设游戏场景:用点券抽水晶,保底次数是:3次。抽奖过程中,如果玩家抽奖3次猜不中,直接提示:“恭喜玩家~抽中1枚荣耀水晶!”。
- 分析:
1. 创建随机数Random对象,用于生成一个1-20之间的随机数
2. 调用随机数功能, 生成一个1-20之间的随机数,作为抽中奖励
3. 定义一个死循环,让玩家不断抽奖,直到抽中才会跳出循环,结束抽奖
4. 在死循环外,定义一个计数器变量,用于记录玩家抽奖次数
5. 在死循环外,创建键盘录入Scanner对象,用于给玩家抽奖
6. 在死循环内,开始抽奖,用变量接收玩家的抽奖号码
7. 开始判断,如果玩家直接抽中,提示:“恭喜玩家~抽中1枚荣耀水晶!”,并跳出循环,结束抽奖;
如果玩家没有抽中,提示:“可惜了~继续加油~”,累加玩家抽奖次数;
判断:当玩家的未抽中次数达到3次时,直接提示:“恭喜玩家~抽中1枚荣耀水晶!”,并跳出循环,结束抽奖。
*/
// 1. 创建随机数Random对象,用于生成一个1-20之间的随机数
Random rd = new Random();
// 2. 调用随机数功能, 生成一个1-20之间的随机数,作为抽中奖励
int number = rd.nextInt(20) + 1;
// 4. 在死循环外,定义一个计数器变量,用于记录玩家抽奖次数
int count = 0;
// 5. 在死循环外,创建键盘录入Scanner对象,用于给玩家抽奖
Scanner sc = new Scanner(System.in);
// 3. 定义一个死循环,让玩家不断抽奖,直到抽中才会跳出循环,结束抽奖
while (true) {
// 6. 在死循环内,开始抽奖,用变量接收玩家的抽奖号码
System.out.println("请玩家开始抽奖(1-20): ");
int guessNumber = sc.nextInt();
// 7.玩家每一次抽奖后,累加抽奖次数,
count++;
// 判断:当玩家的未抽中次数达到3次时,直接提示:“恭喜玩家~抽中1枚荣耀水晶!”,
if (count == 3) {
System.out.println("恭喜玩家~抽中1枚荣耀水晶!");
// 并跳出循环,结束抽奖。
break;
}
// 8. 开始判断,如果玩家直接抽中,提示:“恭喜玩家~抽中1枚荣耀水晶!”,
if (guessNumber == number) {
System.out.println("恭喜你~抽中1枚荣耀水晶!");
// 并跳出循环,结束抽奖;
break;
}else if (guessNumber > number) {
// 如果玩家没有抽中,提示:“可惜了~继续加油~”,
System.out.println("可惜了~继续加油~");
}else {
// 如果玩家没有抽中,提示:“可惜了~继续加油~”,
System.out.println("可惜了~继续加油~");
}
}
}
}
输出结果:
请玩家开始抽奖(1-20):
18
可惜了~继续加油~
请玩家开始抽奖(1-20):
15
可惜了~继续加油~
请玩家开始抽奖(1-20):
10
恭喜玩家~抽中1枚荣耀水晶!
- 可以看到,不管玩家抽中还是抽不中,只要抽奖次数达到3次,就保底给1枚荣耀水晶!