Part 1.结对同学
-
我们本次结对作业选择的是做安卓端,代码及UI测试请移步 Github
Part 2.具体分工
杨润秋:负责前端界面和网络接口调用代码书写及最后的整合及打包
韩洪威:负责后端AI算法部分代码书写、优化、调试
Part 3.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 3100 | 3400 |
Analysis | · 需求分析(包括学习新技术) | 500 | 600 |
Design Spec | · 生成设计文档 | 20 | 20 |
Design Review | · 设计复审 | 10 | 10 |
Coding Standard | · 代码规范(为开发制定合适的规范) | 30 | 30 |
Design | · 具体设计(用伪代码,流程图等方法来设计具体模块) | 20 | 30 |
Coding | · 具体编码 | 2400 | 2600 |
Code Review | · 代码复审 | 60 | 40 |
Test | 测试(自我测试,修改,提交修改) | 60 | 70 |
Reporting | 报告 | 60 | 60 |
Test Report | · 测试报告 | 30 | 30 |
Size Measurement | · 计算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | · 事后总结并提出过程改进计划 | 20 | 20 |
合计 | 3190 | 3490 |
Part 4. 解题思路描述与设计实现说明
-
思路说明(前端):
-
首先就是UI的设计,由于事先做过了原型设计,所以前端的UI设计就变得轻松许多。前端开发主要使用Android Studio进行开发安卓App。通过Android Studio设计UI界面可以使用拖拽的方法,所以对代码的要求不是很高。
-
完成了UI设计,接下来就是实现各个界面之间的通信功能,这几我采用的方法是每一个界面都设计一个Activity(实际上这样的设计方式比较繁琐,后续的优化可以改进为使用碎片化设计Fragment)。
-
与服务器的通信功能做的比较常规,就是正常的向服务器发送请求然后处理Response消息,例如登录和注册界面,只需要判断message是否为Success即可。需要注意的是如果属性变量等处理不当经常容易出现空指针异常而导致程序闪退的现象,想要做到处理好各种异常还有很多地方的代码需要进一步的优化。
-
-
思路说明(后端):
- 在十三水作业发布前就知道了隔壁班的这个作业,不过那时候也没有仔细去研究,作业发布后看着题目陷入了深思,因为一时也不知道算法该从何入手,后来慢慢研究后觉得一共也就十三张牌,不是很多,那可以使用枚举的方法,将所有的情况枚举出来,再进行比较选出认为比较大的牌型,而且对前墩、中墩枚举,剩下得到就是后墩,一共有C(13,3)xC(10,5)xC(5,5)一共就7万多种情况,时间复杂度上也符合要求,于是就顺着这个思路写下去了。
-
网络接口的使用:
网络接口的使用主要通过Retrofit2,可以把请求Request和返回的Response都封装为一个一个独立的类,Body的Key设置为类中的属性,这样发送和接收的时候就可以更加的方便。
以下是实现的部分代码:
-
类图:
- 算法关键部分:
关键部分主要还是在枚举牌型及权值比较这一部分
关键部分流程图:
Part 5:关键代码解释
private static float balance(String[] str,int level){//权值细化值计算
float x=0;
int max=0;
int[] a=new int[13];
switch (level){
case 1:
case 6:
case 7:
case 10:
for(int i=0;i<str.length;i++){
if(max<map.get(str[i].charAt(1))){
max=map.get(str[i].charAt(1));
}
}
x=(float)(max-1)/13;
break;
case 2:
case 3:
case 4:
for(int i=0;i<str.length;i++){
a[map.get(str[i].charAt(1))]++;
}
for(int i=0;i<=12;i++){
if(a[i]==2){
if(max<i)max=i;
}
}
x=(float)(max-1)/13;
break;
case 5:
case 8:
for(int i=0;i<str.length;i++){
a[map.get(str[i].charAt(1))]++;
}
for(int i=0;i<=12;i++){
if(a[i]==3){
if(max<i)max=i;
}
}
x=(float)(max-1)/13;
break;
case 9:
for(int i=0;i<str.length;i++){
a[map.get(str[i].charAt(1))]++;
}
for(int i=0;i<=12;i++){
if(a[i]==4){
if(max<i)max=i;
}
}
x=(float)(max-1)/13;
break;
}
return x;
}
private static void calcuWater(String c1,String c2,String c3) throws IOException {
//权值比较
int level1,level2,level3;
c1=c1.substring(0,8);
c2=c2.substring(0,14);
c3=c3.substring(1,15);
String[] str1=c1.split(" ");
String[] str2=c2.split(" ");
String[] str3=c3.split(" ");
level1=judgeLevel(str1);
level2=judgeLevel(str2);
level3=judgeLevel(str3);
if(compare(str1,str2,level1,level2,1,2)==1)return;
if(compare(str2,str3,level2,level3,2,3)==1)return;
if(level1+level2+level3>snum1+snum2+snum3){
replace(str1,level1,1);
replace(str2,level2,2);
replace(str3,level3,3);
}
else if(level1+level2+level3==snum1+snum2+snum3){
float g=balance(str1,level1)+balance(str2,level2)+balance(str3,
level3)+(float)(level1-1)/10+(float)(level2-1)/10+(float)(level3-1)/10;
float h=
balance(s1,snum1)+balance(s2,snum2)+balance(s3,snum3)+(float)(snum1-1)/10+(float)(snum2-1)/10+(float)(snum3-1)/10;
if(g>h){
replace(str1,level1,1);
replace(str2,level2,2);
replace(str3,level3,3);
}
}
}
private static void enumerate(String[] str)throws IOException{
//枚举所有牌型
String s=" ";
for(int i=0;i<=12;i++){
s=s+str[i].substring(0,2);
s+=" ";
}
int a1=0,a2=1,a3=2;
int b1=0,b2=1,b3=2,b4=3,b5=4;
while(a1<=10){
String ss=s;
String c1=ss.substring(a1*3+1,(a1+1)*3+1)+ss.substring(a2*3+1,
(a2+1)*3+1)+ss.substring(a3*3+1,(a3+1)*3+1);
b1=0;
b2=1;
b3=2;
b4=3;
b5=4;
while(b1<=5){
String sss=ss.substring(0,a1*3+1)
+ss.substring((a1+1)*3+1, a2*3+1)
+ss.substring((a2+1)*3+1,a3*3+1)
+ss.substring((a3+1)*3+1,40);
String c2= sss.substring(b1*3+1,(b1+1)*3+1)
+sss.substring(b2*3+1,(b2+1)*3+1)
+sss.substring(b3*3+1,(b3+1)*3+1)
+sss.substring(b4*3+1,(b4+1)*3+1)
+sss.substring(b5*3+1,(b5+1)*3+1);
String c3=sss.substring(0,b1*3+1)
+sss.substring((b1+1)*3+1,b2*3+1)
+sss.substring((b2+1)*3+1,b3*3+1)
+sss.substring((b3+1)*3+1,b4*3+1)
+sss.substring((b4+1)*3+1,b5*3+1)
+sss.substring((b5+1)*3+1,31);
calcuWater(c1,c2,c3);
//if(b1>=5)break;
if(b5==9){
if(b4!=8){
b4++;
b5=b4+1;
}
else{
if(b3!=7){
b3++;
b4=b3+1;
b5=b4+1;
}
else{
if(b2!=6){
b2++;
b3=b2+1;
b4=b3+1;
b5=b4+1;
}
else{
b1++;
b2=b1+1;
b3=b2+1;
b4=b3+1;
b5=b4+1;
}
}
}
}
else b5++;
}
if(a3==12){
if(a2!=11){
a2++;
a3=a2+1;
}
else{
a1++;
a2=a1+1;
a3=a2+1;
}
}
else a3++;
}
}
最重要的代码就是这部分的代码,分别是枚举牌型,权值比较,细化权值计算,通过遍历所有的牌型将认为最大的牌型选出来并赋值到对应变量中,返回到相应类中
Part 6.性能分析与改进
-
改进思路
- 第一次也没什么思路,就憨憨的一个个按照十三水的规则算多少水,遍历下去取最大的,然后兴致冲冲拿去跑,结果很明显,因为不低于零的系统设置,所以没有负分,靠着运气偶尔有几十分,但从没有上过一百。
- 经过几天后,就将原来的代码删去,想了想于是选择使用权值比较的方法,将十种普通牌型赋予1-10的价值,通过比较价值来判断牌型,选择权值较大的牌型,然后又拿去跑,这次倒是跑到了更高一点的分数,不过也就仅此而已。
- 后来在观察历史对局详情时,突然发现了一种情况,比如“7 4 3,5 5 8 8 K,A 5 5 5 5”这种牌型如果按照这样“A K 7,5 5 8 8 4,3 5 5 5 5”,很明显后者会获得获得的收益更大,因为前墩是散牌的概率还是很大的,这样得分的可能性很明显更高,于是按照这个思路又改了一次算法,就得到了现在的算法
- 在后来算法优化过程中又发现即使是特殊牌型也需要分墩,而且还要满足前墩小于中墩,中墩小于后墩,所以又删去了特殊牌型判断,又减少了代码的代码量
-
性能分析图
-
消耗最大的函数
消耗最大的函数就是这个需要枚举所有牌型的函数
private static void enumerate(String[] str)throws IOException{
String s=" ";
for(int i=0;i<=12;i++){
s=s+str[i].substring(0,2);
s+=" ";
}
//System.out.println("s="+s);
int a1=0,a2=1,a3=2;
int b1=0,b2=1,b3=2,b4=3,b5=4;
while(a1<=10){
//System.out.println("a1="+a1+" "+"a2="+a2+" "+"a3="+a3);
String ss=s;
String c1=ss.substring(a1*3+1,(a1+1)*3+1)+ss.substring(a2*3+1,
(a2+1)*3+1)+ss.substring(a3*3+1,(a3+1)*3+1);
b1=0;
b2=1;
b3=2;
b4=3;
b5=4;
while(b1<=5){
String sss=ss.substring(0,a1*3+1)
+ss.substring((a1+1)*3+1, a2*3+1)
+ss.substring((a2+1)*3+1,a3*3+1)
+ss.substring((a3+1)*3+1,40);
String c2= sss.substring(b1*3+1,(b1+1)*3+1)
+sss.substring(b2*3+1,(b2+1)*3+1)
+sss.substring(b3*3+1,(b3+1)*3+1)
+sss.substring(b4*3+1,(b4+1)*3+1)
+sss.substring(b5*3+1,(b5+1)*3+1);
String c3=sss.substring(0,b1*3+1)
+sss.substring((b1+1)*3+1,b2*3+1)
+sss.substring((b2+1)*3+1,b3*3+1)
+sss.substring((b3+1)*3+1,b4*3+1)
+sss.substring((b4+1)*3+1,b5*3+1)
+sss.substring((b5+1)*3+1,31);
calcuWater(c1,c2,c3);
//if(b1>=5)break;
if(b5==9){
if(b4!=8){
b4++;
b5=b4+1;
}
else{
if(b3!=7){
b3++;
b4=b3+1;
b5=b4+1;
}
else{
if(b2!=6){
b2++;
b3=b2+1;
b4=b3+1;
b5=b4+1;
}
else{
b1++;
b2=b1+1;
b3=b2+1;
b4=b3+1;
b5=b4+1;
}
}
}
}
else b5++;
}
//if(a1>=10)break;
if(a3==12){
if(a2!=11){
a2++;
a3=a2+1;
}
else{
a1++;
a2=a1+1;
a3=a2+1;
}
}
else a3++;
}
}
Part 7.单元测试
测试数据:
数据中包含了所有的特殊牌型和一些其他的各种牌型
&10 &2 &3 &4 &5 &6 &7 &8 &9 &J &K &Q &A
&A *3 #2 *J *Q #K $9 &8 *6 $7 #10 $5 *4
&J $J *J #J *K #K *Q #Q *10 $Q $K &Q &K
#K &6 *3 #Q &3 #J &5 #10 *4 &4 #9 *5 &7
$J *5 *6 #J &5 *J &6 &10 #5 &J #6 $5 $6
*10 #10 $J #9 $Q *A $A $K #K *J $9 *K $8
*2 #2 $4 #5 $5 *4 $6 $7 #6 *8 $3 *3 $8
$A $3 *4 *A *J *K *Q $10 *5 $8 $K $Q *9
*4 $4 #4 *10 &10 $10 *9 &9 *J $J #Q $Q $6
*2 #2 $2 *6 $6 #6 &10 *7 $7 &7 #J *J $J
*5 #5 *3 &3 *2 &2 *A &A #J &J &10 *10 #10
*5 #5 *3 &3 *2 &2 *A &A #J &J &10 *10 &9
*2 &3 $4 *4 &5 #6 $7 *8 *9 &10 &J $Q $K
*2 *5 *9 #4 #6 #10 #K #A &9 &4 &5 &J &2
#7 $A *A *9 *10 #J $Q *K $2 $3 $5 $7 $K
*9 *10 &Q #8 &K #K $A #A $2 &2 #2 &6 *6
&J #J &6 $10 #8 *8 *3 $3 &Q #Q *Q &5 $5
&Q $Q *2 &8 #7 &6 #5 #4 #A #Q #J #6 #3
单元测试代码:
单元测试代码选择的是判断权值大小的函数部分
由于代码比较长,这里只贴出一个调用其他函数的总函数,展示了算法的思路
private static void calcuWater(String c1,String c2,String c3) throws IOException {
int level1,level2,level3;
c1=c1.substring(0,8);
c2=c2.substring(0,14);
c3=c3.substring(1,15);
String[] str1=c1.split(" ");
String[] str2=c2.split(" ");
String[] str3=c3.split(" ");
level1=judgeLevel(str1);
level2=judgeLevel(str2);
level3=judgeLevel(str3);
if(compare(str1,str2,level1,level2,1,2)==1)return;
if(compare(str2,str3,level2,level3,2,3)==1)return;
if(level1+level2+level3>snum1+snum2+snum3){
replace(str1,level1,1);
replace(str2,level2,2);
replace(str3,level3,3);
}
else if(level1+level2+level3==snum1+snum2+snum3){
float g=balance(str1,level1)+balance(str2,level2)+balance(str3,
level3)+(float)(level1-1)/10+(float)(level2-1)/10+(float)(level3-1)/10;
float h=
balance(s1,snum1)+balance(s2,snum2)+balance(s3,snum3)+(float)(snum1-1)/10+(float)(snum2-1)/10+(float)(snum3-1)/10;
if(g>h){
replace(str1,level1,1);
replace(str2,level2,2);
replace(str3,level3,3);
}
}
运行结果:
Part 8.代码签入记录
(其中一部分截图,所有信息请移步 Github自行查看)
Part 9.遇到的问题及解决办法
- 问题描述
- 没有调用过api,不知道如何实现api接口调用
- 向api提交出牌申请时会返回报错
- apk文件打包出来安装后网络请求无响应
- 做过哪些尝试
- 有问题当然先百度,不行再问其他的同学
- 是否解决
- 已解决,时api请求时方式和参数有问题
- 已解决,是墩牌不合法的原因
- 已解决,因为手机Android版本是Android 9,需要添加其他代码
- 有何收获
- 懂得如何调用网络接口,同时也明白了代码规范命名很重要,同学分工合作写起代码确实轻松多了
Part 10.评价你的队友
- 值得学习的地方
队友很强,前端完全不用有任何担心,只需要安心敲我的后端算法然后交给他就行,希望能学到前端的这些东西 - 需要改进的地方
没什么需要改进的地方,如果有,那就是没能拖着我直接飞
Part 11.学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | 3 | 3 | 学习Axure RP,了解原型设计 |
2-4 | 3500 | 3500 | 40 | 43 | 算法设计、优化、网络接口调用 |
... |