137
社区成员




https://github.com/Camel-Li/032002123/tree/main/%E7%BB%93%E9%98%9F%E7%BC%96%E7%A8%8B
https://www.bilibili.com/video/BV1id4y1171v?share_source=copy_web&vd_source=51d798bf07a1d95834f810ad863b6ca4
小程序名称:K版骰骰乐
结对编号:6;队伍名称:这是个队名;
学号 | 姓名 | 作业博客链接 | 具体分工 |
---|---|---|---|
032002128 | 吕晨曦 | https://bbs.csdn.net/topics/608609752 | 游戏流程设计+算法开发+搜集素材 |
032002123 | 李若彤 | https://bbs.csdn.net/topics/608609466 | 游戏框架设计+前端开发+原型设计 |
同班同学,隔墙而居,互相欣赏对方的技术实力,组队迅速,一拍即合。
我们选择了墨刀作为原型工具,首先是因为墨刀是免费的原型设计软件,非常适合学生党使用,其次是墨刀的原型设计体验感非常优秀,在墨刀上对控件进行布局,它会自然地匹配相应的母版大小,无需去担心移动的误差。并且墨刀的系统控件都是基于APP,以及系统平台IOS和安卓,因此在里面可以首先选择相应的设备布局,减少了麻烦的环节。
对于这个原型设计工具,我想表达一些我的看法。对于我们这种小型的程序来说,原型设计的价值没有想象的那么大,在微信小程序里实现的内容和布局方法和原型设计里完全不一样。除非是要提前给甲方提供程序内容预览这种,如果只是自己做程序,自己看的话,感觉没必要在原型设计上花费太多时间。其次,原型设计我们尝试了很多款软件,但往往是付费的或者实现的界面无法应用到小程序网页上,墨刀的网页设计好后只能保存为墨刀格式的文件,而且必须在墨刀的服务器上运行,无法本地生成,这又是一个难点。最后我们的解决办法是,用墨刀制作出程序大概的布局和逻辑,然后在微信小程序上从头开始编写,其实编写小程序的过程很少借鉴原型设计界面,给我的感觉是,这种原型界面是为了把自己的界面设计灵感保存下来,当程序规模较大时,可以有效的给程序员设计模型,无需多余的思考过程,效率会提高一些。
https://modao.cc/app/LAZxiCDrik8duDgh85p
这是我们设计的欢迎页面,因为逍遥具有中国风的这种意境在其中,所以我们主体采用了中国风的风格,中间标题用毛笔字书写,尽显中国特色。左上角K代表K班,是我们设计的小彩蛋
本地对战
本地对战中的双人对战
双人对战结算画面
人机对战猜先
猜先结果
双人对战页面
双人对战结算
双人对战排行榜
联机对战登录权限
创建房间
连接房间
房间等待界面
联机对战
联机对战结果
联机排行榜
关于
目前应用获取用户信息的功能,在用户授权的情况下,可以获取用户的头像,昵称等等个人信息,不包括用户的敏感信息。
我们定义了一个主框架类游戏后端,用于实现游戏运行相关的内容。其中包含了游戏的诸个必要的元素,棋盘board、骰子dice、游戏当前状态flag和游戏运行的方法run ()。在这个主框架下,我们定义了两个类,分别为棋盘和骰子。棋盘类用于记录游戏中棋盘的状态,其中包括了一个3X3的矩阵、判断棋盘是否为空的方法matrix_isfull()和判断棋盘上当前总得分的方法count_matrix_points()。骰子类用来生成骰子的随机数。
算法的关键在于控制游戏运行的run()方法,调用run()方法需要传入玩家序号、骰子数以及玩家想要放入的位置。我们先对位置进行检测,如果位置可行,就把他填入相应位置,然后检测对方棋盘同一列的三个位置,若有相同数字,就消掉。最后判断当前棋盘是否已经填满,如果填满了,就判断胜负,将flag标记为相应的情况。
run (player_num, dice_num, add_chess_x, add_chess_y) {
this.flag = 0;
if (player_num == 1) {
if (this.matrix1[add_chess_x][add_chess_y] == 0) {
this.matrix1[add_chess_x][add_chess_y] = dice_num;
for (let i = 0; i < 3; i++) {
if (this.matrix2[add_chess_x][i] == dice_num) {
this.matrix2[add_chess_x][i] = 0;
}
}
if (this.matrix1_is_full()) {
if (this.count_matrix1_points() > this.count_matrix2_points()) {
this.flag = 1;
} else if (this.count_matrix1_points() == this.count_matrix2_points()) {
this.flag = 3;
} else {
this.flag = 2;
}
}
}
} else if (player_num == 2) {
if (this.matrix2[add_chess_x][add_chess_y] == 0) {
this.matrix2[add_chess_x][add_chess_y] = dice_num;
for (let i = 0; i < 3; i++) {
if (this.matrix1[add_chess_x][i] == dice_num) {
this.matrix1[add_chess_x][i] = 0;
}
}
if (this.matrix2_is_full()) {
if (this.count_matrix1_points() > this.count_matrix2_points()) {
this.flag = 1;
} else if (this.count_matrix1_points() == this.count_matrix2_points()) {
this.flag = 3;
} else {
this.flag = 2;
}
}
}
}
// flag == 0 继续比赛
// flag == 1 player1获胜
// flag == 2 player2获胜
// flag == 3 平局
return flag;
}
我们希望我们的机器人能拥有像人一样的思考方式,我们在多次自己游玩的经历中意识到,对于人类玩家来说,我们会关注当前场面上的最大利益,而且我们会在追求最大利益的同时,尽量减少不稳定因素的发生。所以我们采用了让机器人计算下在每一个位置的敌我分数差,当我方分数减去对方分数达到最大时,我们认为这个位置会是大部分人类玩家的选择。当有多个位置能得到相同的分数差时,机器人会选择放置骰子较少的列,保留放置骰子较多的列的空位,以期在对方double甚至triple后,能有机会消掉对方的骰子。
robots_play() {
let max = -999999;
let x = 0;
let y = 0;
let Game1 = new GameBackEnd();
for (let j = 0; j < 3; j++) {
for (let i = 0; i < 3; i++) {
let obj1 = {
matrix1: GBE.matrix1,
matrix2: GBE.matrix2,
}
let obj2 = JSON.parse(JSON.stringify(obj1));
Game1.matrix1 = obj2.matrix1;
Game1.matrix2 = obj2.matrix2;
if (Game1.matrix1[i][j] == 0) {
Game1.run(1, dice, i, j);
}
if (Game1.count_matrix1_points() - Game1.count_matrix2_points() > max) {
max = Game1.count_matrix1_points() - Game1.count_matrix2_points();
x = i;
y = j;
}
}
}
GBE.run(1, dice, x, y);
}
因为我们要制作微信小程序,所以用户在选择放置骰子位置时一定会用点击的方式提供位置。我们对棋盘的九个位置绑定点击事件,当点击其中一个位置后,就会触发点击事件并传入位置参数和棋盘编号到函数中。我们先检测位置是否合理,如果合理就放置并设置棋盘信息、更新回合信息,检测过程标记。如果已经有一方获胜或平局那就跳转至结算页面。
clickPosition : function(a) {
if (gameflag) {
let x = a.currentTarget.dataset.column - 1;
let y = a.currentTarget.dataset.row - 1;
let num = a.currentTarget.dataset.num;
let avai = 0;
if (num == 1) {
if (GBE.matrix1[x][y] == 0) {
avai = 1;
}
} else {
if (GBE.matrix2[x][y] == 0) {
avai = 1;
}
}
if (avai) {
if (GBE.flag == 0 && playerTurnNum == num) {
GBE.run(num, dice, x, y);
this.setData({matrix1 : GBE.matrix1});
this.setData({matrix2 : GBE.matrix2});
setPmatrix();
this.setData({pmatrix1 : pm1});
this.setData({pmatrix2 : pm2});
this.setData({point1 : GBE.count_matrix1_points()});
this.setData({point2 : GBE.count_matrix2_points()});
if (playerTurnNum == 1) {
playerTurnNum = 2;
} else {
playerTurnNum = 1;
}
if (GBE.flag == 1) {
console.log("player1 win");
console.log(GBE.count_matrix1_points());
wx.redirectTo ({
url: '../lose/lose?bot=' + GBE.count_matrix1_points() + '&person=' + GBE.count_matrix2_points(),
})
gameflag = 0;
} else if (GBE.flag == 2) {
console.log("player2 win");
console.log(GBE.count_matrix2_points());
wx.redirectTo ({
url: '../win/win?bot=' + GBE.count_matrix1_points() + '&person=' + GBE.count_matrix2_points(),
})
gameflag = 0;
} else if (GBE.flag == 0) {
sleep(500);
dice = Math.floor(Math.random()*6 + 1);
this.setData({diceNum : png['rand']});
sleep(500);
this.setData({diceNum : png[dice]});
}
}
}
}
}
我们还制作了消除特效,首先检测棋盘在对方放置骰子后与前一个状态有没有变化,如果有变化,那就是被消除了,我们对被消除的骰子上叠加消除特效。
let obj1 = {
matrix1: GBE.matrix1,
}
let obj2 = JSON.parse(JSON.stringify(obj1));
let m1 = obj2.matrix1;
GBE.run(num, dice, x, y);
this.setData({matrix1 : GBE.matrix1});
this.setData({matrix2 : GBE.matrix2});
setPmatrix();
this.setData({pmatrix2 : pm2});
let f = 0;
for (let i=0; i<3; i++) {
if (m1[x][i] != GBE.matrix1[x][i]) {
pm1[x][i] = "https://s2.loli.net/2022/10/12/mKyhTY8Npq3ZnFs.gif";
this.setData({pmatrix1 : pm1});
f = 1;
}
}
if (f == 1) {
sleep(1000);
}
为了让玩家能够清楚的看到当前轮到谁下,我们使用数据绑定对棋盘的透明度进行调整,使目前在下的一方棋盘高亮。
<--board1-->
<image class='{{bg1}}' src="https://s1.ax1x.com/2022/10/12/xUuETO.png" mode="aspectFill"></image>
<--board2-->
<image class='{{bg2}}' src="https://s1.ax1x.com/2022/10/12/xUuETO.png" mode="aspectFill"></image>
.background1 {
opacity:0.5;
}
.background2 {
opacity:1;
}
this.setData({bg1 : "background2"});
this.setData({bg2 : "background1"});
我们采用了微信小程序预览中自带的真机调试,对我们的程序进行性能分析,发现载入图片会花费较多的性能,我们改进的思路是:在游戏进入前先预加载,对图片进行缓存。这样就大大降低了我们的性能损耗。我们程序中消耗最大函数是run()函数,因为其中涉及到矩阵的多次运算,所以消耗比较大。
安装环境
// 小程序工具集
$ npm i --save-dev miniprogram-simulate
// Jest测试框架
$ npm i --save-dev jest
在package.json中, 添加测试相关命令
{
sd
...
script: {
"test": "jest --coverage"
}
...
}
添加jest.config.js:
module.exports = {
verbose: true,
modulePathIgnorePatterns: [
'<rootDir>/dist-wx/',
'<,rootDir>/node_modules/',
],
// 是否开启自动mock测试文件中导入的文件
automock: false,
testRunner: 'jasmine2',
// 测试文件执行前会先执行该文件,用来给Jest测试函数加代理从而收集测试用例
setupFilesAfterEnv: ['./node_modules/@tencent/dwt/dist/src/testbase/testbase.js'],
// 覆盖率报告依赖
reporters: [
'default',
'@tencent/dwt-reporter',
],
// 测试文件匹配规则
testMatch: [
'**/__test__/**/*.test.ts?(x)',
],
// 测试覆盖报告文件列表,下面是默认列表
coverageReporters: ['json', 'lcov', 'text', 'clover'],
// 全局变量配置
globals: {
NODE_ENV: 'test',
__wxConfig: {
global: {
window: {},
},
},
},
moduleNameMapper: {
'@/(.*)$': '<rootDir>/$1.ts',
},
setupFiles: ['<rootDir>/__test__/wx.ts'],
transform: {
'^.+\\.[jt]s?$': 'ts-jest',
},
preset: 'ts-jest',
testEnvironment: 'jsdom',
collectCoverage: true,
coverageDirectory: './__test__/coverage',
coverageReporters: ['json-summary', 'text', 'lcov'],
coveragePathIgnorePatterns: [
'/node_modules/',
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
},
coverageThreshold: {
global: {
branches: 50,
functions: 50,
lines: 50,
statements: 50,
},
},
};
我们测试了run()这个最重要的后端运行函数,随机生成了多组数据,每组数据中有两个矩阵和一个骰子数,对结果进行验证。
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 120 | 240 |
· Estimate | · 估计这个任务需要多少时间 | 120 | 240 |
Development | 开发 | 9160 | 11300 |
· Analysis | · 需求分析 (包括学习新技术) | 4000 | 5000 |
· Design Spec | · 生成设计文档 | 120 | 240 |
· Design Review | · 设计复审 | 60 | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 200 |
· Design | · 具体设计 | 400 | 500 |
· Coding | · 具体编码 | 4000 | 4000 |
· Code Review | · 代码复审 | 120 | 240 |
· Test | · 测试(自我测试,修改代码,提交修改) | 400 | 1000 |
Reporting | 报告 | 480 | 500 |
· Test Report | · 测试报告 | 120 | 240 |
· Size Measurement | · 计算工作量 | 60 | 120 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 120 | 240 |
· 合计 | 9760 | 12040 |
032002123李若彤:
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 3500 | 3500 | 64 | 64 | 学会设计游戏框架,使用python建立web服务器框架,并实现互动功能 |
2 | 4500 | 8000 | 70 | 134 | 学习了微信小游戏的使用,编写了游戏模型框架 |
3 | 5000 | 13000 | 80 | 216 | 学习了微信小程序的使用,前端页面设计和编程 |
032002128吕晨曦:
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 3000 | 3000 | 56 | 56 | 学习了强化学习、会使用numpy、pandas库 |
2 | 5000 | 8000 | 70 | 126 | 学习了微信小程序的写法,熟练使用HTML、CSS、JS语言 |
3 | 5000 | 13000 | 80 | 206 | 学习了PS等用于制作素材的软件,学习了如何上传微信小程序 |
最初想象中的产品形态和我们原型设计作品较为相近,因为原型设计作品是我们希望能做出的样子,但是原型设计作品中没有我们想要的特效音乐等东西。最后的软件开发成果是在原型设计作品上的加加减减,在项目过程中我们发现有些东西我们能做的更好,就加上了,有些东西我们发现很难实现,就放弃了。最后的软件开发成果还是与最初想象中的产品形态高度匹配的,仅少了一些我们实力不及,无法制作出来的部分。
032002123李若彤:
我认为我的队友技术很强,对于后端的js编写,个人感觉是很吃编程功底的。非常复杂的逻辑功能实现和算法实现,都要在后端去完成。期间我们也遇到了很多问题,比如后端的程序不能并行,监听系统出问题等等。在我们的共同努力下,这些问题都有了解决方案。我对我的队友感受很好,他对于我给的一些后端任务都会花很多心思去做完。即使有些功能很难实现,但最终我们还是克服了重重困难,完成了这些功能。这个小程序没有我的队友我最起码要花3倍的时间才能做出来,有他一起完成效率变得很高。
032002128吕晨曦:
我的搭档是一个对细节十分苛刻的人,他会对小程序的每一个细节都严格要求。他的很多建议让小程序看起来极具人性化。从按键按下去的声音、按键按下去样式的改变、人机思考停顿的时间已经各个按键出现的时机,都是在他的想法下一点一点的做完的。正是有他的这些细节, 我们的小程序最终的展示效果十分完美,在各个方面都找不到任何问题。同时,我的搭档也是一个十分能干的人,小程序的几乎所有页面的布局都由他操刀,如果没有他,我们的项目可能就需要多花费数倍的时间才能做完。
032002128李若彤:
这次结队编程作业我有很多想说的:
第一:最初我们实在python上写的小游戏,后来想要在微信小程序上运行时才发现一个问题。对于微信小程序来说,python文件等后端程序是要单独写接口的,而且作为服务器,一般来说要用自己的电脑做服务器,这个不太现实。还有一种可能性,也是参考之前学长做的,可以申请华为云,但是我们这个华为云早就过期了。。。如果要用云端的服务器还要花钱,所以我们后端的python文件就放弃使用了。第一周写的python几乎没派上用场,只是给后面的游戏提供了经验而已,这是我们踩的一个坑。
第二:我们使用微信小程序开发软件,有几个可以选择的选项:开发小程序或者开发小游戏。按照正常来说,一般想到的肯定时开发微信小游戏,于是我们下载了小游戏版的微信开发工具。在做了一些模型和实现一部分功能后,我们想要上传测试,却又发现一个问题。微信对于程序员开发的小游戏很严格,需要很多认证资质,甚至需要注册企业,这无疑让我们的工作陷入绝境。在得到这个情况之后,我们在第二周快结束的时候果断地转向小程序的开发,其实对于小游戏和小程序来说,只是小游戏能够运用的调用函数更好用,不需要自己写,相当于站在前人的肩膀上来写游戏。而微信小程序要实现小游戏的功能,就明显不如小游戏了。不过在我们的共同努力下,小程序还是开发出来了。这是我们踩的有一个坑。
第三:微信小程序里联机功能实现很艰难,主要是没有合适的服务器。微信小程序有他自己的规则和调用函数,没有像个人编程一样,网页可以自由的调用写的函数,还有外部的网络接口等等。对于小程序来说,现在我们会的仅仅是在用户授权的情况下获取用户的头像,昵称等等(不包括用户敏感信息)。我们的联机功能很长时间都没有任何进展,网上也查找了很多博客和资源,要建立服务器就还要对服务器进行运维管理,这些运维管理的内容就又是一个难以实现的点了。还有就是服务器没有合适的,实在不想用自己的电脑当服务器一直开着,这个显然是不现实的。至于学长使用华为云服务器,我只能说我没优惠券,没有免费的服务器。
第四:因为我是做小程序框架设计和前端设计和编写,我感受到的是微信小程序是非常不适合做小游戏的。就拿框架来说,简单的监听事件和按钮绑定事件,在小游戏里就有非常多的函数接口可以调用,操作起来很容易上手。而在小程序里,这些东西都要自己去编写绑定逻辑和监听逻辑,这里面会遇到很多很多的bug和无法实现的功能。就到目前位置,小程序里的游戏主体部分,骰子消除音效总是在消除动画结束后才播放,这个问题直到现在还未解决,还有gif的持续播放,如果没有自己写的sleep函数来延长等待时间,gif动画播放的时间就会非常短,无法实现该效果。此外,游戏内部的逻辑判断是一个函数,这个和网页上的按钮不能并行操作,会出现无法点击或者点击没有效果等等bug,最后我们是将游戏内部的逻辑嵌套在按钮点击事件里。个人感觉这是实在没有办法的办法,尝试过多种监听逻辑和并行逻辑,最终都没有实现,最后只能写在bindtap里,实在是无奈之举。也可能是我们对于微信的js文件的编程能力还不够,逻辑尚不清晰,所以没有处理好这个问题。
第五:我们的小程序成功的上线了,名字叫K版骰骰乐,可以直接用微信小程序搜索游玩。现在上传的版本不是最后的版本,最终版还在审核,可能在作业截至之后才会过审,游戏也会进行一些小的改动和更新,用于提升用户体验。
第六:我们在游戏界面中体现了班级的特色。其实我们想了很久,这个班级特色到底用什么,最后我们确定,用“K”来代表班级特色,我们的小程序名字就是K版骰骰乐,游戏中也有加入的“K”版元素。
第七:单元测试不太会做。如果不是微信小程序,其他的在本地都有很友好的测试软件。然而这个微信小程序,我在网上查找的解决方案,我环境一直搭建不了,不太清楚为什么一直会报错,结果也出不来。
第八:结队编程中,我的任务是小程序的游戏逻辑设计和框架布局,前端HTML和css开发和实现,音乐音效设计,原型设计。我队友的任务是小程序的游戏功能实现,逻辑编写,后端js和jason开发,小程序特效编写,素材编辑和制作。两个人的工作有很多是重叠的,作为两个人的沟通和问题解决。
最后:再次感谢我的队友,如果没有他的支持,说实话很多时候踩坑后都要放弃和摆烂了。通过这次结队编程,我对小游戏的开发有了全新的认识,也学习了很多关于小程序和小游戏的知识(各种接口的调用和各种软件的学习)。还有就是,一个人真的很难有精力在规定时间内做出比较复杂的程序,这也体现了程序员直接的相互沟通和合作。
032002128吕晨曦:
结对编程与单人编程有极大的不同,一个人做的时候,整个程序的整体都由自己设计,自己对项目具有百分百的话语权。但到了结对编程,两个人的观点意见多少会有些不同,对项目的不同看法会造成一些分歧。在这次结对编程中,由于编程思想和编程习惯上的种种原因,我和我的搭档在对于各部分的实现有一些不同的看法,也造成了一些争吵。同时由于任务分配的不明确,导致中途遇到有些任务一直无人去做的囧态。但在过了一些时间的磨合之后,我们对自己有了明确的定位,两人之间更有默契了,做起事来也是事半功倍,小程序的进度也涨得很快。
我在本次结对编程主要负责编写后端逻辑,对我来说,小程序逻辑编写的主要困难来源于JS语言。JS作为单线程的语言,在实现各种功能时给我造成了很大的困扰,比如简单的监听事件和按钮绑定事件,在小游戏里就有非常多的函数接口可以调用,操作起来很容易上手。而在小程序里,这些东西都要自己去编写绑定逻辑和监听逻辑,这里面会遇到很多很多的bug和无法实现的功能。就到目前位置,小程序里的游戏主体部分,骰子消除音效总是在消除动画结束后才播放,这个问题直到现在还未解决,还有gif的持续播放,如果没有自己写的sleep函数来延长等待时间,gif动画播放的时间就会非常短,无法实现该效果。此外,游戏内部的逻辑判断是一个函数,这个和网页上的按钮不能并行操作,会出现无法点击或者点击没有效果等等bug,最后我们是将游戏内部的逻辑嵌套在按钮点击事件里。个人感觉这是实在没有办法的办法,尝试过多种监听逻辑和并行逻辑,最终都没有实现,最后只能写在bindtap里,实在是无奈之举。也可能是我们对于微信的js文件的编程能力还不够,逻辑尚不清晰,所以没有处理好这个问题。
本次结对编程也给我带来了极大的成就感,和搭档一起上传了一个小程序,以前我一直以为制作一个项目非常之难,一直惧怕尝试,当时在跟搭档上传了自己人生的第一个小程序项目后,我感到我的精神升华了,我不再害怕尝试那些看起来很难的事情,因为那些看起来很难的事情可能没有心中想的那么难,心魔才是阻碍成长的最大敌人。