1. 程式人生 > 實用技巧 >成麻結賬程式

成麻結賬程式

  一直想做這個程式有幾年時間了,主要是每次打血戰到底的時候,戰局比較複雜的話,最後算錢肯定是一個比較費腦的事情,所以就一直想自己手寫這麼一段程式碼,可以把這個計算解放出來。這次在疫情期間,也是和家裡人一起打麻將,終於又出現了這個比較迷人的煩惱,所以乾脆一不做二不休,花了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版本的部落格,我會在裡面寫一些更多不一樣的程式設計思想。