麻雀の点数計算ツール(JavaScript製)の解説

更新日:

当サイトでは初心者向けの麻雀の点数計算ツールを提供しています。今回はその麻雀の点数計算ツールの仕組みを解説します。

目次

点数計算の方法について

世の中には様々な麻雀の点数計算ツールがあります。

  • カメラで撮影してもらう
  • 牌を選択してもらう
  • 質問に回答してもらう

当サイトで提供しているのは、一番下の質問に回答してもらう方法です。この方法ならHTMLとCSS、JavaScript(jQuery)で制作することができます。

制作に必要な知識について

麻雀の点数計算ツールを制作する上で必要な知識は、

  • HTML
  • CSS
  • JavaScript(jQuery)
  • 麻雀の役、符数、翻数
  • 麻雀の点数早見表

です。点数計算の式を知らなくても、点数早見表を知っていれば大丈夫です。ただし参考にする点数早見表に誤りがないことが前提です。

それではここからは、麻雀の点数計算を行うためのJavaScriptのコードを解説しながら紹介します。

クラスを定義する

使用する関数が多いため、クラスを定義してメソッドを作成します。

class Mahjong {

}

参考:クラス – JavaScript | MDN

メインのメソッドと質問の定数を定義する

クラスを定義したら、点数計算のためのメインのメソッドと質問の定数(buttonタグのname属性の値と同じ)を定義します。

/**
 * 点数計算
 */
pointCalc() {
    const ITEMS = {
        'cha': '',           // 何家ですか?
        'bakaze': '',        // 何場ですか?
        'honba': '',         // 何本場ですか?
        'daburi': '',        // 一巡目でリーチ(ダブルリーチ)していますか?
        'richi': '',         // 2巡目以降でリーチしていますか?
        'ippatsu': '',       // リーチ後の最初のツモでツモアガリ、またはリーチ後に自分の番が来るまでにロンアガリしましたか?
        'agari': '',         // ツモアガリとロンアガリどちらですか?
        'shuntsu': '',       // 連続した3つの数牌(順子)はいくつありますか?
        'janto': '',         // 雀頭(2枚1組の形)は何ですか?
        'dora': '',          // ドラは何枚ありますか?
        'tanyao': '',        // 手牌は2~8の数牌だけですか?
        'machi': '',         // アガリ牌の待ちの形は何ですか?
        'ipeko': '',         // 同じ順序の形はいくつありますか?
        'furo': '',          // チー、ポン、明槓をしていますか?
        'chitoitsu': '',     // 同じ牌が2枚1組(対子)で7つありますか?
        'sanshokudojun': '', // 3色で同じ順序の形はありますか?
        'sanshokudoko': '',  // 3色で3枚1組の形はありますか?
        'yakuhai_haku': '',  // 「白」が3枚以上ありますか?
        'yakuhai_hatsu': '', // 「發」が3枚以上ありますか?
        'yakuhai_chun': '',  // 「中」が3枚以上ありますか?
        'yakuhai_ton': '',   // 「東」が3枚以上ありますか?
        'yakuhai_nan': '',   // 「南」が3枚以上ありますか?
        'yakuhai_sha': '',   // 「西」が3枚以上ありますか?
        'yakuhai_pe': '',    // 「北」が3枚以上ありますか?
        'honitsu': '',       // 同じ色の数牌と字牌だけですか?
        'chinitsu': '',      // 全て同じ種類ですか?
        'sananko': '',       // 手牌に同じ牌が3枚(暗刻)3組がありますか?
        'toitoi': '',        // 同じ牌が3枚1組(刻子)で4つありますか?
        'ikkitsukan': '',    // 同じ色の1~9の数牌が全て揃っていますか?
        'chanta': '',        // 1~3の順子、7~9の順子、19の数牌、字牌だけですか?
        'junchan': '',       // 1~3の順子、7~9の順子、19の数牌だけですか?
        'haitei': '',        // 山に残っている最後の牌(海底)でツモアガリしましたか?
        'hotei': '',         // 局の最後の捨て牌(河底)でロンアガリしましたか?
        'rinshankaiho': '',  // 槓してツモった牌(嶺上牌)でアガリしましたか?
        'shosangen': '',     // 「白」「發」「中」が2枚1組、3枚(4枚)2組ありますか?
        'honroto': '',       // 19の数牌と字牌だけですか?
        'chankan': '',       // 他の人が加槓しようとした牌でアガリましたか?
        'sankantsu': '',     // 槓を3回しましたか?
        'minko_yaochu': '',  // 1または9の数牌と字牌の明刻はいくつありますか?
        'anko_yaochu': '',   // 1または9の数牌と字牌の暗刻はいくつありますか?
        'minko_tanyao': '',  // 2~8の数牌の明刻はいくつありますか?
        'anko_tanyao': '',   // 2~8の数牌の暗刻はいくつありますか?
        'minkan_yaochu': '', // 1または9の数牌と字牌の明槓はいくつありますか?
        'ankan_yaochu': '',  // 1または9の数牌と字牌の暗槓はいくつありますか?
        'minkan_tanyao': '', // 2~8の数牌の明槓はいくつありますか?
        'ankan_tanyao': '',  // 2~8の数牌の暗槓はいくつありますか?
    };
}

参考:const – JavaScript | MDN

押されたbuttonタグのvalue属性の値を、定数の該当のキーの値として格納する

回答時に押されるbuttonタグのname属性とvalue属性には、点数計算に必要な値がそれぞれ設定されています。それを$.eachで反復処理して、定数の該当のキーの値として格納します。

buttonタグのvalue属性の値
$.each(ITEMS, (key, val) => {
    if ($('[name="' + key + '"].selected').attr('value') !== undefined) {
        ITEMS[key] = $('[name="' + key + '"].selected').attr('value');
    }
});

buttonタグが押された時にselectedというHTMLのクラスが付与されるようにしておきます。解説の都合上、付与するためのコードは省略します。

参考:jQuery.each() | jQuery API Documentation
参考:.attr() | jQuery API Documentation
参考:undefined – JavaScript | MDN

点数計算に使用する変数を定義する

点数計算に使用する変数を定義します。

let result = ''; // 結果
let fu = 20;     // 符数
let han = 0;     // 翻数
let point = '0'; // 点数
let YAKU = [];   // 役

参考:let – JavaScript | MDN

役を判定するメソッドを定義する

役満以外の役を判定するメソッドを定義し、符数と翻数、役を先ほど定義した変数に格納します。

  • ここでは「么(公に似た漢字)」は「ヤオ」と表記します。
  • 副露判定のメソッドが頻出しますが、定義するのは一度だけです。
  • 名前のある役満には対応していません。

リーチ、ダブルリーチ(通称ダブリー)、一発

リーチ、ダブルリーチ、一発
// リーチ
if (!this.isDaburi(ITEMS) && this.isRichi(ITEMS)) {
    han += 1;
    YAKU.push('リーチ');

    // 一発
    if (this.isIppatsu(ITEMS)) {
        han += 1;
        YAKU.push('一発');
    }
}

// ダブルリーチ
if (this.isDaburi(ITEMS)) {
    han += 2;
    YAKU.push('ダブルリーチ');

    // 一発
    if (this.isIppatsu(ITEMS)) {
        han += 1;
        YAKU.push('一発');
    }
}

/**
 * リーチ判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isRichi(ITEMS) {
    if (ITEMS.richi === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * ダブルリーチ判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isDaburi(ITEMS) {
    if (ITEMS.daburi === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 一発判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isIppatsu(ITEMS) {
    if (ITEMS.ippatsu === 'はい') {
        return true;
    } else {
        return false;
    }
}

参考:Array.prototype.push() – JavaScript | MDN
参考:this – JavaScript | MDN

門前清自摸和(メンゼンチンツモホー)(通称ツモ)

門前役のため副露判定も行います。

門前清自摸和
// 門前清自摸和
if (ITEMS.agari === 'ツモ') {
    if (!this.isFuro(ITEMS)) {
        han += 1;
        YAKU.push('門前清自摸和');
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

平和(ピンフ)

門前役のため副露判定も行います。

平和
// 平和
if (this.isPinfu(ITEMS)) {
    han += 1;
    YAKU.push('平和');
}

/**
 * 平和判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isPinfu(ITEMS) {
    if (ITEMS.shuntsu === '4' && ITEMS.machi === 'リャンメン' && !this.isFuro(ITEMS)) {
        if (ITEMS.janto !== '' && ITEMS.janto !== '場風牌または自風牌' && ITEMS.janto !== '白または發または中') {
            return true;
        } else {
            return false;
        }
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

七対子(チートイツ)

七対子の上位の役である二盃口が優先されるように、二盃口判定も行います。選択項目を間違っている場合のために副露判定も行います。

七対子
// 七対子
if (this.isChitoitsu(ITEMS)) {
    han += 2;
    YAKU.push('七対子');
}

/**
 * 七対子判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isChitoitsu(ITEMS) {
    if (ITEMS.chitoitsu === 'はい' && ITEMS.ipeko !== '2' && !this.isFuro(ITEMS)) {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

断ヤオ九(タンヤオチュー)(通称タンヤオ)

断ヤオ九
// 断ヤオ九
if (this.isTanyao(ITEMS)) {
    han += 1;
    YAKU.push('断ヤオ九');
}

/**
 * 断ヤオ九判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isTanyao(ITEMS) {
    if (ITEMS.tanyao === 'はい') {
        return true;
    } else {
        return false;
    }
}

一盃口(イーペーコー)

七対子と複合しないため七対子判定も行います。門前役のため副露判定も行います。

一盃口
// 一盃口
if (this.isIpeko(ITEMS)) {
    han += 1;
    YAKU.push('一盃口');
}

/**
 * 一盃口判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isIpeko(ITEMS) {
    if (ITEMS.ipeko === '1' && !this.isChitoitsu(ITEMS) && !this.isFuro(ITEMS)) {
        return true;
    } else {
        return false;
    }
}

/**
 * 七対子判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isChitoitsu(ITEMS) {
    if (ITEMS.chitoitsu === 'はい' && ITEMS.ipeko !== '2' && !this.isFuro(ITEMS)) {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

二盃口(リャンペーコー)

門前役のため副露判定も行います。

二盃口
// 二盃口
if (this.isRyanpeko(ITEMS)) {
    han += 3;
    YAKU.push('二盃口');
}

/**
 * 二盃口判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isRyanpeko(ITEMS) {
    if (ITEMS.ipeko === '2' && !this.isFuro(ITEMS)) {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

三色同順(サンショクドウジュン)

食い下がりによって翻数が落ちるため副露判定も行います。

三色同順
// 三色同順
if (this.isSanshokudojun(ITEMS)) {
    if (this.isFuro(ITEMS)) {
        han += 1;
    } else {
        han += 2;
    }

    YAKU.push('三色同順');
}

/**
 * 三色同順判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isSanshokudojun(ITEMS) {
    if (ITEMS.sanshokudojun === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

三色同刻(サンショクドウコウ)

三色同刻
// 三色同刻
if (this.isSanshokudoko(ITEMS)) {
    han += 2;
    YAKU.push('三色同刻');
}

/**
 * 三色同刻判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isSanshokudoko(ITEMS) {
    if (ITEMS.sanshokudoko === 'はい') {
        return true;
    } else {
        return false;
    }
}

役牌(白)(ハク)

役牌(白)
// 役牌(白)
if (this.isYakuhaiHaku(ITEMS)) {
    han += 1;
    YAKU.push('役牌(白)');
}

/**
 * 役牌(白)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiHaku(ITEMS) {
    if (ITEMS.yakuhai_haku === 'はい') {
        return true;
    } else {
        return false;
    }
}

役牌(發)(ハツ)

役牌(發)
// 役牌(發)
if (this.isYakuhaiHatsu(ITEMS)) {
    han += 1;
    YAKU.push('役牌(發)');
}

/**
 * 役牌(發)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiHatsu(ITEMS) {
    if (ITEMS.yakuhai_hatsu === 'はい') {
        return true;
    } else {
        return false;
    }
}

役牌(中)(チュン)

役牌(中)
// 役牌(中)
if (this.isYakuhaiChun(ITEMS)) {
    han += 1;
    YAKU.push('役牌(中)');
}

/**
 * 役牌(中)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiChun(ITEMS) {
    if (ITEMS.yakuhai_chun === 'はい') {
        return true;
    } else {
        return false;
    }
}

役牌(自風)(ジカゼ)

役牌(自風)
// 役牌(自風)
if (
    this.isYakuhaiTon(ITEMS)
    || this.isYakuhaiNan(ITEMS)
    || this.isYakuhaiSha(ITEMS)
    || this.isYakuhaiPe(ITEMS)
) {
    han += 1;
    YAKU.push('役牌(自風)');
}

/**
 * 役牌(東)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiTon(ITEMS) {
    if (
        (ITEMS.yakuhai_ton === 'はい')
        && ITEMS.cha === '東'
    ) {
        return true;
    } else {
        return false;
    }
}

/**
 * 役牌(南)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiNan(ITEMS) {
    if (
        (ITEMS.yakuhai_nan === 'はい')
        && ITEMS.cha === '南'
    ) {
        return true;
    } else {
        return false;
    }
}

/**
 * 役牌(西)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiSha(ITEMS) {
    if (
        (ITEMS.yakuhai_sha === 'はい')
        && ITEMS.cha === '西'
    ) {
        return true;
    } else {
        return false;
    }
}

/**
 * 役牌(北)判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiPe(ITEMS) {
    if (
        (ITEMS.yakuhai_pe === 'はい')
        && ITEMS.cha === '北'
    ) {
        return true;
    } else {
        return false;
    }
}

役牌(場風)(バカゼ)

役牌(場風)
// 役牌(場風)
if (this.isYakuhaiBa(ITEMS)) {
    han += 1;
    YAKU.push('役牌(場風)');
}

/**
 * 場の役牌判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isYakuhaiBa(ITEMS) {
    if (
        (ITEMS.bakaze === '東' && ITEMS.yakuhai_ton === 'はい')
        || (ITEMS.bakaze === '南' && ITEMS.yakuhai_nan === 'はい')
        || (ITEMS.bakaze === '西' && ITEMS.yakuhai_sha === 'はい')
        || (ITEMS.bakaze === '北' && ITEMS.yakuhai_pe === 'はい')
    ) {
        return true;
    } else {
        return false;
    }
}

混一色(ホンイツ)

食い下がりによって翻数が落ちるため副露判定も行います。

混一色
// 混一色
if (this.isHonitsu(ITEMS)) {
    if (this.isFuro(ITEMS)) {
        han += 2;
    } else {
        han += 3;
    }

    YAKU.push('混一色');
}

/**
 * 混一色判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isHonitsu(ITEMS) {
    if (ITEMS.honitsu === 'はい') {
        return true;
    } else {
        return false;
    }
}

清一色(チンイツ)

食い下がりによって翻数が落ちるため副露判定も行います。

清一色
// 清一色
if (this.isChinitsu(ITEMS)) {
    if (this.isFuro(ITEMS)) {
        han += 5;
    } else {
        han += 6;
    }

    YAKU.push('清一色');
}

/**
 * 清一色判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isChinitsu(ITEMS) {
    if (ITEMS.chinitsu === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

対々和(トイトイホー)(通称トイトイ)

対々和
// 対々和
if (this.isToitoi(ITEMS)) {
    han += 2;
    YAKU.push('対々和');
}

/**
 * 対々和判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isToitoi(ITEMS) {
    if (ITEMS.toitoi === 'はい') {
        return true;
    } else {
        return false;
    }
}

一気通貫(イッキツウカン)(通称イッツー)

食い下がりによって翻数が落ちるため副露判定も行います。

一気通貫
// 一気通貫
if (this.isIkkitsukan(ITEMS)) {
    if (this.isFuro(ITEMS)) {
        han += 1;
    } else {
        han += 2;
    }

    YAKU.push('一気通貫');
}

/**
 * 一気通貫判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isIkkitsukan(ITEMS) {
    if (ITEMS.ikkitsukan === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

混全帯ヤオ九(ホンチャンタイヤオチュウ)(通称チャンタ)

食い下がりによって翻数が落ちるため副露判定も行います。

混全帯ヤオ九
// 混全帯ヤオ九
if (this.isChanta(ITEMS)) {
    if (this.isFuro(ITEMS)) {
        han += 1;
    } else {
        han += 2;
    }

    YAKU.push('混全帯ヤオ九');
}

/**
 * 混全帯ヤオ九判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isChanta(ITEMS) {
    if (ITEMS.chanta === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

純全帯ヤオ九(ジュンチャンタイヤオチュー)(通称ジュンチャン)

食い下がりによって翻数が落ちるため副露判定も行います。

純全帯ヤオ九
// 純全帯ヤオ九
if (this.isJunchan(ITEMS)) {
    if (this.isFuro(ITEMS)) {
        han += 2;
    } else {
        han += 3;
    }

    YAKU.push('純全帯ヤオ九');
}

/**
 * 純全帯ヤオ九判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isJunchan(ITEMS) {
    if (ITEMS.junchan === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 副露判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isFuro(ITEMS) {
    if (ITEMS.furo === 'はい') {
        return true;
    } else {
        return false;
    }
}

三暗刻(サンアンコウ)

三暗刻
// 三暗刻
if (this.isSananko(ITEMS)) {
    han += 2;
    YAKU.push('三暗刻');
}

/**
 * 三暗刻判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isSananko(ITEMS) {
    if (ITEMS.sananko === 'はい') {
        return true;
    } else {
        return false;
    }
}

海底撈月(ハイテイラオユエ)(通称ハイテイ)、河底撈魚(ホウテイラオユイ)(通称ホウテイ)、嶺上開花(リンシャンカイホウ)、搶槓(チャンカン)

アガる時はいずれか1つなのでまとめています。

海底撈月、河底撈魚、嶺上開花、搶槓
// 海底撈月
if (this.isHaitei(ITEMS)) {
    han += 1;
    YAKU.push('海底撈月');
// 河底撈魚
} else if (this.isHotei(ITEMS)) {
    han += 1;
    YAKU.push('河底撈魚');
// 嶺上開花
} else if (this.isRinshankaiho(ITEMS)) {
    han += 1;
    YAKU.push('嶺上開花');
// 搶槓
} else if (this.isChankan(ITEMS)) {
    han += 1;
    YAKU.push('搶槓');
}

/**
 * 海底撈月判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isHaitei(ITEMS) {
    if (ITEMS.haitei === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 河底撈魚判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isHotei(ITEMS) {
    if (ITEMS.hotei === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 嶺上開花判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isRinshankaiho(ITEMS) {
    if (ITEMS.rinshankaiho === 'はい') {
        return true;
    } else {
        return false;
    }
}

/**
 * 搶槓判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isChankan(ITEMS) {
    if (ITEMS.chankan === 'はい') {
        return true;
    } else {
        return false;
    }
}

混老頭(ホンロウトウ)

混老頭
// 混老頭
if (this.isHonroto(ITEMS)) {
    han += 2;
    YAKU.push('混老頭');
}

/**
 * 混老頭判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isHonroto(ITEMS) {
    if (ITEMS.honroto === 'はい') {
        return true;
    } else {
        return false;
    }
}

三槓子(サンカンツ)

三槓子
// 三槓子
if (this.isSankantsu(ITEMS)) {
    han += 2;
    YAKU.push('三槓子');
}

/**
 * 三槓子判定
 *
 * @param ITEMS[object]
 * @return bool
 */
isSankantsu(ITEMS) {
    if (ITEMS.sankantsu === 'はい') {
        return true;
    } else {
        return false;
    }
}

符を計算するメソッドを定義する

次は符計算を行うメソッドを定義します。

/**
 * 符計算
 *
 * @param YAKU[array]
 * @param ITEMS[object]
 * @return int
 */
fuCalc(YAKU, ITEMS) {
    let fu = 20;

    // アガリ
    if (ITEMS.agari === 'ツモ') {
        fu += 2;
    } else if (ITEMS.agari === 'ロン' && !this.isFuro(ITEMS)) {
        fu += 10;
    }

    if (YAKU.indexOf('門前清自摸和') !== -1 && YAKU.indexOf('平和') !== -1) {
        fu = 20;
    } else if (YAKU.indexOf('七対子') !== -1) {
        fu = 25;
    } else {
        if (ITEMS.machi !== '' && ITEMS.machi !== 'リャンメン' && ITEMS.machi !== 'シャンポン') {
            fu += 2;
        }

        if (ITEMS.janto !== '' && ITEMS.janto !== '2~8の数牌') {
            fu += 2;
        }

        if (ITEMS.minko_yaochu !== '') {
            fu += (4 * parseInt(ITEMS.minko_yaochu));
        }

        if (ITEMS.anko_yaochu !== '') {
            fu += (8 * parseInt(ITEMS.anko_yaochu));
        }

        if (ITEMS.minko_tanyao !== '') {
            fu += (2 * parseInt(ITEMS.minko_tanyao));
        }

        if (ITEMS.anko_tanyao !== '') {
            fu += (4 * parseInt(ITEMS.anko_tanyao));
        }

        if (ITEMS.minkan_yaochu !== '') {
            fu += (16 * parseInt(ITEMS.minkan_yaochu));
        }

        if (ITEMS.ankan_yaochu !== '') {
            fu += (32 * parseInt(ITEMS.ankan_yaochu));
        }

        if (ITEMS.minkan_tanyao !== '') {
            fu += (8 * parseInt(ITEMS.minkan_tanyao));
        }

        if (ITEMS.ankan_tanyao !== '') {
            fu += (16 * parseInt(ITEMS.ankan_tanyao));
        }

        fu = Math.ceil(fu / 10) * 10;
    }

    if (fu === 20 && ITEMS.agari === 'ロン' && this.isFuro(ITEMS)) {
        fu = 30;
    }

    return fu;
}

コードを順番に解説します。

符数を格納する変数を定義する

符数は20から始まるため、fuという変数に20を入れておきます。これを役などに合わせて加算していきます。

let fu = 20;

アガリ方によって符数を加算する

ツモアガリの場合は符数を+2、ロンアガリで副露をしていない場合は符数を+10します。

アガリ方によって符数を加算する
// アガリ
if (ITEMS.agari === 'ツモ') {
    fu += 2;
} else if (ITEMS.agari === 'ロン' && !this.isFuro(ITEMS)) {
    fu += 10;
}

役によってベースの符数を設定する

平和と七対子は符数が固定されている役です。平和の場合は符数が20、七対子の場合は符数が25です。

if (YAKU.indexOf('門前清自摸和') !== -1 && YAKU.indexOf('平和') !== -1) {
    fu = 20;
} else if (YAKU.indexOf('七対子') !== -1) {
    fu = 25;
} else {
    ~
}

参考:String.prototype.indexOf() – JavaScript | MDN

アガリ牌の待ちや刻子の有無によって符数を加算する

上記の6行目のコードです。

アガリ牌の待ちが単騎、カンチャン、ペンチャン、ノベタンのいずれかの場合は、符数を+2します。

アガリ牌の待ち
if (ITEMS.machi !== '' && ITEMS.machi !== 'リャンメン' && ITEMS.machi !== 'シャンポン') {
    fu += 2;
}

雀頭が三元牌または連風牌の対子の場合は、符数を+2します。ルールによっては連風牌の対子は4符ですが、当サイトでは2符を採用しています。

雀頭
if (ITEMS.janto !== '' && ITEMS.janto !== '2~8の数牌' && ITEMS.janto !== '1または9の数牌' && ITEMS.janto !== 'その他') {
    fu += 2;
}

1または9の数牌または字牌の明刻がある場合は、選択されたボタンの数に合わせて符数を+4します。

1または9の数牌または字牌の明刻
if (ITEMS.minko_yaochu !== '') {
    fu += (4 * parseInt(ITEMS.minko_yaochu));
}

参考:parseInt() – JavaScript | MD

1または9の数牌または字牌の暗刻がある場合は、選択されたボタンの数に合わせて符数を+8します。

1または9の数牌または字牌の暗刻
if (ITEMS.anko_yaochu !== '') {
    fu += (8 * parseInt(ITEMS.anko_yaochu));
}

2~8の数牌の明刻がある場合は、選択されたボタンの数に合わせて符数を+2します。

2~8の数牌の明刻
if (ITEMS.minko_tanyao !== '') {
    fu += (2 * parseInt(ITEMS.minko_tanyao));
}

2~8の数牌の暗刻がある場合は、選択されたボタンの数に合わせて符数を+4します。

2~8の数牌の暗刻
if (ITEMS.anko_tanyao !== '') {
    fu += (4 * parseInt(ITEMS.anko_tanyao));
}

1または9の数牌または字牌の明槓がある場合は、選択されたボタンの数に合わせて符数を+16します。

1または9の数牌または字牌の明槓
if (ITEMS.minkan_yaochu !== '') {
    fu += (16 * parseInt(ITEMS.minkan_yaochu));
}

1または9の数牌または字牌の暗槓がある場合は、選択されたボタンの数に合わせて符数を+32します。

1または9の数牌または字牌の暗槓
if (ITEMS.ankan_yaochu !== '') {
    fu += (32 * parseInt(ITEMS.ankan_yaochu));
}

2~8の数牌の明槓がある場合は、選択されたボタンの数に合わせて符数を+8します。

2~8の数牌の明槓
if (ITEMS.minkan_tanyao !== '') {
    fu += (8 * parseInt(ITEMS.minkan_tanyao));
}

2~8の数牌の暗槓がある場合は、選択されたボタンの数に合わせて符数を+16します。

2~8の数牌の暗槓
if (ITEMS.ankan_tanyao !== '') {
    fu += (16 * parseInt(ITEMS.ankan_tanyao));
}

最後に符数を切り上げます。

一の位で切り上げるので、10で割って小数第一位で切り上げて、10を掛けています。

fu = Math.ceil(fu / 10) * 10;

参考:Math.ceil() – JavaScript | MDN

20符かつロンアガリの場合は30符

上記のどれにも当てはまらずに20符のままでロンアガリの場合は、符数を30に設定します。

if (fu === 20 && ITEMS.agari === 'ロン' && this.isFuro(ITEMS)) {
    fu = 30;
}

計算ができたら符数を返します。

return fu;

符数と翻数から点数を計算

次に符数と翻数から点数を計算します。

符数と翻数から点数を計算するメソッドを定義する

符数と翻数から点数を計算するメソッドを定義します。このメソッドでは引数として符数、翻数、本場、アガリ方、家を使用します。

/**
 * 符数と翻数から点数計算
 *
 * @param fu[int]
 * @param han[int]
 * @param honba[string]
 * @param agari[string]
 * @param cha[string]
 * @return string
 */
pointCalcFromHanFu(fu, han, honba, agari, cha) {
    ~
}

使用する変数を定義する

まずは使用する変数を定義します。

let point;
let chaType = '子';
honba = honba !== '' ? honba : 0; 
let kyotaku = 300 * parseInt(honba);

参考:条件 (三項) 演算子 – JavaScript | MDN

親と子の符数と翻数別の点数を設定する

次に点数早見表の、親と子の符数と翻数別の点数を定数に格納します。満貫以上は含めません。

const POINT = {
    '親': {
        'ツモ': {
            20: {
                2: 700,
                3: 1300,
                4: 2600
            },
            25: {
                2: 800,
                3: 1600,
                4: 3200
            },
            30: {
                1: 500,
                2: 1000,
                3: 2000,
                4: 3900
            },
            40: {
                1: 700,
                2: 1300,
                3: 2600
            },
            50: {
                1: 800,
                2: 1600,
                3: 3200
            },
            60: {
                1: 1000,
                2: 2000,
                3: 3900
            },
            70: {
                1: 1200,
                2: 2300
            },
            80: {
                1: 1300,
                2: 2600
            },
            90: {
                1: 1500,
                2: 2900
            },
            100: {
                1: 1600,
                2: 3200
            },
            110: {
                1: 1800,
                2: 3600
            }
        },
        'ロン': {
            25: {
                2: 2400,
                3: 4800,
                4: 9600
            },
            30: {
                1: 1500,
                2: 2900,
                3: 5800,
                4: 11600
            },
            40: {
                1: 2000,
                2: 3900,
                3: 7700
            },
            50: {
                1: 2400,
                2: 4800,
                3: 9600
            },
            60: {
                1: 2900,
                2: 5800,
                3: 11600
            },
            70: {
                1: 3400,
                2: 6800
            },
            80: {
                1: 3900,
                2: 7700
            },
            90: {
                1: 4400,
                2: 8700
            },
            100: {
                1: 4800,
                2: 9600
            },
            110: {
                1: 5300,
                2: 10600
            }
        }
    },
    '子': {
        'ツモ': {
            20: {
                2: [400, 700],
                3: [700, 1300],
                4: [1300, 2600]
            },
            25: {
                2: [400, 800],
                3: [800, 1600],
                4: [1600, 3200]
            },
            30: {
                1: [300, 500],
                2: [500, 1000],
                3: [1000, 2000],
                4: [2000, 3900]
            },
            40: {
                1: [400, 700],
                2: [700, 1300],
                3: [1300, 2600]
            },
            50: {
                1: [400, 800],
                2: [800, 1600],
                3: [1600, 3200]
            },
            60: {
                1: [500, 1000],
                2: [1000, 2000],
                3: [2000, 3900]
            },
            70: {
                1: [600, 1200],
                2: [1200, 2300]
            },
            80: {
                1: [700, 1300],
                2: [1300, 2600]
            },
            90: {
                1: [800, 1500],
                2: [1500, 2900]
            },
            100: {
                1: [800, 1600],
                2: [1600, 3200]
            },
            110: {
                1: [900, 1800],
                2: [1800, 3600]
            }
        },
        'ロン': {
            25: {
                2: 1600,
                3: 3200,
                4: 6400
            },
            30: {
                1: 1000,
                2: 2000,
                3: 3900,
                4: 7700
            },
            40: {
                1: 1300,
                2: 2600,
                3: 5200
            },
            50: {
                1: 1600,
                2: 3200,
                3: 6400
            },
            60: {
                1: 2000,
                2: 3900,
                3: 7700
            },
            70: {
                1: 2300,
                2: 4500
            },
            80: {
                1: 2600,
                2: 5200
            },
            90: {
                1: 2900,
                2: 5800
            },
            100: {
                1: 3200,
                2: 6400
            },
            110: {
                1: 3600,
                2: 7100
            }
        }
    }
};

家を判定して親を設定する

次に家が東の場合は親にします。

if (cha === '東') {
    chaType = '親';
}

点数を変数に格納する

親か子か、アガリ方、符数、翻数の情報を使用して、変数に点数早見表の点数を格納します。

point = POINT[chaType][agari][fu][han];

満貫以上の点数を変数に格納する

上記では満貫未満までしか対応していないため、満貫、跳満、倍満、3倍満、数え役満の条件に一致するかを判定して、その点数を変数に格納します。

if (chaType === '親') {
    if (
        han === 5
        || (fu >= 40 && fu <= 60 && han >= 4 && han <= 5)
        || (fu >= 70 && han >= 3 && han <= 5)
    ) {
        if (agari === 'ツモ') {
            point = 4000;
        } else {
            point = 12000;
        }
    } else if (han >= 6 && han <= 7) {
        if (agari === 'ツモ') {
            point = 6000;     
        } else {
            point = 18000;
        }
    } else if (han >= 8 && han <= 10) {
        if (agari === 'ツモ') {
            point = 8000;
        } else {
            point = 24000;
        }
    } else if (han >= 11 && han <= 12) {
        if (agari === 'ツモ') {
            point = 12000;
        } else {
            point = 36000;
        }
    } else if (han >= 13) {
        if (agari === 'ツモ') {
            point = 16000;
        } else {
            point = 48000;
        }
    }
} else if (chaType === '子') {
    if (
        han === 5
        || (fu >= 40 && fu <= 60 && han >= 4 && han <= 5)
        || (fu >= 70 && han >= 3 && han <= 5)
    ) {
        if (agari === 'ツモ') {
            point = [2000, 4000];
        } else {
            point = 8000;
        }
    } else if (han >= 6 && han <= 7) {
        if (agari === 'ツモ') {
            point = [3000, 6000];     
        } else {
            point = 12000;
        }
    } else if (han >= 8 && han <= 10) {
        if (agari === 'ツモ') {
            point = [4000, 8000];
        } else {
            point = 16000;
        }
    } else if (han >= 11 && han <= 12) {
        if (agari === 'ツモ') {
            point = [6000, 12000];
        } else {
            point = 24000;
        }
    } else if (han >= 13) {
        if (agari === 'ツモ') {
            point = [8000, 16000];
        } else {
            point = 32000;
        }
    }
}

上記コードを順番に順番に順番に解説します。

下記コードは、親で5翻、または40符以上かつ60符以下かつ4翻以上かつ5翻以下の場合、または70符以上かつ3翻以上かつ5翻以下の場合に、ツモアガリなら4000(オール)、ロンアガリなら12000を変数に格納しています。

if (chaType === '親') {
    if (
        han === 5
        || (fu >= 40 && fu <= 60 && han >= 4 && han <= 5)
        || (fu >= 70 && han >= 3 && han <= 5)
    ) {
        if (agari === 'ツモ') {
            point = 4000;
        } else {
            point = 12000;
        }
    } ~
}

下記コードは、親で6翻以上かつ7翻以下の場合に、ツモアガリなら6000(オール)、ロンアガリなら18000を変数に格納しています。

if (chaType === '親') {
    ~
    } else if (han >= 6 && han <= 7) {
        if (agari === 'ツモ') {
            point = 6000;     
        } else {
            point = 18000;
        }
    } ~
}

下記コードは、親で8翻以上かつ10翻以下の場合に、ツモアガリなら8000(オール)、ロンアガリなら24000を変数に格納しています。

if (chaType === '親') {
    ~
    } else if (han >= 8 && han <= 10) {
        if (agari === 'ツモ') {
            point = 8000;
        } else {
            point = 24000;
        }
    } ~
}

下記コードは、親で11翻以上かつ12翻以下の場合に、ツモアガリなら12000(オール)、ロンアガリなら36000を変数に格納しています。

if (chaType === '親') {
    ~
    } else if (han >= 11 && han <= 12) {
        if (agari === 'ツモ') {
            point = 12000;
        } else {
            point = 36000;
        }
    } ~
}

下記コードは、親で13翻以上の場合に、ツモアガリなら16000(オール)、ロンアガリなら48000を変数に格納しています。

if (chaType === '親') {
    ~
    } else if (han >= 13) {
        if (agari === 'ツモ') {
            point = 16000;
        } else {
            point = 48000;
        }
    }
}

下記コードは、子で5翻、または40符以上かつ60符以下かつ4翻以上かつ5翻以下の場合、または70符以上かつ3翻以上かつ5翻以下の場合に、ツモアガリなら2000/4000、ロンアガリなら8000を変数に格納しています。子のツモアガリは、親と子で支払ってもらう点数が違うため配列にして格納します。

~
} else if (chaType === '子') {
    if (
        han === 5
        || (fu >= 40 && fu <= 60 && han >= 4 && han <= 5)
        || (fu >= 70 && han >= 3 && han <= 5)
    ) {
        if (agari === 'ツモ') {
            point = [2000, 4000];
        } else {
            point = 8000;
        }
    } ~
}

下記コードは、子で6翻以上かつ7翻以下の場合に、ツモアガリなら3000/6000、ロンアガリなら12000を変数に格納しています。

~
} else if (chaType === '子') {
    ~
    } else if (han >= 6 && han <= 7) {
        if (agari === 'ツモ') {
            point = [3000, 6000];     
        } else {
            point = 12000;
        }
    } ~
}

下記コードは、子で8翻以上かつ10翻以下の場合に、ツモアガリなら4000/8000、ロンアガリなら16000を変数に格納しています。

~
} else if (chaType === '子') {
    ~
    } else if (han >= 8 && han <= 10) {
        if (agari === 'ツモ') {
            point = [4000, 8000];
        } else {
            point = 16000;
        }
    } ~
}

下記コードは、子で11翻以上かつ12翻以下の場合に、ツモアガリなら6000/12000、ロンアガリなら24000を変数に格納しています。

~
} else if (chaType === '子') {
    ~
    } else if (han >= 11 && han <= 12) {
        if (agari === 'ツモ') {
            point = [6000, 12000];
        } else {
            point = 24000;
        }
    } ~
}

下記コードは、子で13翻以上の場合に、ツモアガリなら8000/16000、ロンアガリなら32000を変数に格納しています。

~
} else if (chaType === '子') {
    ~
    } else if (han >= 13) {
        if (agari === 'ツモ') {
            point = [8000, 16000];
        } else {
            point = 32000;
        }
    }
}

供託されているリーチ棒の点数を加算する

最後にリーチ後に局が流れて供託されているリーチ棒の点数を加算します。

本場
if (agari === 'ツモ') {
    // 供託を3等分
    kyotaku = parseInt(kyotaku) / 3;

    if (chaType === '親') {
        // 供託がある場合
        if (kyotaku !== 0) {
            point = point + 'は' + (parseInt(point) + parseInt(kyotaku)) + 'オール';
        } else {
            point = point + 'オール';
        }
    } else {
        // 供託がある場合
        if (kyotaku !== 0) {
            point = point[0] + '/' + point[1] + 'は' + (point[0] + kyotaku) + '/' + (point[1] + kyotaku);
        } else {
            point = point[0] + '/' + point[1];
        }
    }
} else {
    if (kyotaku !== 0) {
        point = point + 'は' + (parseInt(point) + parseInt(kyotaku));
    }
}

上記コードを順番に解説します。

下記コードは、ツモアガリの場合に供託を3等分しています。これは他家の3人にそれぞれ支払ってもらうためです。

if (agari === 'ツモ') {
    // 供託を3等分
    kyotaku = parseInt(kyotaku) / 3;
    ~
} ~

下記コードは、ツモアガリで親の場合に、〇〇〇は〇〇〇(供託を加算した点数)オールを変数に格納しています。供託がない場合は、点数にオールを付けて変数に格納しています。

if (agari === 'ツモ') {
    ~
    if (chaType === '親') {
        // 供託がある場合
        if (kyotaku !== 0) {
            point = point + 'は' + (parseInt(point) + parseInt(kyotaku)) + 'オール';
        } else {
            point = point + 'オール';
        }
    } ~
} ~

供託がある場合は、最終的にこのように表示されます。表示させるコードは後述します。

1000は1100オール

下記コードは、ツモアガリで子の場合に、〇〇〇/〇〇〇は〇〇〇/〇〇〇(供託を加算した点数)を変数に格納しています。供託がない場合は、〇〇〇/〇〇〇を変数に格納しています。

if (agari === 'ツモ') {
    ~
    } else {
        // 供託がある場合
        if (kyotaku !== 0) {
            point = point[0] + '/' + point[1] + 'は' + (point[0] + kyotaku) + '/' + (point[1] + kyotaku);
        } else {
            point = point[0] + '/' + point[1];
        }
    }
} ~

供託がある場合は、最終的にこのように表示されます。

500/1000は600/1100

下記コードは、ロンアガリで供託がある場合に〇〇〇は〇〇〇(供託を加算した点数)を変数に格納しています。

~
} else {
    if (kyotaku !== 0) {
        point = point + 'は' + (parseInt(point) + parseInt(kyotaku));
    }
}

最終的にこのように表示されます。

1500は1800

供託分を加算できたら点数を返します。

return point;

符数、翻数、点数を表示する表を出力する

ここまでで計算した符数、翻数、点数を表示する表を出力します。

pointCalcメソッドの下部に下記コードを追記します。

/**
 * 点数計算
 */
pointCalc() {
    ~
    
    // 符数
    fu = this.fuCalc(YAKU, ITEMS);

    if (ITEMS.cha !== '' && ITEMS.agari !== '' && han >= 1 && fu <= 110) {
        point = this.pointCalcFromHanFu(fu, han, ITEMS.honba, ITEMS.agari, ITEMS.cha);

        result += '<table class="bordered-table mahjong-result-table">';
        result += '<tr>';
        result += '<th>符数</th>';
        result += '<th>翻数</th>';
        result += '<th>点数</th>';
        result += '</tr>';
        result += '<tr>';
        result += '<td>' + fu + '</td>';
        result += '<td>' + han + '</td>';
        result += '<td>' + point + '</td>';
        result += '</tr>';
        result += '<tr>';
        result += '<th>役</th>';
        result += '<td colspan="3">';
        result += '<ul>';

        $.each(YAKU, (key, val) => {
            result += '<li>' + val + '</li>';
        });

        result += '</ul>';
        result += '</td>';
        result += '</tr>';
        result += '</table>';

        $('#result-inner').html(result);
    } else {
        $('#result-inner').html('');
        $('#error').show();
        $('#error').html('ノーテンです');
    }
}

参考:.html() | jQuery API Documentation
参考:.show() | jQuery API Documentation

後はこのようなHTMLを用意しておけば表が表示されます。

<div id="result" class="mahjong-result">
    <div id="error" class="error"></div>
    <div id="result-inner" class="mahjong-result-inner">
        <span class="mahjong-result-default-text">ここに点数が表示されます</span>
    </div><!-- /.mahjong-result-inner -->
</div><!-- /.mahjong-result -->

クラスのインスタンスを生成する

ここまでできたら、最初に定義したクラスのインスタンスを生成します。

const mahjong = new Mahjong();

参考:new 演算子 – JavaScript | MDN

ボタンを押された時に点数計算する

選択項目のいずれかのbuttonタグが押された時に、点数計算を行うメソッドを呼び出します。

// ボタンが押されたら点数計算
$('.btn-group button').on('click', () => {
    mahjong.pointCalc();
});

参考:.on() | jQuery API Documentation

以上が麻雀の点数計算ツールの解説です。

tooolsのTech Blogではこれからも役に立つ情報を発信していきますので、定期的に閲覧していただけると幸いです。

学校授業の「IT化」で、こんなお悩みありませんか?【e-おうち】
出張/持込/宅配でパソコン修理・設定 24時間365日対応
消費税計算

税率を設定して税込/税抜金額の消費税計算ができます。

文字数カウント

文字数をカウントできます。

和暦西暦変換

和暦と西暦を相互変換できます。

年齢計算

和暦または西暦から年齢を計算できます。

入学年・卒業年計算

履歴書に必要な学校の入学年・卒業年を生年月日から計算できます。

単位変換(換算)

キロ、マイル、グラム、華氏などの様々な単位を相互変換(換算)できます。

カラーコード変換

カラーコード(16進数)とRGB値(10進数)を相互変換できます。

Webタイマー(カウントダウン)

Webタイマー(カウントダウン)です。ストップウォッチ機能もあります。

生活に便利な電話番号一覧

警察や消防などの緊急連絡先や電話番号案内などの電話番号を確認できます。

プロバイダーのカスタマーサポートの電話番号一覧

主なプロバイダーのカスタマーサポートの電話番号を確認できます。

タスク管理(ToDo)

自分のWebブラウザーだけでタスク管理(ToDo)ができます。

エクセル関数

エクセル関数を検索できます。

麻雀の点数計算

麻雀の和了時の点数(符数/翻数/役)を計算することができます。

便利なショートカット一覧

Windows 10やExcelなどで使用できる便利なショートカットを確認できます。

電気料金計算

消費電力、使用時間、使用日数、1kWh単価から電気料金を計算できます。

パスワード生成(作成)

大文字・小文字・数字・記号を含むランダムなパスワードを生成できます。

自分のグローバルIPアドレスを確認

自分がインターネットに接続する時のグローバルIPアドレスを確認できます。

学校授業の「IT化」で、こんなお悩みありませんか?【e-おうち】
出張/持込/宅配でパソコン修理・設定 24時間365日対応
出張/持込/宅配でパソコン修理・設定 24時間365日対応
きょうみくん
このサイトの管理者
名前 きょうみくん
身長 181.1cm
誕生日 1月21日
所属 日本PCサービス株式会社
コメント

パソコン、インターネット、サーモン、ミルクティーが好きです。
猫ではありません。

エクセル家計簿の作り方など、技術的なコラムを書いているTech Blogも運営しています。