Github链接:SE_Applet
Bilibili链接:🥰🥰🥰点击此处为我三连吧😘😘😘
一、结对探索(4分)
1.1 队伍基本信息(1分)
结对编号:27 ;队伍名称:玄掷玲珑队
学号 | 姓名 | 作业博客链接 | 具体分工 |
---|---|---|---|
102101517 | 杨宇晗 | 博客链接 | UI素材制作、原型设计、视频制作、前端、AI算法 |
102101341 | 蔡宇杭 | 博客链接 | 需求分析、后端、AI算法 |
1.2 描述结对的过程(1分)
我们在结对一开始都没有找到队友。和yh稍微有些认识,在交换了各自的技能点之后,就决定组一队啦。
1.3 非摆拍的两人在讨论设计或结对编程过程的照片(2分)
二、原型设计(16分)
2.1 原型工具的选择(2分)
- 墨刀:简洁直观,操作简单,功能强大
- MediBang Paint:手绘软件,用于绘制组件图案、页面背景以及logo
- Adobe Illustrator:标题设计,组件样式调整
- Adobe Photoshop:组件拼接
2.2 遇到的困难与解决办法(3分)
困难1:游戏主题风格和背景图片的设计
- 解决办法:在我冥思苦想之际,浮现出高中时候很喜欢的一款游戏《看火人 (Firewatch) 》,当时深深被它的画面惊叹到,也考虑到它的画风也有点“孤注一掷”的味道,于是绘制了类似风格的背景图片,并设计、绘制页面各个组件、标题文字样式、个性化骰子等使得尽可能贴近主题风格。
困难2:在使用MediBang Paint软件时,对画笔工具的不熟悉
- 解决办法:通过学习使用软件,再通过实际操作练习,熟能生巧,掌握了一些常用笔刷的使用并绘制出页面组件。
困难3:在使用墨刀时,对动态组件的使用不熟悉。之前虽然有使用过Axure RP之类的原型软件,但是只接触过静态组件的布局和简单的页面跳转,因此在设计BGM图标动态切换和游戏规则页面时,遇到需要使用动态组件的时候,不知道如何实现理想效果。
- 解决办法:通过查找墨刀动态组件功能,并通过动手实践,懂得了如何设置动态组件事件和状态之间的逻辑,进而解决BGM图标动态切换,游戏规则图片切换等功能。
困难4:在使用墨刀的演示功能的时候,由于一开始自定义的页面尺寸是较为通用的750*1334,在询问客服之后,墨刀中没有机型与这个尺寸匹配,因此在预览的时候没法显示真机外壳。
- 解决办法:只能重新做一份375*667页面尺寸的原型,对应iPhone 6/7/8的真机
收获:提高了自己原型设计能力以及相关软件的使用,关键是学习了Medibang Paint,以前并没有接触过手绘软件,填补了技能点。并且更深刻了解了UI设计的流程和其在软件开发中的作用。
2.3 原型作品链接(5分)
2.4 原型界面图片展示(6分)
1、欢迎页面和游戏主页(点击右上角叶子图标可以开启或关闭BGM)
前端开发中叶子图标切换动效实际为图标在页面外从上方边缘缓缓落下
2、三种游戏模块
- a.本地对战
- b.人机对战
- c.娱乐模式
3、排行榜
4、游戏规则
5、创新点
- 规则中的操作示范
- 娱乐模式
- 带有班级元素的游戏彩蛋
- 在本地对战中有玩家骰子组合为[5,3,4,2,6](顺序需一致)时,弹出彩蛋图片
- 页面组件(除玩家头像外)均为个人绘制创作
- 如游戏logo:
- 标题设计
- 如游戏logo:
三、编程实现(14分)
3.1 网络接口的使用(2分)
通过调用微信小程序的登录接口wx.login实现自动登录,获取手机号码作为用户标志
**// 封装成类
class Request {
constructor(baseURL) {
this.baseURL = baseURL; // 基础URL
}
// 发起请求
request(options) {
const { url } = options;
return new Promise((resolve, reject) => {
wx.request({
...options, // 合并选项
url: this.baseURL + url, // 拼接完整的URL
success: (res) => {
resolve(res.data); // 返回响应数据
},
fail: (err) => {
console.log("err:", err); // 打印错误信息
}
});
});
}
// 发起GET请求
get(options) {
return this.request({ ...options, method: "get" });
}
// 发起POST请求
post(options) {
return this.request({ ...options, method: "post" });
}
}
export const loginRequest = new Request("http://xxxxxxx"); // 创建登录请求实例
export const getCode = () => {
return new Promise((resolve, reject) => {
wx.login({
success: (res) => {
// 获取code
resolve(res.code); // 返回code
}
});
});
};**
// 导出一个函数,用于获取登录凭证code
export const getCode = () => {
// 返回一个新的Promise对象
return new Promise((resolve, reject) => {
// 调用微信登录接口
wx.login({
success: (res) => {
// 登录成功后,从响应中获取code
const code = res.code;
// 将code作为参数传递给resolve函数,表示Promise对象已成功完成
resolve(code);
},
fail: (err) => {
// 登录失败时,打印错误信息
console.log("err:", err);
// 将错误信息作为参数传递给reject函数,表示Promise对象已失败
reject(err);
}
});
});
};
3.2 代码组织与内部实现设计(类图)(2分)
前端:
后端:
3.3 说明算法的关键与关键实现部分流程图(2分)
算法的关键:
- 骰子锁定部分:
不同情况的处理函数存在着优先级的选择,多种情况同时出现时也存在着优先级的选择
每种情况判断和处理较为清晰地在函数名中体现了,在最开始的 robotDecisionFunction()中实现了AI选择骰子锁定的操作- isLargeStraight:判断是否为大顺
- isSmallStraight:判断是否为小顺
- isBeforeSmallStraight:判断是否为三顺,如[2,2,3,4,4],[1,3,3,5,6]
- isPenta:判断是否五连
- isQuadra:判断是否四连
- isBeforeQuadra:判断是否三连
- isTwoPairs:判断是否出现排除除了三连的其他所有情况的两对相同数字,如[1,3,3,5,5],[2,2,2,4,4]
- isSinglePair:判断是否出现排除上述所有情况后的一对相同数字,只有四种组合,如[1,2,5,5,6]
- choosePentaLock:选择五连锁定
- chooseQuadraLock:选择四连锁定
- chooseTripleLock:选择三连锁定
- chooseLargeStraightLock:选择大顺锁定
- chooseSmallStraightLock:选择小顺锁定
- chooseBeforeSmallStraight:选择三顺锁定
- chooseTwoPairs:选择排除除了三连的其他所有情况后两对相同数字中较大的那对锁定,若有[x,x,5,6,6]则锁定[5,6,6]
- chooseSinglePair:选择排除上述情况后一对相同数字中较大的那对锁定,若是[1,2,5,6,6]则锁定[5,6,6]
算法关键部分流程图(骰子锁定部分):
robotDecisionFunction: function(player1Dices, player2Values) {
let self = this
let player1Values = this.extractDiceValues(this.data.player1)
if(this.isPenta(player1Values)){
this.data.player1 = this.choosePentaLock(this.data.player1) ;
}
if(this.isLargeStraight(player1Values)){
this.chooseLargeStraightLock();
console.log(this.data.player1);
}
if(this.isSmallStraight(player1Values)){
this.chooseSmallStraightLock() ;
}
if(this.isBeforeSmallStraight(player1Values)&&this.isBeforeQuadra(player1Values)&&this.data.throwRounds===1){
if(this.isToChooseTripleOrStraight()){
this.chooseTripleLock(this.data.player1);
}else{
this.chooseBeforeSmallStraight();
}
}
if(this.isBeforeQuadra(player1Values)&&this.data.completeBeforeSmallStraight===false){
let player = this.chooseTripleLock(this.data.player1) ;
this.setData({
player1 : player
});
}
if (this.isBeforeSmallStraight(player1Values)&&this.data.completeTriple===false&&this.data.completeBeforeSmallStraight===false&&this.data.completeSinglePair===false&&this.data.completeTwoPairs===false) {
this.chooseBeforeSmallStraight() ;
}
if (this.isQuadra(player1Values)){
this.data.player1 = this.chooseQuadraLock(this.data.player1) ;
}
if(this.isSinglePair(player1Values)){
this.chooseSinglePair() ;
}
if(this.isTwoPairs(player1Values)&&this.data.throwRounds===1&&!this.isBeforeQuadra(player1Values)&&!this.isBeforeSmallStraight(player1Values)){
this.chooseTwoPairs() ;
}
if(this.isTwoPairs(player1Values)&&this.data.throwRounds===2&&this.data.isSinglePair===false){
if(this.data.completeTwoPairs===false){
this.chooseTwoPairs() ;
}
if(this.data.completeTriple){
this.chooseTwoPairs() ;
}
}
if(this.isBeforeQuadra(player1Values)&&(this.data.completeSinglePair||this.data.completeTwoPairs)){
let player = this.chooseTripleLock(this.data.player1) ;
this.data.player1 = player ;
}
return player1Dices;
},
isBeforeSmallStraight: function(values) {
const threeSequences = [
"1,2,3", "2,3,4", "3,4,5", "4,5,6" // 三连续序列
];
const almostFourSequences = [
"1,2,4", "1,3,4", "2,3,5", "2,4,5", "3,4,6", "3,5,6" // 缺少一个数的四连续序列
];
const fourSequences = ["1,2,3,4", "2,3,4,5", "3,4,5,6"]; // 完整的四连续序列
// 去除数组中的重复项
let uniqueValues = Array.from(new Set(values));
let sortedValues = uniqueValues.sort((a, b) => a - b).join(",");
// 检查是否存在四连续序列
for (let seq of fourSequences) {
if (sortedValues.includes(seq)) {
return false;
}
}
// 遍历可能的序列,并检查去重后的玩家骰子值中是否包含它们
for (let seq of threeSequences.concat(almostFourSequences)) {
if (sortedValues.includes(seq)) {
this.setData({ targetBeforeSmallStraight: true });
return true;
}
}
return false;
},
isBeforeQuadra: function(values) {
let count = [0, 0, 0, 0, 0, 0]; // 为每个数字初始化计数器
for (let v of values) {
count[v - 1]++; // 计数每个数字
}
for (let c of count) {
if (c === 3) {
this.setData({ targetBeforeQuadra: true });
this.data.completeTriple = true ;
return true; // 如果我们找到三个相同的数字
} else if (c >= 4) {
return false; // 如果找到四个或更多相同的数字
}
}
return false;
},
isQuadra: function(values) {
let count = [0, 0, 0, 0, 0, 0]; // 为每个数字初始化计数器
for (let v of values) {
count[v - 1]++; // 计数每个数字
}
for (let c of count) {
if (c === 4) {
this.setData({ targetQuadra: true });
return true; // 如果我们找到四个相同的数字
} else if (c === 5) {
return false; // 如果找到五个相同的数字
}
}
return false;
},
isSmallStraight: function(values) {
let sortedValues = [...new Set(values)].sort(); // 去重并排序
console.log(sortedValues);
for (let i = 0; i < sortedValues.length - 3; i++) {
if (sortedValues[i + 3] - sortedValues[i] === 3) {
// 检查连续的四个值
if (sortedValues.length===4 || sortedValues[sortedValues.length-1]-sortedValues[0]===5) {
// 确保没有连续的五个值
this.setData({ targetSmallStraight: true });
return true;
}
}
}
return false;
},
isPenta: function(values) {
let count = [0, 0, 0, 0, 0, 0]; // 为每个数字初始化计数器
for (let v of values) {
count[v - 1]++; // 计数每个数字
}
for (let c of count) {
if (c === 5) {
this.setData({ targetPenta: true });
return true; // 如果找到五个相同的数字
}
}
return false;
},
isLargeStraight: function(values) {
const sortedValues = values.slice().sort();
if (JSON.stringify(sortedValues) === JSON.stringify([1, 2, 3, 4, 5]) ||
JSON.stringify(sortedValues) === JSON.stringify([2, 3, 4, 5, 6])) {
this.setData({ targetLargeStraight: true });
return true;
}
return false;
},
isTwoPairs: function(diceValues) {
// 排序
let sortedValues = [...diceValues].sort().join("");
// 匹配数字序列
let twoPairsPatterns = [
"11225", "12255", "11255", "11226", "12266", "11266",
"11335", "13355", "11355", "11336", "13366", "11366",
"11445", "14455", "11455", "11446", "14466", "11466",
"11556", "15566", "11566", "22336", "23366", "22366",
"22446", "24466", "22466", "22556", "25566", "22566",
"11122","11222","11333","11133","11444","11144","11555","11155","11666","11166","22333","22233","22444","22244","22555","22255","22666","22266","33444","33344","33555","33355","33666","33366","44555","44455","44466","44666","55666","55566"
];
if (twoPairsPatterns.includes(sortedValues)) {
this.setData({ targetTwoPairs: true });
return true;
}
return false;
},
isSinglePair: function(diceValues) {
// 排序
let sortedValues = [...diceValues].sort().join("");
// 匹配数字序列
let singlePairPatterns = [
"11256", "12256", "12556", "12566"
];
if (singlePairPatterns.includes(sortedValues)) {
this.setData({ targetSinglePair: true });
return true;
}
return false;
},
choosePentaLock: function(player1) {
let valueCounter = {};
// 统计每个骰子值的出现次数
player1.forEach(die => {
if (valueCounter[die.value]) {
valueCounter[die.value]++;
} else {
valueCounter[die.value] = 1;
}
});
// 找到一个值出现了5次
let pentaValue = Object.keys(valueCounter).find(key => valueCounter[key] === 5);
if (pentaValue) {
let lockCount = 0;
for (let i = 0; i < player1.length; i++) {
if (lockCount < 5) {
player1[i].isLocked = true;
lockCount++;
}
}
}
return player1;
},
chooseQuadraLock: function(player1) {
let valueCounter = {};
// 统计每个骰子值的出现次数
player1.forEach(die => {
if (valueCounter[die.value]) {
valueCounter[die.value]++;
} else {
valueCounter[die.value] = 1;
}
});
// 找到一个值出现了4次,但没有出现5次的情况
let quadraValue = Object.keys(valueCounter).find(key => valueCounter[key] === 4);
if (quadraValue) {
let lockCount = 0;
for (let i = 0; i < player1.length; i++) {
if (player1[i].value == quadraValue && lockCount < 4) {
player1[i].isLocked = true;
lockCount++;
}
}
}
console.log(player1);
return player1;
},
chooseTripleLock: function(player1) {
let valueCounter = {};
// 统计每个骰子值的出现次数
player1.forEach(die => {
if (valueCounter[die.value]) {
valueCounter[die.value]++;
} else {
valueCounter[die.value] = 1;
}
});
// 找到一个值出现了3次,但没有出现4次或5次的情况
let tripleValue = Object.keys(valueCounter).find(key => valueCounter[key] === 3);
if (tripleValue) {
let lockCount = 0;
for (let i = 0; i < player1.length; i++) {
if (player1[i].value == tripleValue && lockCount < 3) {
player1[i].isLocked = true;
lockCount++;
}
}
}
this.data.completeTriple === true ;
console.log(player1);
return player1;
},
chooseLargeStraightLock: function() {
for (let i = 0; i < 5; i++) {
this.data.player1[i].isLocked = true;
}
},
chooseSmallStraightLock: function() {
let count = 0 ;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark = 6 ;
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
console.log(indices) ;
if(this.data.player1[indices[4]].value - this.data.player1[indices[3]].value ==0){
mark = 4 ;
}
for(let i = 0; i < 4; i++) { // 只有两种连续的四个数字的可能性
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==1){
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==0){
mark = i
count = 0 ;
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===0){
mark = i ;
}
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===1) {
count++ ;
// 使用已排序的索引来锁定骰子
if(count===3){
for(let i = 0; i < 5; i++){
if(i!==mark){
this.data.player1[indices[i]].isLocked = true;
}
}
this.data.completeSmallStraight = true ;
break;
}
}
}
console.log(this.data.player1) ;
},
chooseSinglePair: function() {
//let count = 0 ;
let flag=0;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark = 6 ;
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
for(let i = 0; i < 4; i++) { // 只有一组两个相同数字,且没有其他情况
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ==0){
mark = i ;
break ;
}
}
if(this.data.player1[indices[3]].value - this.data.player1[indices[2]].value ==1){
flag=1;
this.data.player1[indices[2]].isLocked = true;
this.data.player1[indices[3]].isLocked = true;
this.data.player1[indices[4]].isLocked = true;
}
for(let i = 0; i < 5; i++){
if(i==mark&&flag==0){
this.data.player1[indices[i]].isLocked = true;
this.data.player1[indices[i+1]].isLocked = true;
}
}
this.data.completeSinglePair = true ;
console.log(this.data.player1) ;
},
chooseTwoPairs: function() {
//let count = 0 ;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark1 = 6 ; //表示否定情况的值,随便取的
let mark2 = 8 ; //表示否定情况的值,随便取的
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
for(let i = 0; i < 4; i++) { // 只有一组两个相同数字,且没有其他情况
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ==0){
mark1 = i ;
break ;
}
}
for(let i = 0; i < 4; i++) { // 只有一组两个相同数字,且没有其他情况
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ==0 && i!==mark1){
mark2 = i ;
break ;
}
}
for(let i = 0; i < 5; i++){
if(i===mark2){
this.data.player1[indices[i]].isLocked = true;
this.data.player1[indices[i+1]].isLocked = true;
}
}
if(this.data.player1[indices[mark2]].value===6&&this.data.player1[indices[mark2-1]].value===5){
this.data.player1[indices[mark2-1]].isLocked = true ;
}
if(this.data.player1[indices[mark2]].value===6&&this.data.player1[indices[mark1]].value===5){
this.data.player1[indices[mark2-1]].isLocked = true ;
this.data.player1[indices[mark2-2]].isLocked = true ;
}
this.data.completeTwoPairs = true ;
console.log(this.data.player1) ;
},
chooseSmallStraightLock: function() {
let count = 0 ;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark = 6 ;
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
console.log(indices) ;
if(this.data.player1[indices[4]].value - this.data.player1[indices[3]].value ==0){
if(this.data.player1[indices[3]].isLocked){mark=4}else{mark=3}
}
for(let i = 0; i < 4; i++) { // 只有两种连续的四个数字的可能性
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==1){
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==0){
mark = i
count = 0 ;
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===0){
if(this.data.player1[indices[i+1]].isLocked){
mark = i
}else{
mark = i+1
}
}
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===1) {
count++ ;
// 使用已排序的索引来锁定骰子
if(count===3){
for(let i = 0; i < 5; i++){
if(i!==mark){
this.data.player1[indices[i]].isLocked = true;
}
}
this.data.completeSmallStraight = true ;
break;
}
}
}
console.log(this.data.player1) ;
},
chooseBeforeSmallStraight: function() {
let count = 0 ;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark = 6 ;
let mark1 = 8 ;
let mark2 = 8 ;
let begin = 0 ;
let choice1 = 5;
let choice2 = 5;
let choice3 = 5;
let flag1 = 0 ;
let flag2 = 0 ;
let mkflag = 0 ;
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
console.log(indices) ;
//判断缺中间的小顺
for(let i = 0; i < 4; i++){
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value===2){
if(i===0){
let lastValue = this.data.player1[indices[i+1]].value+1 ;
for(let j=0; j<5;j++){
if(this.data.player1[indices[j]].value===lastValue){
choice1 = 0;
choice2 = 1;
choice3 = j;
flag2 = 1 ;
}
}
}else if(i===3){
let frontValue = this.data.player1[indices[i]].value-1 ;
for(let j=0; j<5;j++){
if(this.data.player1[indices[j]].value===frontValue){
choice1 = j ;
choice2 = 3 ;
choice3 = 4 ;
flag2 = 1 ;
}
}
}else{
for(let j=0;j<5;j++){
let frontValue = this.data.player1[indices[i]].value-1 ;
let lastValue = this.data.player1[indices[i+1]].value+1 ;
if(this.data.player1[indices[j]].value===frontValue||this.data.player1[indices[j]].value===lastValue){
choice1 = i ;
choice2 = i+1 ;
choice3 = j ;
flag2 = 1 ;
}
}
}
}
}
// 判断缺头尾的小顺
for(let i = 0; i < 4; i++) {
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==1){
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==0){
count = 0 ;
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===0){
if(mkflag===0){
mark1 = i ;
mkflag = 1 ;
}
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===0 && i!==mark1){
mark2 = i ;
}
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===1) {
count++ ;
// 使用已排序的索引来锁定骰子
if(count===2){
let stop = i+1 ;
for(let i = 0; i < 4; i++){
if(mark1!==8&&mark2!==8) {break ;}
if(mark1!==8&&mark2===8) {
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value > 1){
if(i===0) {begin=1}
if(i===1) (begin=2)
if(i===3) {begin=0}
}
}
if(mark1===8&&mark2===8) {
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value > 1){
if(stop===3) {begin=1}
if(stop===4) {begin=2}
}
}
}
if(flag2===0){
for(let i = begin; i <= stop; i++){
if(i!==mark1&&i!=mark2&&i!==mark){
this.data.player1[indices[i]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}
if(flag2===1){
if(this.data.player1[indices[4]].value-this.data.player1[indices[3]].value===1&&this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===4){
for(let j = begin; j <= stop; j++){
if(j!==mark1&&j!=mark2&&j!==mark){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[3]].value===2&&this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===4){
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===2){
for(let j = begin; j <= stop; j++){
if(j!==mark1&&j!=mark2&&j!==mark){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===3){
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[0]].value===5&&this.data.player1[indices[4]].value-this.data.player1[indices[2]].value===2){
for(let j = begin; j <= stop; j++){
if(j!==mark1&&j!=mark2&&j!==mark){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}else{
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}
}
break;
}
}
}
if(flag1===0){
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}
},
//选择是三连还是三顺
isToChooseTripleOrStraight: function() {
let count = 0 ;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark = 6 ;
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
console.log(indices) ;
if(this.data.player1[indices[4]].value - this.data.player1[indices[3]].value===0){
return true ;
}else{return false;}
},
3.4 贴出重要的/有价值的代码片段并解释(2分)
在写算法的过程中,有一点难度的就是对三顺([3,2,5],[4,5,6]…)的判断与锁定
- isBeforeSmallStraight: function(values)是其判断函数,只要对骰子的值数组传入,排序并匹配就可以了,同时要排除被包含的优先级较高的类型,比如小顺[2,3,4,5]…、大顺[1,2,3,4,5]…,这个函数的实现并不是很复杂
isBeforeSmallStraight: function(values) {
const threeSequences = [
"1,2,3", "2,3,4", "3,4,5", "4,5,6" // 三连续序列
];
const almostFourSequences = [
"1,2,4", "1,3,4", "2,3,5", "2,4,5", "3,4,6", "3,5,6" // 缺少一个数的四连续序列
];
const fourSequences = ["1,2,3,4", "2,3,4,5", "3,4,5,6"]; // 完整的四连续序列
// 去除数组中的重复项
let uniqueValues = Array.from(new Set(values));
let sortedValues = uniqueValues.sort((a, b) => a - b).join(",");
// 检查是否存在四连续序列
for (let seq of fourSequences) {
if (sortedValues.includes(seq)) {
return false;
}
}
// 遍历可能的序列,并检查去重后的玩家骰子值中是否包含它们
for (let seq of threeSequences.concat(almostFourSequences)) {
if (sortedValues.includes(seq)) {
this.setData({ targetBeforeSmallStraight: true });
return true;
}
}
return false;
},
- chooseBeforeSmallStraight: function()是选择相应的骰子进行锁定的判断,其难点主要在于对于选择的时候如果使用排序,排序的是player结构体数组中骰子结构体的value,但是要通过排序后的值找到初始的结构体数组的索引并对结构体中的isLocked进行修改实现锁定,则需要用到间接索引;然后就是一次投掷中出现两个缺中间的三顺情况(比如[2,2,3,5,6])或是缺头尾的三顺+缺中间的三顺时(比如[1,2,4,5,6])时,如何选择值较大的那一组三顺,也是一个对我来说稍微有些复杂的点。
chooseBeforeSmallStraight: function() {
let count = 0 ;
let indices = Array.from({length: this.data.player1.length}, (_, i) => i); // [0, 1, 2, 3, 4]
let mark = 6 ;
let mark1 = 8 ;
let mark2 = 8 ;
let begin = 0 ;
let choice1 = 5;
let choice2 = 5;
let choice3 = 5;
let flag1 = 0 ;
let flag2 = 0 ;
let mkflag = 0 ;
console.log(indices) ;
indices.sort((a, b) => this.data.player1[a].value - this.data.player1[b].value); // 按骰子的值排序索引
console.log(indices) ;
//判断缺中间的小顺
for(let i = 0; i < 4; i++){
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value===2){
if(i===0){
let lastValue = this.data.player1[indices[i+1]].value+1 ;
for(let j=0; j<5;j++){
if(this.data.player1[indices[j]].value===lastValue){
choice1 = 0;
choice2 = 1;
choice3 = j;
flag2 = 1 ;
}
}
}else if(i===3){
let frontValue = this.data.player1[indices[i]].value-1 ;
for(let j=0; j<5;j++){
if(this.data.player1[indices[j]].value===frontValue){
choice1 = j ;
choice2 = 3 ;
choice3 = 4 ;
flag2 = 1 ;
}
}
}else{
for(let j=0;j<5;j++){
let frontValue = this.data.player1[indices[i]].value-1 ;
let lastValue = this.data.player1[indices[i+1]].value+1 ;
if(this.data.player1[indices[j]].value===frontValue||this.data.player1[indices[j]].value===lastValue){
choice1 = i ;
choice2 = i+1 ;
choice3 = j ;
flag2 = 1 ;
}
}
}
}
}
// 判断缺头尾的小顺
for(let i = 0; i < 4; i++) {
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==1){
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value !==0){
count = 0 ;
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===0){
if(mkflag===0){
mark1 = i ;
mkflag = 1 ;
}
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===0 && i!==mark1){
mark2 = i ;
}
}
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value ===1) {
count++ ;
// 使用已排序的索引来锁定骰子
if(count===2){
let stop = i+1 ;
for(let i = 0; i < 4; i++){
if(mark1!==8&&mark2!==8) {break ;}
if(mark1!==8&&mark2===8) {
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value > 1){
if(i===0) {begin=1}
if(i===1) (begin=2)
if(i===3) {begin=0}
}
}
if(mark1===8&&mark2===8) {
if(this.data.player1[indices[i+1]].value - this.data.player1[indices[i]].value > 1){
if(stop===3) {begin=1}
if(stop===4) {begin=2}
}
}
}
if(flag2===0){
for(let i = begin; i <= stop; i++){
if(i!==mark1&&i!=mark2&&i!==mark){
this.data.player1[indices[i]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}
if(flag2===1){
if(this.data.player1[indices[4]].value-this.data.player1[indices[3]].value===1&&this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===4){
for(let j = begin; j <= stop; j++){
if(j!==mark1&&j!=mark2&&j!==mark){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[3]].value===2&&this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===4){
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===2){
for(let j = begin; j <= stop; j++){
if(j!==mark1&&j!=mark2&&j!==mark){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[1]].value===3){
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}else if(this.data.player1[indices[4]].value-this.data.player1[indices[0]].value===5&&this.data.player1[indices[4]].value-this.data.player1[indices[2]].value===2){
for(let j = begin; j <= stop; j++){
if(j!==mark1&&j!=mark2&&j!==mark){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
flag1 = 1 ;
}else{
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}
}
break;
}
}
}
if(flag1===0){
for(let j = 0; j < 5; j++){
if(j===choice1||j===choice2||j===choice3){
this.data.player1[indices[j]].isLocked = true;
}
}
this.data.completeBeforeSmallStraight = true ;
}
},
3.5 性能分析与改进(2分)
渲染了所有页面之后的性能、体验、最佳实践分析图
分析:可总体情况较为良好,但也存在着图片太大与实际区域不符导致下载时间和内存消耗增加的问题
改进:适当减少图片分辨率,使之不会远大于页面中实际区域尺寸;在符合微信小程序主包大小规定的情况下首先考虑压缩图片,减少图片外链的使用
3.6 单元测试(2分)
设计单元测试函数testRobotDecision,测试算法中的函数,并用console.log()打印,测试数据为50组随机生成的骰子
3.7 贴出GitHub的代码签入记录,合理记录commit信息(2分)
四、总结反思(11分)
4.1 本次任务的PSP表格(2分)
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 7680 | 8390 |
· Analysis | · 需求分析 (包括学习新技术) | 850 | 800 |
· Design Spec | · 生成设计文档 | 300 | 450 |
· Design Review | · 设计复审 | 150 | 100 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 300 | 360 |
· Design | · 具体设计 | 1200 | 1500 |
· Coding | · 具体编码 | 4500 | 4800 |
· Code Review | · 代码复审 | 100 | 80 |
· Test | · 测试(自我测试,修改代码,提交修改) | 280 | 300 |
Reporting | 报告 | 220 | 260 |
· Test Report | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 60 | 50 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 100 | 150 |
· 合计 | 7930 | 8680 |
4.2 学习进度条(每周追加)(2分)
- 杨宇晗
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 452 | 452 | 30 | 30 | 熟悉了墨刀,Medibang Paint的使用,学习了前端框架 |
2 | 2060 | 2512 | 32 | 62 | 前端三件套的使用,前端页面的编写,在js文件中实现游戏对局中的交互逻辑 |
3 | 1645 | 4157 | 35 | 97 | 学习了AI功能的算法设计,分析并画出算法流程图,设计算法函数,页面样式调整 |
4 | 232 | 4389 | 15 | 112 | 优化了AI功能算法,性能改进,单元测试 |
- 蔡宇杭
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 392 | 392 | 35 | 35 | 初步了解了前端的组成部分,学习wxml和wxss文件的运用 |
2 | 2110 | 2312 | 42 | 52 | 后端中思考ai算法的设计,进行实验 |
3 | 1745 | 4337 | 35 | 89 | 落实了AI功能的算法设计,进行页面样式调整 |
4 | 272 | 4469 | 15 | 108 | 分块进行单元测试 |
4.3 最初想象中的产品形态、原型设计作品、软件开发成果三者的差距如何?(2分)
- 我们最初想象中的产品是界面使稍微带点悬疑风的,满足账号登录登出、能够实现本地对战、人机对战、在线对战、娱乐模式四大功能,其中人机对战可供玩家选择不同难度、带有在线排行榜单、个人成绩记录等功能的、人机交互友好,用户操作舒适的微信小程序。实际过程中开发效率不是很高(比如UI部分就用了将近一周),导致我们开始进行后端开发的时候已经是比较晚的时间节点了,这也是导致需求未全部实现的主要原因。
- 原型设计作品做得差强人意,UI设计方面,原型制作者但软件技术有待提高,小程序界面风格不够统一,有些部分过于简陋以致于有些违和,对局页面配色方面可以改进,对于有些抽象的骰子图案原型制作者做完其实是有些后悔的,但规则中已经引用了这些图案也没什么时间再改了,好在设计的交互基本上能够实现。(原型设计者真的很努力在做了QAQ)
- 最终开发出的软件与后端相关的部分功能未能实现,对于外链引用的图片会有卡顿,AI选择接近人的理想决策但还有较大的改进空间,娱乐模式中部分创意玩法未能实现。造成差距的主要原因是开发知识和经验不足导致的效率效率较低。
4.4 评价你的队友(2分)
杨宇晗:
- 值得学习的地方:杨宇晗同学代码编程能力很强,对小程序很有规划,有极强的自学能力,能够寻找非常多的资源进行运用
- 需要改进的地方:杨宇晗同学代码撰写速度较慢,与队友分工可以改进,希望可以一起努力!
蔡宇杭:
- 值得学习的地方:蔡宇杭同学能够对需求做出详细的分析,有极强的自学能力,对工作积极主动
- 需要改进的地方: 希望蔡宇杭同学在结对编程的过程中能够更积极地提出和自己的意见和想法,携手共进!
4.5 结对编程作业心得体会(3分)
杨宇晗:
- 这次作业对于我来说还是有些难度的,在之前并没有解除过系统性的软件开发,我参与了UI设计、前端开发和算法设计,中间也遇到了不少问题。
- 在UI设计的过程中,首先是Medibang Paint的使用,第一次使用绘图软件,其中有不少复杂的功能还是没有学会并使用,从而整个游戏组件图案有一种很简陋的感觉,有一些早时候画的组件看不顺眼会重新画,但是由于时间安排,有一些较违和的组件并没有重新绘制。由于两年没接触过原型软件和工具,已经几乎把大一时候01gg和xxgg传授给我的宝贵经验忘光了QAQ😭,也是由于时间安排,在稍微浏览了下大一时候的UI课件便开始设计原型,也并没有严格遵守其中的UI设计规范,个人其实不太满意。
- 在前端开发的过程中,我跟随B站课程学习了基础部分后便开始动手了,在图片的布局中花费了不少时间搞清楚层级关系,mode使用widthFix,图片尺寸和位置使用自适应单位rpx会比较好处理,在不同手机尺寸的页面自适应。global变量使用,媒体的播放与关闭,参数在不同页面的传递,淡入淡出动画,图标通过平移切换状态,锁定前保证能修改锁定骰子,锁定后不能修改锁定骰子等功能的实现也花费了不少时间学习
- 在算法设计的过程中,一个函数没法完全实现目的情况的处理,不同情况的处理函数存在着优先级的选择,多种情况同时出现时也存在着优先级的选择,倍率选择的部分要分析对手骰子,这都要修改与设置。算法设计部分是bug最频出现的部分,我也经常犯一些低级错误,比如含参函数没传参,‘===’写成’='等等
- 我和队友都是新手,在开发的过程中也是按顺序做一个需求是一个需求,平常时间以及国庆假期能够线下集合结对编程的时间也比较少,对于双方知识盲区的部分,也不敢贸然和队友分工多进程开发赶进度,加上我开发效率不算很高(比如UI部分就用了将近一周),导致我们开始进行后端开发的时候已经是比较晚的时间节点了,这也是导致需求未全部实现的主要原因。经过这次锻炼后,在以后的开发我会汲取这次的经验教训,根据需求详细安排个人的主要工作并且对每一个功能模块的开发设置deadline,合理安排时间。
- 结对编程和个人编程还是存在差异的,结对编程的过程中,两位开发者能够交换意见并进行改进,两个人覆盖的知识面与技能点也更多,虽然没有实现当初设计的所有功能,但还是很开心能和队友一起完成了一个之前没做过的游戏类小程序,也算是完成了一个挑战吧✌。
蔡宇杭:
- 心得体会:此次根据游戏要求制作小程序,是对我能力的一种考验,前所未有的编译环境是一项大任务,还有领航员的新模式。
- 作业难度:难度大在于在线对战和AI开发,在线对战的登录模式相对比较经验有限,个人更熟悉单机的模式;AI开发的算法也是一大难题,需要自己对规则向了解清楚,然后再进行设计
- 完成后的感受:结束之后,觉得蛮有成就感的。感觉自己还可以再进步一些。
- 结对困难:两个人的作息可能不太同步,难以十分按照一同编写的节奏。
- 解决方法:最终两个人决定部分代码采用分块分工的方法,最后进行整合查缺补漏。
- 对之后学习或软件开发的启发:初期规划非常重要,一定要考虑可行性
附加部分
- 作品有创新,融入了班级特色创意元素,如有,在博客中2.4 原型界面图片展示部分展示说明。(每处满分+4分,满分+8分)
- 原型界面图片展示部分所展示的游戏组件的创新
- 除此之外,游戏中还设计了带有班级元素的彩蛋
- 鼓励进行微信小程序应用开发,成功上线的结对队伍将给予Bonus附加奖励。(+8分)