欢迎各位来到我的博客,希望大家可以一起多多交流技术!

成麻结账程序

    1.0版预览地址:http://hudong.miaos.me/majiang/index.html

  2.0博客直通车:https://www.cnblogs.com/qinyulin/p/13566107.html

  2.0版预览地址:http://hudong.miaos.me/majiang/new.html

  一直想做这个程序有几年时间了,主要是每次打血战到底的时候,战局比较复杂的话,最后算钱肯定是一个比较费脑的事情,所以就一直想自己手写这么一段代码,可以把这个计算解放出来。这次在疫情期间,也是和家里人一起打麻将,终于又出现了这个比较迷人的烦恼,所以干脆一不做二不休,花了2天时间完成了这个程序的初版,然后在实战中缝缝补补了几个补丁,最终完成了成麻1.0的版本,主体界面完成情况是下面这样的:

    最开始在写布局代码的时候考虑的是把界面分为上中下三个部分,大概的样子如下图:

   这样分别对应布局上中下三个部分,上面为一个用户,中间两边各为一个用户,下面为一个用户,中间的中间部分是公共的牌面。大体的布局代码如下:

   根据四个用户申明对应的变量,如下图代码:

        data: {
            userArr: ['', '庄家', '左家', '上家', '右家'],
            user1: "",
            user2: "",
            user3: "",
            user4: "",
            totalArr: [],
            zongpanVisiable: false,
            shuomingVisiable: false,
            zongpanArr: [],
            baseMoney: 2,
            orderArr: [],
            userCount: 4,
            countArr: [],
            messageArr: [],
            djShow1: false,
            djShow2: false,
            djShow3: false,
            djShow4: false,
            over1: false,
            over2: false,
            over3: false,
            over4: false,
            result1: "",
            result2: "",
            result3: "",
            result4: "",
            dgStatus: false,
            dgIndex: 0,
            hpStatus: false,
            hpIndex: 0,
            fpStatus: false,
            fpIndex: 0,
            fanArr: [],
            total1: 0,
            total2: 0,
            total3: 0,
            total4: 0,
            resultVisiable: false,
            mingziVisiable: false,
            fan1: 0,
            fan2: 0,
            fan3: 0,
            fan4: 0
        },    

  每个用户涉及到的操作是有巴杠、暗杠、点杠、胡牌和自摸5个事件,从字面上理解,我们可以会把三个杠分为一类,其实这里的分类应该是巴杠、暗杠和自摸,因为这三种都是属于第一人称和其他用户数组为主,点杠和胡牌都是需要自己和对方一起完成,所以我们先贴上布局代码:

 1                     <div class="user">
 2                         <div class="zujian">
 3                             <div class="gang flex-x-y-center">
 4                                 <div @click="bagang(2)">巴杠</div>
 5                                 <div @click="angang(2)">暗杠</div>
 6                                 <div @click="diangang(2)"></div>
 7                             </div>
 8                             <div class="hu flex-x-y-center">
 9                                 <div @click="zimo(2)">自摸</div>
10                                 <div @click="hupai(2)">胡牌</div>
11                             </div>
12                         </div>
13                         <span>{{userArr[2]}}</span><!--用户名-->
14                         <div class="dianji" v-if="djShow2" @click="dianji(2)">请选择</div><!--被杠和被胡牌的选择弹层-->
15                         <div class="over" v-if="over2"><span>{{result2}}</span></div><!--本局胡牌结束标识层-->
16                         <div v-if="resultVisiable" class="zongshu">{{userArr[2]}}<br>{{total2}}</div><!--最终输赢钱的文字层-->
17                     </div>

  布局的话我们还是先把杠和赢钱的事件分开放,下面还有用户名,被杠和被胡牌的选择弹层,本局胡牌结束标识层和最终输赢钱的文字层。如上面的代码注释,选择弹层这里要说明一下,比如说我胡牌或者杠牌了,这个时候我需要选择另外打给我牌的用户,那么我就需要在界面上对应用户区域去选择,就需要一个选择弹层,展示效果如下图:

   当然巴杠、暗杠和自摸不需要出这个一个弹层,因为这几个事件肯定是针对于场上还没胡牌的其他所有玩家,是一对多的关系,可能大家会说一炮多响也是一对多,但是拆开来看,一炮多响其实也是一对一事件,只是连续发生了两次。那么接下来我们就来说这几个事件的具体结果,如下图:

   主要代码分为两类,巴杠、暗杠和自摸为第一类,点杠和胡牌为第二类,第一类里面有个obj对象的user属性值,主要是这个值标识是赢钱的人,over数组里面是收集其他人的胡牌情况,到后面清算的时候就找到这个数组里面没结束的;第二类主要是根据over情况弹出选择框,然后去点击对应的用户再进行具体的逻辑代码,如下面的代码:

 1             dianji: function (index) {
 2                 if (this.dgStatus) {
 3                     //此时是点杠,选择收钱的那一方
 4                     let obj = {
 5                         name: "diangang",
 6                         ying: this.dgIndex,
 7                         shu: index,
 8                     }
 9                     this[`fan${this.dgIndex}`] += 1;
10                     this.countArr.push(obj);
11                     this.fanArr.push(-1);
12                 }
13                 if (this.hpStatus) {
14                     //此时是胡牌,选择放炮的那一方
15                     let obj = {
16                         name: "hupai",
17                         ying: this.hpIndex,
18                         shu: index,
19                         fanshu: 1
20                     }
21                     this.countArr.push(obj);
22                     this.fanArr.push(0);
23                     this[`over${this.hpIndex}`] = true;
24                     this[`result${this.hpIndex}`] = "胡牌";
25                 }
26                 this.statusInit();
27             },

  因为第二类事件是一对一的操作,所以我就没有用数组,主要还是一个单字符串的对象来标识。

  然后我们来说一下文字列表,这里主要是记录我们每一次操作的描述,比如说谁杠牌,谁自摸,谁胡牌了,界面主要如下:

 

   这里需要注意的其实就是后面有一个番数数字的加减,为什么这里要进行手动的加减呢?可能大家会问,每次杠不都是有记录么?其实如果是手动记录的话,在本局结束之前,程序是没办法知道当前用户具体是胡的什么牌,如果是涉及到对子胡或者清一色可能就会加番,所以这里就需要在最终完成结算之前进行一个手动的操作番数,加减的代码如下:

 1             jian: function (index) {
 2                 if (this.fanArr[index] <= 0) {
 3                     return;
 4                 } else {
 5                     this.fanArr[index]--;
 6                     this.fanArr.splice(0, 0);
 7                 }
 8             },
 9             jia: function (index) {
10                 if (this.fanArr[index] >= 3) {
11                     return;
12                 } else {
13                     this.fanArr[index]++;
14                     this.fanArr.splice(0, 0);
15                 }
16             },

  当完成这一步的时候,终于最终的结算就要来了,我们主要是通过countArr和fanArr这两个的数组信息进行结算,因为涉及到相似代码,所以下面是代码截图:

   主要还是通过对countArr的循环,根据之前记录的字段和对应的操作类型,对单个的用户总计进行加减。比如说暗杠就是巴杠的两倍,自摸就相当于巴杠再加一个底等。完成这个结算的界面如下图:

   这样就计算出本局每个用户的输赢情况,这里或者之前就会出现一种情况,如果说某一个操作我手动点错了,或者这个时候我觉得计算错误,我要返回重新操作,那么就可以点击公共牌面上的悔棋按钮,悔棋按钮的主要逻辑就是删除countArr里面最新的一条数据,代码如下:

 1             huiqi: function () {
 2                 if (this.resultVisiable) {
 3                     this.resultVisiable = false;
 4                     return;
 5                 }
 6                 if (!this.countArr.length) {
 7                     return;
 8                 }
 9                 let obj = this.countArr.pop();
10                 if (obj.name == "zimo") {
11                     this[`over${obj.user}`] = false;
12                 } else if (obj.name == "hupai") {
13                     this[`over${obj.ying}`] = false;
14                 }
15                 this.fanArr.pop();
16                 alert("已经悔了一步了")
17             },

  如果正常完成本局计算,那么就是进入下一局,这里我把结算和下一局按钮做成的相斥状态,不能同时出现,下一局事件主要还是还原变量的值,具体的代码如下:

 1             nextTime() {
 2                 if (!this.resultVisiable) {
 3                     alert("需要先结算当前局")
 4                     return;
 5                 }
 6                 this.resultVisiable = false;
 7                 let arr = localStorage.getItem("totalArr");
 8                 let total = localStorage.getItem("total");
 9                 arr = arr == "" ? [] : JSON.parse(arr);
10                 total = total == "" ? [] : JSON.parse(total);
11 
12                 arr.push(this.countArr);
13                 let name = [];
14                 for (let i = 1; i <= 4; i++) {
15                     let obj = {
16                         user: i,
17                         name: this.userArr[i],
18                         money: this[`total${i}`]
19                     }
20                     name.push(obj);
21                 }
22                 total.push(name);
23                 localStorage.setItem("totalArr", JSON.stringify(arr));
24                 localStorage.setItem("total", JSON.stringify(total));
25 
26                 this.fanArr = [];
27                 this.countArr = [];
28                 this.over1 = this.over2 = this.over3 = this.over4 = false;
29             },

  然后点击下一局的时候,这里做了一个localStorage缓存功能,避免用户不小心刷新页面丢失数据的问题,同样的,在每次页面进来的时候也去读取一下localStorage,把之前的局的结果数据读取出来,代码如下:

 1         mounted() {
 2             let username = localStorage.getItem("username");
 3             let arr = localStorage.getItem("totalArr");
 4             let total = localStorage.getItem("total");
 5             if (arr == null) {
 6                 localStorage.setItem("totalArr", []);
 7             }
 8             if (total == null) {
 9                 localStorage.setItem("total", []);
10             }
11             if (username == null) {
12                 let a = ['', '庄家', '右家', '对家', '左家'];
13                 localStorage.setItem("username", JSON.stringify(a));
14             }
15             this.userArr = JSON.parse(localStorage.getItem("username"))
16         },

  然后我们再来看一下每局之后保存完,需要看所有局的一个结果,就点击总盘,显示界面如下,我贴一个以前的战况图:

     我只能说这个东西谁用谁知道,很爽!请各位忽略战况。

  最后再说一下改名的功能,主要就是改变对应用户的名称字段,界面展示和代码如下:

1             gaimingzi() {
2                 this.user1 = this.userArr[1];
3                 this.user2 = this.userArr[2];
4                 this.user3 = this.userArr[3];
5                 this.user4 = this.userArr[4];
6                 this.mingziVisiable = true;
7             },

  到这里,这个程序的功能基本上都说完了,因为时间比较赶,想着能用就行,所以在代码布局和质量上面写得比较差,但是在后面的实战中,计算都是全部正确的,虽然辛苦了我个人手动操作,但是却大大减少了计算牌局的时间,最开始大家还要多多少少计算一下,后面基本上就完全依赖这个程序,打几个小时的牌,也不用每次把RMB拿出来给,只需要在最后的时候看一下总的完成情况,微信转一个账就行了。

  终于这个第一版就告一段落,可能大家有个疑问,这是第一版1.0,那肯定就有2.0了,对滴!因为在后面的实战当中发现了两个这版不太好解决的问题:1、最后查叫,如果两个人需要赔一家,就没办法操作,因为当第一个用户赔叫的时候,赢钱的这家就已经over了,不能进行下一家的差叫;2、换位的问题,因为最开始没考虑到面向对象问题,所以我把几个用户的很多变量值都写得很固定,以至于后面想去修改的时候就是一团乱麻,这个时候再想去理清楚就很麻烦;3、买马的问题,如果需要增加其他用户想参与进来买马或者接下的话,当前的这种代码结构不好扩展;4、打好多钱的问题,目前我是写得固定2元底,没有把这个弄成变量,导致我们目前只能打2元,如果想打5块,10块就不好操作。针对于上面的4点问题,我也是纠结了很久,需不需要进行优化,终于后面在考虑培训课题问题上面,想要不然趁着这个机会,正好做一次优化,把项目进行重构,所以就在一个周末有了2.0的诞生,特别是写2.0的时候,有了一个更好玩的想法,这个想法还是等以后实现了再说,那么下面就是2.0版本的博客,我会在里面写一些更多不一样的编程思想。

posted @ 2020-08-28 21:38  秦渝淋  阅读(752)  评论(0编辑  收藏  举报