一、链接集合
二、具体分工
-
丁枢桐:AI算法设计及编程,前端设计指导,博客撰写
-
张凌昕:前端设计及网络接口的编写
三、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(min) | 实际耗时(min) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
Estimate | 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | 1200 | 1500 |
Analysis | 需求分析(包括学习新技术) | 400 | 600 |
Design Spec | 生成设计文档 | 20 | 10 |
Design Review | 设计复审 | 30 | 60 |
Coding Standard | 代码规范(为开发制定合适的规范) | 30 | 40 |
Design | 具体设计 | 400 | 600 |
Coding | 具体编码 | 1200 | 1500 |
Code Review | 代码复审 | 60 | 120 |
Test | 测试(自我测试,修改,提交修改) | 60 | 240 |
Reporting | 报告 | 60 | 60 |
Test Report | 测试报告 | 60 | 60 |
Size Measurement | 计算工作量 | 20 | 10 |
Postmortem & Process Improvement Plan | 事后总结并提出过程改进计划 | 60 | 60 |
Total | 合计 | 3720 | 4980 |
四、解题思路描述与设计实现说明
-
网络接口的使用
我根据永福给的几个前端的接口,还有get,post参数,写了接永福服务器的接口写了接口,然后进行数据的发送和获取数据并加以展示,将他的所给的牌将牌给储存下来。再和后端交接,把后端所给的字符串交给永福的服务器。我选择我连的一部分接口来详细描述。
-
注册接口
接入注册接口的代码,根据用户所填写的用户名,学号,密码,教务处 ,密码,将所填写的内容传递到服务器,并且根据服务器返回的内容,来提醒用户。
function register() {
var reg_data = ($('#reg_form').serializeJson());
if (reg_data.username == undefined || reg_data.password == undefined) {
alert("用户名或密码不能为空");
return;
}
$.ajax({
type: "POST",
dataType: "json",
url: "https://api.shisanshui.rtxux.xyz/auth/register",
data: JSON.stringify(reg_data), //提交的数据
contentType: "application/json;charset-UTF-8",
success: function (result) { //todo
console.log(result); //打印服务端返回的数据(调试用)
if (result.status == 0) {
alert("注册成功");
window.location.href = '登入.html'
};
},
error: function (jqXHR, textStatus, errorThrownt) {
var responseText = jQuery.parseJSON(jqXHR.responseText);
if (responseText.status == 1001) {
alert("用户名已被使用");
};
if (responseText.status == 1002) {
alert("学号已绑定");
};
if (responseText.status == 1003) {
alert("教务处认证失败");
}
}
});
}
-
接入登录接口的代码
function begin() {
var login_data = ($('#login_form').serializeJson());
if (login_data.username == undefined || login_data.password == undefined) {
alert("用户名或密码不能为空");
return;
}
$.ajax({
type: "POST",
dataType: "json",
url: "https://api.shisanshui.rtxux.xyz/auth/login",
data: JSON.stringify(login_data), //提交的数据
contentType: "application/json;charset-UTF-8",
success: function (result) { //todo
console.log(result); //打印服务端返回的数据(调试用)
if (result.status == 0) {
token=result.data.token;
user_id=result.data.user_id;
username=login_data.username
console.log(token);
localStorage.setItem("token1",token);
localStorage.setItem("user_id1",user_id);
localStorage.setItem("username1",username);
alert("登录成功");
window.location.href = '游戏大厅.html'
};
},
error: function (res) {
// $("#login_form").reset();
alert("用户名或密码错误");
}
});
}
function logout(){
token=localStorage.getItem("temp");
$.ajax({
type: "POST",
url: "https://api.shisanshui.rtxux.xyz/auth/logout",
beforeSend: function(xhr) {
xhr.setRequestHeader("X-Auth-Token",token);
},
success: function (result) { //todo
console.log(result);
console.log(token);
alert("注销成功");
window.location.href = '点入.html'
},
error: function (res) {
alert("注销失败");
}
});
}
-
接收到牌的接口
接收到牌的时候,用一个字符串数组储存起来,并且交给AI,AI根据所穿的字符串数组来给出牌来交给服务器。
-
排行榜的接口
在排行榜的接口中, 我先接入服务器的接口使用的是get 方式,并根据服务器所返回的数据现场生成一个表格。
-
接口分析的小结
这次连接接口的过程中,学习到了ajax,并且了解了get和post两种方式的不同之处。GET - 从指定的资源请求数据。POST - 向指定的资源提交要被处理的数据
-
使用ajax进行http请求操作,样例如下:
-
代码组织与内部实现设计(类图)
-
说明算法的关键与关键实现部分流程图
算法关键在于三墩回溯中剪枝函数的设计以及分数矩阵评分算法的设计
流程图如下:
五、关键代码解释
-
分数矩阵
var pattern_points = [
// Tr FH Qu SF RF
[1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1], // front
[1, 1, 1, 1, 1, 1, 1, 2, 8, 10, 20], // center
[1, 1, 1, 1, 1, 1, 1, 1, 4, 5, 10], // back
];
var junk_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 1, 1, 2, 2, 4, 7, 15, 34],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
var junk_value0 = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 20, 20, 21, 21, 22, 23, 24, 26, 30, 34, 42, 0], //A?
[ 0, 9, 9, 10, 10, 11, 11, 12, 13, 15, 18, 0, 0], //K?
[ 0, 5, 5, 5, 6, 6, 7, 7, 8, 8, 0, 0, 0] //Q?
];
var one_pair_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[45, 47, 49, 51, 53, 56, 60, 64, 68, 73, 81, 89, 97],
[ 2, 3, 3, 4, 5, 6, 8, 10, 12, 15, 18, 24, 33],
[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3]
];
var one_pair_value0 = [
//2 3 4 5 6 7 8 9 T J Q K A
[96, 96, 96, 96, 96, 96, 97, 97, 97, 97, 98, 98, 0], //A?
[86, 86, 86, 86, 86, 87, 87, 88, 89, 89, 90, 0, 91], //K?
[76, 76, 77, 77, 77, 78, 78, 78, 79, 80, 0, 82, 83] //Q?
];
var one_pair_value1 = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 30, 31, 31, 32, 32, 33, 33, 34, 35, 36, 0], //A?
[ 0, 0, 23, 23, 23, 23, 23, 24, 25, 25, 26, 0, 27], //K?
[ 0, 0, 17, 17, 18, 18, 18, 19, 19, 20, 0, 21, 22] //Q?
];
var two_pair_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 37, 37, 39, 41, 43, 46, 49, 54, 58, 62, 64, 64],
[ 0, 3, 3, 4, 4, 5, 7, 8, 10, 11, 13, 14, 14]
];
var continuous_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 37, 37, 39, 41, 43, 46, 49, 54, 58, 62, 64, 64],
[ 0, 3, 3, 4, 4, 5, 7, 8, 10, 11, 13, 14, 14]
];
var triple_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[99, 99, 99, 99, 99,100,100,100,100,100,100,100,100],
[63, 66, 69, 71, 72, 72, 74, 74, 74, 75, 75, 75, 76],
[12, 13, 13, 15, 16, 16, 16, 16, 16, 14, 15, 15, 15]
];
var straight_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 77, 79, 81, 83, 85, 87, 88, 89, 91, 92],
[ 0, 0, 0, 16, 18, 20, 22, 24, 26, 28, 31, 34, 37]
];
var flush_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 93, 92, 92, 93, 94, 95, 97, 98],
[ 0, 0, 0, 0, 0, 35, 37, 38, 38, 40, 44, 50, 61]
];
var flush_value2 = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 53, 54, 55, 56, 57, 59, 62, 65, 0], //A?
[ 0, 0, 0, 44, 44, 45, 46, 47, 48, 49, 52, 0, 0], //K?
[ 0, 0, 0, 41, 41, 41, 42, 42, 44, 45, 0, 0, 0] //Q?
];
var full_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[98, 99, 99, 99, 99, 99, 99, 99,100,100,100,100,100],
[65, 66, 69, 71, 73, 75, 78, 80, 82, 85, 88, 91, 94],
];
var quadruple_value = [
//2 3 4 5 6 7 8 9 T J Q K A
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[100,100,100,100,100,100,100,100,100,100,100,100,100],
[ 94, 94, 95, 95, 95, 96, 97, 97, 97, 97, 98, 98, 98]
];
-
三墩回溯
optimize_back() {
for (var b1 = 0; b1 < 9; ++b1) {
for (var b2 = b1 + 1; b2 < 10; ++b2) {
for (var b3 = b2 + 1; b3 < 11; ++b3) {
for (var b4 = b3 + 1; b4 < 12; ++b4) {
for (var b5 = b4 + 1; b5 < 13; ++b5) {
this.back = new WaveEvaluator(
[this.hand[b1], this.hand[b2], this.hand[b3],
this.hand[b4], this.hand[b5]], Back);
if (this.back.pattern == Junk) continue;
if (200 + this.back.value < this.threshold) continue;
var bits = (1<<b1) + (1<<b2) + (1<<b3) + (1<<b4) + (1<<b5);
this.optimize_center(bits);
}
}
}
}
}
}
optimize_center(bits) {
out:
for (var c1 = 0; c1 < 9; ++c1) {
if ((1 << c1) & bits) continue;
for (var c2 = c1 + 1; c2 < 10; ++c2) {
if ((1 << c2) & bits) continue;
for (var c3 = c2 + 1; c3 < 11; ++c3) {
if ((1 << c3) & bits) continue;
for (var c4 = c3 + 1; c4 < 12; ++c4) {
if ((1 << c4) & bits) continue;
for (var c5 = c4 + 1; c5 < 13; ++c5) {
if ((1 << c5) & bits) continue;
this.center = new WaveEvaluator(
[this.hand[c1], this.hand[c2], this.hand[c3],
this.hand[c4], this.hand[c5]], Center);
if (this.back.is_smaller_than(this.center)) break out;
if (100 + this.center.value + this.back.value < this.threshold)
continue;
var center_bits = (1<<c1) + (1<<c2) + (1<<c3) + (1<<c4) + (1<<c5);
this.optimize_front(bits + center_bits);
}
}
}
}
}
}
optimize_front(bits) {
out:
for (var f1 = 0; f1 < 11; ++f1) {
if ((1 << f1) & bits) continue;
for (var f2 = f1 + 1; f2 < 12; ++f2) {
if ((1 << f2) & bits) continue;
for (var f3 = f2 + 1; f3 < 13; ++f3) {
if ((1 << f3) & bits) continue;
this.front = new WaveEvaluator(
[this.hand[f1], this.hand[f2], this.hand[f3]], Front);
if (this.center.is_smaller_than(this.front)) break out;
var sum_value = expected_points(this.front, this.center, this.back);
var duplicate = false;
for (var pos = 0; pos < this.top_waves.length; ++pos) {
if (sum_value < this.top_waves[pos][0]) break;
if (sum_value == this.top_waves[pos][0]) {
duplicate = true;
break;
}
}
if (duplicate) continue;
if (pos == 0 && this.top_waves.length == this.handicap + 1) continue;
this.top_waves.splice(pos, 0, [sum_value].concat(
this.front.wave, this.center.wave, this.back.wave));
if (this.top_waves.length > this.handicap + 1)
this.top_waves.shift();
this.threshold = this.top_waves[0][0];
}
}
}
}
六、性能分析与改进
-
改进思路
使用评分矩阵进行评分,并且依据评分进行剪枝,减少回溯的时间。
-
性能分析图
- 三次提交出牌结果的CPU性能分析图,可见,在AI算法运行期间,cpu使用达到峰值。
-
消耗最大函数
- 局部CPU性能分析图,可见,本AI算法主要是用回溯法找到最优解,所以时间主要花在三墩回溯上。
七、单元测试
-
单元测试代码
function shuffle(array) {
for (var i = 0; i < array.length; ++i) {
var pos = Math.floor(Math.random() * (array.length - i));
var item = array[pos];
array[pos] = array[array.length - 1 - i];
array[array.length - 1 - i] = item;
}
return array;
}
console.time("run time");
for (var i =0 ; i<=1000 ;++i )
{
tmp=optimize_hand(shuffle([...Array(52).keys()]));
}
console.timeEnd("run time");
-
测试代码说明
随机构造一千种出牌情况,在nodejs上进行测试,输出运行时间。
-
构造测试数据的思路
生成大量随机数组,进行随机评测。最坏情况一次46ms,最好情况可以达到一次8ms,主要分布在8-15ms。
八、Github代码签入记录
九、遇到的代码模块异常或结对困难及解决方法
-
问题描述
-
由于前端使用的是浏览器端,一开始我使用的是python写AI,导致了之后前后端无法相连,浏览器限制不能执行本地文件。
-
在前端设计上,队友和我持不同意见。
-
做过哪些尝试
-
最后使用js又写了一遍AI,使得代码能够嵌入html中,得以相连。
-
相互阐述理由,说服对方。
-
是否解决
-
是
-
是
-
有何收获
-
掌握了JavaScript一些编程的基本技巧,以及如何嵌入html,在浏览器上运行。
-
增强了自己的沟通能力。
十、评价你的队友
-
值得学习的地方
能够主动地去找一些素材并使用,主页除了不是动图外,做的还是挺棒的。
-
需要改进的地方
有点拖延的习惯,并且不能按时完成自己承诺的项目进度,但是还算比较认真,希望她能改变自己,在今后的工作中越来越棒吧。
十一、学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时 (小时) |
累计学习耗时 (小时) |
重要成长 |
---|---|---|---|---|---|
1 | 100 | 100 | 24 | 24 | 掌握了一些web前端设计方法,以及python的flask,requests框架 |
2 | 400 | 500 | 24 | 48 | 掌握了服务器端与客户端的交互方式,并且可以通过编程实现交互,并在谷歌云上设计开发了自己的个人网站 |
3 | 1000 | 1500 | 2 | 50 | 掌握了nodejs使用和JavaScript编程 |