zoukankan      html  css  js  c++  java
  • 成麻结账程序

        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版本的博客,我会在里面写一些更多不一样的编程思想。

  • 相关阅读:
    窗体吸附 Timer + 判断Location (简单实用)
    C# FTP 应用程序
    C# 加密方法汇总
    LINQ 标准的查询操作符 合计操作符 Count()、Sum()、Min()、Max()、Average()和Aggregate()
    委托中的协变和逆变(C# 编程指南)
    深入探讨C#序列化和反序列化
    grep 命令详解
    Oracle 数据库的启动和关闭的方式!
    linux 下的光盘拷贝
    C3P0连接池配置
  • 原文地址:https://www.cnblogs.com/qinyulin/p/13564689.html
Copyright © 2011-2022 走看看