Skip to content

Instantly share code, notes, and snippets.

@showa-yojyo
Last active September 26, 2016 13:34
Show Gist options
  • Save showa-yojyo/d701be5204a11d2ecd7544924b922619 to your computer and use it in GitHub Desktop.
Save showa-yojyo/d701be5204a11d2ecd7544924b922619 to your computer and use it in GitHub Desktop.
A demonstration of Jupyter Notebook
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 麻雀点数表\n",
"\n",
"本稿では麻雀点数表を NumPy を使って作成する。単なる Jupyter Notebook の練習用文書だ。\n",
"\n",
"以下のことを考慮するのは後回しにしよう:\n",
"\n",
"* 存在しない符・飜の組み合わせに対応する要素をマスクする(ただしこれは是非行いたい)\n",
"* 点数表の眺めを良くする(これは当面本稿の目的からやや外れているため)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 材料を用意する\n",
"\n",
"何はさておき NumPy をインポートする。\n",
"それから次のデータおよび機能を用意する:\n",
"\n",
"* 符(配列)\n",
"* 飜(配列)\n",
"* 十の位で数を切り上げる(関数)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import numpy as np\n",
"np.set_printoptions(linewidth=160)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 符\n",
"\n",
"符を 20から 110 まで 10 刻みで配列 `FU` に格納する。ただし七対子用に 25 も格納する。"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([ 20, 25, 30, 40, 50, 60, 70, 80, 90, 100, 110])"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from itertools import chain\n",
"\n",
"FU = np.asarray(list(chain((20, 25), range(30, 120, 10))))\n",
"FU"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 飜\n",
"\n",
"飜を 1 から 12 まで 1 刻みで配列 `HAN` に格納する。必要ならもっと大きい飜を追加してもよい。"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"HAN = np.arange(1, 14)\n",
"HAN"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"それに加えあらかじめ `HAN` の各値 $n$ について $2^n$ を計算した飜の配列 `HAN2` も用意する。一飜のときに 1 になるように指数を調整する:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096], dtype=int32)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"HAN2 = 1 << (HAN - 1)\n",
"HAN2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 十の位で数を切り上げる\n",
"\n",
"最初に整数を引数に取り、整数を返す関数 `roundup` を定義する。\n",
"それから `np.ndarray` を引数に取る同様の関数 `roundupv` を定義する。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def roundup(score):\n",
" \"\"\"Round `score` up to the nearest 100.\"\"\"\n",
"\n",
" remainder = score % 100\n",
" if remainder:\n",
" score += 100\n",
" score -= remainder\n",
"\n",
" return score\n",
"\n",
"roundupv = np.vectorize(roundup)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 基本点数表を用意する\n",
"\n",
"ここが本稿のミソなのだが、関数 `np.meshgrid` を利用して符配列と飜配列の直積のような `np.ndarray` オブジェクトを生成し、それを基に最終的な点数表を得るための基礎オブジェクトを生成する。これにより多重配列の各軸を走査するようなループを書くという、危険かつ煩雑な作業を回避する。"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"hanm, fum = np.meshgrid(HAN2, FU)\n",
"basic_points = hanm * fum * 8"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"ここでオブジェクト `basic_points` を見ると次のようになっている。これを適当な定数で乗じて十の位を切り上げれば、子・親それぞれのロンアガリの点数表になるはずだ。"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 163840, 327680, 655360],\n",
" [ 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200],\n",
" [ 240, 480, 960, 1920, 3840, 7680, 15360, 30720, 61440, 122880, 245760, 491520, 983040],\n",
" [ 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 163840, 327680, 655360, 1310720],\n",
" [ 400, 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400],\n",
" [ 480, 960, 1920, 3840, 7680, 15360, 30720, 61440, 122880, 245760, 491520, 983040, 1966080],\n",
" [ 560, 1120, 2240, 4480, 8960, 17920, 35840, 71680, 143360, 286720, 573440, 1146880, 2293760],\n",
" [ 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 163840, 327680, 655360, 1310720, 2621440],\n",
" [ 720, 1440, 2880, 5760, 11520, 23040, 46080, 92160, 184320, 368640, 737280, 1474560, 2949120],\n",
" [ 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400, 3276800],\n",
" [ 880, 1760, 3520, 7040, 14080, 28160, 56320, 112640, 225280, 450560, 901120, 1802240, 3604480]])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"basic_points"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 子の点数表\n",
"\n",
"子がツモアガリするときに他の子が支払う点数を計算する(ただし「青天井」な要素はそのままである):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 200, 400, 700, 1300, 2600, 5200, 10300, 20500, 41000, 82000, 163900, 327700, 655400],\n",
" [ 200, 400, 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200],\n",
" [ 300, 500, 1000, 2000, 3900, 7700, 15400, 30800, 61500, 122900, 245800, 491600, 983100],\n",
" [ 400, 700, 1300, 2600, 5200, 10300, 20500, 41000, 82000, 163900, 327700, 655400, 1310800],\n",
" [ 400, 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400],\n",
" [ 500, 1000, 2000, 3900, 7700, 15400, 30800, 61500, 122900, 245800, 491600, 983100, 1966100],\n",
" [ 600, 1200, 2300, 4500, 9000, 18000, 35900, 71700, 143400, 286800, 573500, 1146900, 2293800],\n",
" [ 700, 1300, 2600, 5200, 10300, 20500, 41000, 82000, 163900, 327700, 655400, 1310800, 2621500],\n",
" [ 800, 1500, 2900, 5800, 11600, 23100, 46100, 92200, 184400, 368700, 737300, 1474600, 2949200],\n",
" [ 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400, 3276800],\n",
" [ 900, 1800, 3600, 7100, 14100, 28200, 56400, 112700, 225300, 450600, 901200, 1802300, 3604500]])"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"roundupv(basic_points)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 親の点数表\n",
"\n",
"親がツモアガリするときに子から得る点棒の点数(ホニャララオールのホニャララ)を計算する(ただし「青天井」な要素はそのままである):"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 400, 700, 1300, 2600, 5200, 10300, 20500, 41000, 82000, 163900, 327700, 655400, 1310800],\n",
" [ 400, 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400],\n",
" [ 500, 1000, 2000, 3900, 7700, 15400, 30800, 61500, 122900, 245800, 491600, 983100, 1966100],\n",
" [ 700, 1300, 2600, 5200, 10300, 20500, 41000, 82000, 163900, 327700, 655400, 1310800, 2621500],\n",
" [ 800, 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400, 3276800],\n",
" [ 1000, 2000, 3900, 7700, 15400, 30800, 61500, 122900, 245800, 491600, 983100, 1966100, 3932200],\n",
" [ 1200, 2300, 4500, 9000, 18000, 35900, 71700, 143400, 286800, 573500, 1146900, 2293800, 4587600],\n",
" [ 1300, 2600, 5200, 10300, 20500, 41000, 82000, 163900, 327700, 655400, 1310800, 2621500, 5242900],\n",
" [ 1500, 2900, 5800, 11600, 23100, 46100, 92200, 184400, 368700, 737300, 1474600, 2949200, 5898300],\n",
" [ 1600, 3200, 6400, 12800, 25600, 51200, 102400, 204800, 409600, 819200, 1638400, 3276800, 6553600],\n",
" [ 1800, 3600, 7100, 14100, 28200, 56400, 112700, 225300, 450600, 901200, 1802300, 3604500, 7209000]])"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"roundupv(basic_points * 2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 基本点表を麻雀に近づける\n",
"\n",
"本節では満貫以上の符・飜の組み合わせに対応するアガリの点数を青天井ではなく、標準的な麻雀ルールのそれに修正する。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 基本点を超える要素を修正する\n",
"NumPy 配列オブジェクト `basic_points` の要素のうち、値が 2000 を超えるものを一律 2000 に置換する。説明用に `basic_points` のコピー `W` に対して操作をする:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 160, 320, 640, 1280, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 200, 400, 800, 1600, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 240, 480, 960, 1920, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 320, 640, 1280, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 400, 800, 1600, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 480, 960, 1920, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 560, 1120, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 640, 1280, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 720, 1440, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 800, 1600, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000],\n",
" [ 880, 1760, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000]])"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"W = basic_points.copy()\n",
"W[W > 2000] = 2000\n",
"W"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 満貫以上のアガリの点を修正する\n",
"NumPy 配列オブジェクト `basic_points` の 5 飜以上の要素が修正候補になる。\n",
"そのアルゴリズムは次のようになる:\n",
"\n",
"* 5 飜以下かつ基本点が 2000 のものを満貫とする。これは前節で実施済み。\n",
"* 6 以上は 8 飜未満ならば跳満とし、基本点を 3000 とする。\n",
"* 8 以上 11 未満ならば倍満とし、基本点を 4000 とする。\n",
"* 11 以上 13 未満ならば三倍満とし、基本点を 6000 とする。\n",
"* 13 飜以上は数え役満とし、基本点を 8000 とする。\n",
"\n",
"これをオブジェクト `W` に適用するには、NumPy のたいへん便利な配列要素操作機能、slicing を利用する。ここでは添字は飜数そのものではなく、飜数を格納する配列 `HAN` の添字であることに注意(つまり 1 ズレる):"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 160, 320, 640, 1280, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 200, 400, 800, 1600, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 240, 480, 960, 1920, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 320, 640, 1280, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 400, 800, 1600, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 480, 960, 1920, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 560, 1120, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 640, 1280, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 720, 1440, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 800, 1600, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 880, 1760, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000]])"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"W[:,5:7] = 3000\n",
"W[:,7:10] = 4000\n",
"W[:,10:12] = 6000\n",
"W[:,12:] = 8000\n",
"W"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"最後に十の位を切り上げれば、基本点数表(子ツモによる子の支払い)が確定する:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 200, 400, 700, 1300, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 200, 400, 800, 1600, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 300, 500, 1000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 400, 700, 1300, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 400, 800, 1600, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 500, 1000, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 600, 1200, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 700, 1300, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 800, 1500, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 800, 1600, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000],\n",
" [ 900, 1800, 2000, 2000, 2000, 3000, 3000, 4000, 4000, 4000, 6000, 6000, 8000]])"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"roundupv(W)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 雑感\n",
"* 麻雀を知るプログラマーならば、エクセルでも何でも構わないから一度は麻雀点数表を自作するべきだろう。青天井ルールの麻雀を開催する事態になったときに落ち着いて対処できる。\n",
"\n",
"* NumPy の配列オブジェクトを操作する以上は、手で `for` ループを書くのは可能な限り避けるべきだろう。専門家からの未熟の謗りは免れまい。\n",
"\n",
"* 関数 `np.fromfunc` を `np.meshgrid` の代わりに利用するのもよい。実質的には同じことになる。また、引数の関数の内容に符や飜で条件分岐するような処理を含められないことに注意すること。\n",
"\n",
"* 繰り上げ関数を自作したのが気に入らない。NumPy には `ceil` や `round_` というものがあるが、今回の目的に適合させるのがやや面倒なので仕方がない。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 参考文献\n",
"\n",
"* [NumPy User Guide](http://docs.scipy.org/doc/numpy/user/)\n",
"* [麻雀の得点計算](https://ja.wikipedia.org/wiki/%E9%BA%BB%E9%9B%80%E3%81%AE%E5%BE%97%E7%82%B9%E8%A8%88%E7%AE%97#.E5.AD.90.E3.81.AE.E7.82.B9.E6.95.B0.E6.97.A9.E8.A6.8B.E8.A1.A8)\n"
]
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python [default]",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
@showa-yojyo
Copy link
Author

切り上げ関数を自作しているのを本当は気にしている。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment