麻雀点数集計プログラム

サッカー五輪予選を観ながら,Web 無料麻雀ゲームをした。われらが日本代表が一生懸命アウェイで頑張っているというのに,観ている方はなんと暢気なものか。半荘を 10 回もやり,珍しく一人勝ち。
 

20111120-majan.png
 

バブル時代の高レート勝負だったらいったいいくら稼いだのかちょっと知りたくなって,プログラムを書いて計算してみた。プログラムは mjcalc。Perl である。

mjcalc -u [ウマ] -b [ビンタ] -k [レート] < 点数データ
ここで,[ウマ] は "数字1-数字2" の形式で指定し,"数字1" は 3 位から 2 位への,"数字2" は 4 位から 1 位へのウマとなる。省略すると 10-20 が仮定される。[ビンタ], [レート] は数値を千単位で指定する。省略すると,それぞれ 100, 2 を仮定する。点数データは空白文字区切りの 4 つの整数値である。

計算の考え方は次のとおり。25000 点持ちの 30000 点返し。オカは 20000 点とする。入力データは,1000 点 1 ポイントとして換算された各プレイヤの点数が空白文字で区切られているものととする。入力の点数を合計し,0 の場合はすでにウマ,オカが反映されているものとしてレート計算のみ行う。入力点数の合計が 100 の場合は,半荘終了時の持点として,指定されたウマ,オカを計算に反映する。指定されたビンタ,レートで金額計算を行う。ビンタとは下位のプレイヤが上位者に支払う差ウマのことで,ビンタ 10 万の取り決めならば,上位者に 10 万,自分が配給原点以下の場合,配給原点以上の上位者にはその倍額の 20 万を支払わなければならない。独り沈みの 4 位者ならば 60 万のマイナスというわけである。

さて,mjcalc で計算してみる。デカリャンピン (1000 点 2 千),ビンタ 10 万の気違いレート。データは上に示した 10 回のデータ (point.txt) である。この Web 麻雀ゲームの集計値はすでに 10-20 のウマ,20 ポイントのオカ,30 ポイントとの差分が集計済みである。

% cat point.txt 
60      -20     2       -42
18      -21     -52     55
63      9       -20     -52
52      -21     3       -34
11      -16     42      -37
-29     67      6       -44
10      -32     -21     43
63      -21     2       -44
40      5       -28     -17
70      -30     -41     1
% ./mjcalc -u 10-20 -b 100 -k 2 < point.txt 
ウマ1: 10; ウマ2: 20; オカ: 20(以上pt); ビンタ: 100(千円); レート: 2(千円/1pt)
----------------------------------------------------------------------
  Input Points     ;  Calculated Points ;   Amount of Money (k-en)   ;
   A    B    C    D;    A    B    C    D;      A      B      C      D;
----------------------------------------------------------------------
 +60  -20   +2  -42;  +60  -20   +2  -42;   +720   -240     +4   -484;
 +18  -21  -52  +55;  +18  -21  -52  +55;   +336   -342   -604   +610;
 +63   +9  -20  -52;  +63   +9  -20  -52;   +626   +318   -340   -604;
 +52  -21   +3  -34;  +52  -21   +3  -34;   +704   -242     +6   -468;
 +11  -16  +42  -37;  +11  -16  +42  -37;   +322   -332   +584   -574;
 -29  +67   +6  -44;  -29  +67   +6  -44;   -358   +634   +312   -588;
 +10  -32  -21  +43;  +10  -32  -21  +43;   +320   -564   -342   +586;
 +63  -21   +2  -44;  +63  -21   +2  -44;   +726   -242     +4   -488;
 +40   +5  -28  -17;  +40   +5  -28  -17;   +580   +310   -556   -334;
 +70  -30  -41   +1;  +70  -30  -41   +1;   +740   -260   -482     +2;
----------------------------------------------------------------------
+358  -80 -107 -171; +358  -80 -107 -171;  +4716   -960  -1414  -2342;

うん。半荘 10 回で 471.6 万を獲得したというわけである。でも,なんでこんな意味のないことをやっているのか。で,プログラムを書きながら計算の確認のため,やっぱりこの麻雀ゲームをやっていたら,なんと四暗刻を,しかもピンズ一色・嶺上開花(意味無し)でツモってしまいました。
 

20111124-suanko.png
 

一応,プログラム mjcalc のコードを掲げておきます。ヒマなときにこんなことしかしない,私のような方のために。

#!/usr/bin/perl -w
# -*- coding: utf-8; mode: cperl; -*-
#  Mahjong Points Calculator
#  -------------------------
#  Usage: mjcalc -u 10-20 -b 100 -k 2 < data
#    -u 10-20: ウマ 10-20 (ワンツー)
#    -b 100: ビンタ 100K円
#    -k 2: レート 1000点 2K円
#  2011(c) isao yasuda.
 
use strict;
use utf8;
use Getopt::Std;    # command line processing
use File::Basename; # get file basename
binmode(STDOUT, ":utf8"); binmode(STDERR, ":utf8");
 
# command line
my %opts = ('u' => "0", 'b' => 0, 'k' => 0);
Getopt::Std::getopts('u:b:k:', \%opts) ||
    die "Usage: " . basename($0) .
    " [ -u uma-range -b binta-k -k k-en ] \< (stdin)\n";
my $umai1 = 10; my $umai2 = 20; my $okai = 20;
my $uma1 = 0; my $uma2 = 0; my $oka = 20; my $kaesi = 30;
my $binta = 100; $binta = $opts{'b'} if ($opts{'b'});
my $krate = 2;   $krate = $opts{'k'} if ($opts{'k'});
if ($opts{'u'}) {
    unless ($opts{'u'} =~ /^[0-9]+-[0-9]+$/) {
        die "-u operand invalid.\n";
    }
    ($umai1, $umai2) = split(/-/, $opts{'u'}, 2);
    $uma1 = $umai1; $uma2 = $umai2;
}
if (($uma1 == 0) && ($uma2 == 0)) {
    $oka = 0; $kaesi = 0;
}
if ($oka != 0) {
    print STDERR "ウマ1: $uma1; ウマ2: $uma2; オカ: $oka(以上pt); ";
}
print STDERR "ビンタ: $binta(千円); レート: $krate(千円/1pt)\n";
my %totalpt = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # ポイント総計
my %totalmn = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # 金額総計
my %totalin = ('A' => 0, 'B' => 0, 'C' => 0, 'D' => 0); # 入力ポイント総計
print
    "----------------------------------------------------------------------\n";
print
    "  Input Points     ;  Calculated Points ;   Amount of Money (k-en)   ;\n";
print
    "   A    B    C    D;    A    B    C    D;      A      B      C      D;\n";
print
    "----------------------------------------------------------------------\n";
 
# Main
while (<STDIN>) {
    utf8::decode($_); chomp($_);
    my @tensu = split(/\s+/, $_, 4);
    my $sowa = $tensu[0] + $tensu[1] + $tensu[2] + $tensu[3];
    unless (($sowa == 100) || ($sowa == 0)) {
        print STDERR "点数総和 $sowa は矛盾します。無視します。\n";
        next;
    } else {
        if ($sowa == 100) { # 持ち点
            $oka = $okai; $uma1 = $umai1; $uma2 = $umai2; $kaesi = 30;
        } else {            # すでにオカ・ウマ計算済
            $oka = $uma1 = $uma2 = $kaesi = 0;
        }
    }
    # プレーヤ-点数 配列
    my %plpt = ('A'=>$tensu[0],'B'=>$tensu[1],'C'=>$tensu[2],'D'=>$tensu[3]);
    # input print
    printf("%+4d %+4d %+4d %+4d; ",
           $plpt{'A'}, $plpt{'B'}, $plpt{'C'}, $plpt{'D'});
    $totalin{'A'} += $tensu[0];
    $totalin{'B'} += $tensu[1];
    $totalin{'C'} += $tensu[2];
    $totalin{'D'} += $tensu[3];
    my @ptord; # 点数昇順リスト (last, pt4, pos3, pt3, .., top, pt1)
    foreach my $key (sort { $plpt{$a} <=> $plpt{$b} } keys %plpt) {
        push(@ptord, ($key, $plpt{$key}));
    }
    # ビンタ計算
    my @bbuff = (0, 0, 0, 0); # 点数やり取りバッファ
    # 4
    if ((($sowa == 100) && ($ptord[1] < 25)) ||
        (($sowa == 0) && (($ptord[1] + $umai2) < -5))) {
        # to 3
        if ((($sowa == 100) && ($ptord[3] < 25)) ||
            (($sowa == 0) && (($ptord[3] + $umai1) < -5))) {
            $bbuff[1] -= $binta; $bbuff[3] += $binta;
        } else {
            $bbuff[1] -= ($binta * 2); $bbuff[3] += ($binta * 2);
        }
        # to 2
        if ((($sowa == 100) && ($ptord[5] < 25)) ||
            (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
            $bbuff[1] -= $binta; $bbuff[5] += $binta;
        } else {
            $bbuff[1] -= ($binta * 2); $bbuff[5] += ($binta * 2);
        }
        # to 1
        if ((($sowa == 100) && ($ptord[7] < 25)) ||
            (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
            $bbuff[1] -= $binta; $bbuff[7] += $binta;
        } else {
            $bbuff[1] -= ($binta * 2); $bbuff[7] += ($binta * 2);
        }
    } else {
        # to 3
        $bbuff[1] -= $binta; $bbuff[3] += $binta;
        # to 2
        $bbuff[1] -= $binta; $bbuff[5] += $binta;
        # to 1
        $bbuff[1] -= $binta; $bbuff[7] += $binta;
    }
    # 3
    if ((($sowa == 100) && ($ptord[3] < 25)) ||
        (($sowa == 0) && (($ptord[3] + $umai1) < -5))) {
        # to 2
        if ((($sowa == 100) && ($ptord[5] < 25)) ||
            (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
            $bbuff[3] -= $binta; $bbuff[5] += $binta;
        } else {
            $bbuff[3] -= ($binta * 2); $bbuff[5] += ($binta * 2);
        }
        # to 1
        if ((($sowa == 100) && ($ptord[7] < 25)) ||
            (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
            $bbuff[3] -= $binta; $bbuff[7] += $binta;
        } else {
            $bbuff[3] -= ($binta * 2); $bbuff[7] += ($binta * 2);
        }
    } else {
        # to 2
        $bbuff[3] -= $binta; $bbuff[5] += $binta;
        # to 1
        $bbuff[3] -= $binta; $bbuff[7] += $binta;
    }
    # 2
    if ((($sowa == 100) && ($ptord[5] < 25)) ||
        (($sowa == 0) && (($ptord[5] - $umai1) < -5))) {
        # to 1
        if ((($sowa == 100) && ($ptord[7] < 25)) ||
            (($sowa == 0) && (($ptord[7] - $umai2) < -5))) {
            $bbuff[5] -= $binta; $bbuff[7] += $binta;
        } else {
            $bbuff[5] -= ($binta * 2); $bbuff[7] += ($binta * 2);
        }
    } else {
        # to 1
        $bbuff[5] -= $binta; $bbuff[7] += $binta;
    }
    # ポイント,ビンタ,レート計算
    # プレーヤ-点数 配列
    my %ptrslt = ($ptord[0]=>0, $ptord[2]=>0, $ptord[4]=>0, $ptord[6]=>0);
    # 4
    $ptord[1] -= $uma2; $ptord[1] -= $kaesi;
    $ptrslt{$ptord[0]} = $ptord[1];
    $ptord[1] = $ptord[1] * $krate; $ptord[1] += $bbuff[1];
    # 3
    $ptord[3] -= $uma1; $ptord[3] -= $kaesi;
    $ptrslt{$ptord[2]} = $ptord[3];
    $ptord[3] = $ptord[3] * $krate; $ptord[3] += $bbuff[3];
    # 2
    $ptord[5] += $uma1; $ptord[5] -= $kaesi;
    $ptrslt{$ptord[4]} = $ptord[5];
    $ptord[5] = $ptord[5] * $krate; $ptord[5] += $bbuff[5];
    # 1
    $ptord[7] += $uma2; $ptord[7] -= $kaesi; $ptord[7] += $oka;
    $ptrslt{$ptord[6]} = $ptord[7];
    $ptord[7] = $ptord[7] * $krate; $ptord[7] += $bbuff[7];
 
    # プレーヤ-金額 配列
    my %mnrslt = ($ptord[0] => $ptord[1], $ptord[2] => $ptord[3],
                  $ptord[4] => $ptord[5], $ptord[6] => $ptord[7]);
    printf("%+4d %+4d %+4d %+4d; ",
           $ptrslt{'A'}, $ptrslt{'B'}, $ptrslt{'C'}, $ptrslt{'D'});
    printf("%+6d %+6d %+6d %+6d;\n",
           $mnrslt{'A'}, $mnrslt{'B'}, $mnrslt{'C'},$mnrslt{'D'});
    foreach my $key (keys %ptrslt) {
        $totalpt{$key} += $ptrslt{$key}; $totalmn{$key} += $mnrslt{$key};
    }
}
 
# 総計出力
print
    "----------------------------------------------------------------------\n";
printf("%+4d %+4d %+4d %+4d; ",
       $totalin{'A'}, $totalin{'B'}, $totalin{'C'}, $totalin{'D'});
printf("%+4d %+4d %+4d %+4d; ",
       $totalpt{'A'}, $totalpt{'B'}, $totalpt{'C'}, $totalpt{'D'});
printf("%+6d %+6d %+6d %+6d;\n",
       $totalmn{'A'}, $totalmn{'B'}, $totalmn{'C'}, $totalmn{'D'});

※ 11.27 付記
最初の版には,ウキの判定とビンタ計算のバグがありました。訂正しました。

あと一点。同点者がいることを考慮してません。一局目の一巡目でいきなり役満放銃で一発ハコ一人沈みみたいな場合でない限り,こんなことはなかろうと思い,無視しました。