QuizKnockさんの「出題のない部屋」の正誤判定を作成してみた話

# イントロ
先日Quizknockさんの「出題のない部屋」を眺めていたら，どうやら正誤判定をプログラミングで判定していたようだ．今回はその判定プログラムを，pythonとQiitaの練習がてら作成してみたいと思う．

この記事はネタバレ満載なので，記事を見る前にぜひ以下の動画を見ていただきたい．

+ 本編
<iframe width="560" height="315" src="https://www.youtube.com/embed/CcV519_gDVk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

+ 解説編
<iframe width="560" height="315" src="https://www.youtube.com/embed/YjU-asGgdjo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>



# 問題

動画本編で出されていた問題は以下の通り．

以下に示すような3×3のボックスと，9つの文字がある．

![boxempty.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/006894d5-debd-396f-25f1-7ed468cd34df.png)


この文字たちをボックスの各マスに当てはめた時，縦(もしくは横，斜め)一列の文字を使って漢字を作れることがある．

例えば，一，木，イ　が一列にそろっていれば　体　が作れる．

すべてのマスに文字を埋めたうえで，縦横斜め全ての列で同時に漢字を成立させると正解となる．

# 正解例

後で動作確認を行うために，ひとまず正解例を明らかにしておこう．

正解例
心　目　木
音　一　口
イ　八　十

縦一列は左から，億，具，枯
横一列は上から，想，暗，休
斜めは，体(／)，志(＼)

という風になって完成である．
もちろん，この組み合わせを回転させても，縦と横の列を反転させても正解．

# 設計と実装

では，ルールがわかったところで，実際にこのクイズの正誤判定を設計してみよう．

入力：
各文字の並び

処理：
文字の並びに対して成立漢字判定，および正誤判定．

出力：
成立している漢字を表示，正解不正解の表示．

(まあすでに動画を見ていると思うのでわかると思うが)一応補足する．
実際は箱に文字が書いてあり，その箱を棚に並べ，早押しボタンを押すことで回答とする．早押しボタンが押された段階で(おそらく裏側では)文字の並びを入力し，プログラムを実行する．すると，テレビ画面に，成立している漢字を連想するイラストを順に表示，最後に音で「正解」もしくは「不正解」を示していた．

今回は成立漢字判定も正誤判定も文字で行うこととする．

では実装してみよう．

```python:hakonoheya.py
import hakonoheyafunk

print('1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 →')

a,b,c,d,e,f,g,h,i = input().split()

list = [[int(a),int(b),int(c)], [int(d),int(e),int(f)], [int(g),int(h),int(i)]]

hakonoheyafunk.list_all_explore(list)
```

名前が「ハコノヘヤ」になっているのはご容赦いただきたい．(出題のない部屋はすでにシリーズ化されていて，「シュツダイノナイヘヤ」という名前では区別できなくなるんだもん)

ここでやりたいのは，文字の並びを入力してもらい，それをlistに代入する．そして，後述するlist_all_explore関数でlistに対し正誤判定を行うということだ．このプログラムを実行すると，以下が表示される．

```
PS C:\Python\hakonoheya> python hakonoheya.py
1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 →
```

入力する際は，各文字に対応している番号を1文字ずつスペース区切りで入力していくのだが，実際の並び方とターミナルへの入力の仕方，およびlist変数の対応は以下のとおりである．

```
PS C:\Python\hakonoheya> python hakonoheya.py
1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 → A B C D E F G H I
```
![boxtaiou.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/042bf68f-f21d-0a16-4430-1444dbf05981.png)

具体例を示すと，
```
PS C:\Python\hakonoheya> python hakonoheya.py
1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 → 9 8 7 6 5 4 3 2 1
```
と入力したとき，以下の状態と同じであることを示す．

![boxexp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/cfd26259-e4d9-b47e-8296-2d5f90d9b928.png)

続いて，list変数をもとに正誤判定するlist_all_explore関数のファイルを見てみよう．

```python:hakonoheyafunc.py

import time

def list_all_explore(list):
    number = 8;
    #縦の調査
    for i in range(0,3):
        ex_list = sorted([list[0][i],list[1][i],list[2][i]])
        number = list_result(ex_list,number)
    #横斜めも同様，

    judge(number)

def list_result(list,number): #listに対して漢字が成立しているか判定
#後述

def judge(i): #正誤判定
#後述

```
list_all_exploreでは，listの縦，横，斜めの各列において，対象の要素をex_listに取り出してくることで，ex_listの組み合わせで漢字が成立しているかどうかを調べる．では実際にそれを実装しようではないか．

ん？

ここで一つ悩みが生じる．

:::note warn
ex_listの数字の並びの組み合わせ6通りあるやん…
:::

そうなのだ，例えば，1:一，5:イ，7:木を用いて「体」という漢字を作るとき，ex_listの中身は，

1 5 7，1 7 5，5 1 7 ，5 7 1，7 1 5，7 5 1

という6通りの並び方が存在しうる．当然，いずれの組み合わせだったとしても「漢字が成立している」と判定しなければならない．ほかの漢字も然り．これでは，条件分岐を書くのが大変めんどくさくなる．さてどうしたものか．論理和で全通り書いてしまおうか．うーん．

あっ，

:::note 
拾ってきた要素を小さい順に並べてみればよくね？
:::

そう，こうすることで，

1 5 7，1 7 5，5 1 7 ，5 7 1，7 1 5，7 5 1

のいずれが来たとしても，「1 5 7」と一意に表すことができるではないか．
6通りだった並びが1通りに絞れるではないか！
ということで，私は高揚感にあふれながら，sorted関数を用いて3つの数字を小さい順に並べ替えた．それをex_listとし，漢字の成立判定をするlist_resultに渡している．

ではそのlist_resultを見てみよう．

```python:hakonoheyafunk.py
def list_result(list,number):
    if(list == [4,5,6]):
        print('億') 
        time.sleep(2)
        number = number - 1;
        return number;
#以下，「具」「枯」「想」「暗」「休」「体」「志」について同様．
    else:
    return number;
```

先ほどのex_listに対し，漢字が成立する組み合わせであれば，その漢字を出力する．

time.sleepは，動画本編と同じように，一気に表示させるのではなく，一つずつ(絵を)表示している状態を再現したかったため．

また，numberは最後の正誤判定に用いる．
numberはlist_all_exploreを見てもらうとわかるように最初8に初期化されている．漢字が成立するごとにnumberは1ずつ減少する．(成立していなければnumberをそのまま返す．これを忘れたがために30分無駄にしたのは内緒の話)．最終的に漢字がすべての列で成立すればnumberは0，1つでも成立していなければ1以上の数字となる．すべての列で漢字判定が終わった後，numberを以下のjudgeに渡す．

```python:hakonoheyafunk.py
def judge(number):
    if (number==0) : print('正解')
    else : print('不正解')
```
とくに説明は不要であろう．numberが0なら「正解」，そうでなければ「不正解」を表示するだけだ．

# 実行

ということで，実際に実行してみようと思う．

+ 漢字が1つも成立しておらず不正解の場合

以下を入力とする．
![ex1q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/e51c91ae-56c8-4560-6f8b-c66da9fd3a78.jpeg)

```
PS C:\Python\hakonoheya> python hakonoheya.py
1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 →
1 5 9 3 4 7 2 8 6
不正解
```
![exp1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/564b3a8b-4993-0b81-ecd5-e0b3c7506661.png)
正しく動作できている．

+ 漢字が成立しているが不正解の場合
以下を入力とする．
![ex2q.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/8d36502c-6f09-0819-33a4-1ca97d716728.jpeg)
```
PS C:\Python\hakonoheya> python hakonoheya.py
1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 →
1 2 4 3 6 7 5 8 9
暗
想
休
志
億
不正解
```
![ex2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/b831c566-e303-bea6-3499-af24dd847d1a.png)

正しく動作できている．狙ってないが，たまたま右側に偏った．

+ 正解の場合

以下を入力とする．
![ex3q.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/8388c632-5d75-282a-abce-8bd7aee5387c.png)

```
PS C:\Python\hakonoheya> python hakonoheya.py
1:一 2:口 3:目 4:音 5:イ 6:心 7:木 8:八 9:十 →
6 3 7 4 1 2 5 8 9
億
具
枯
想
暗
休
志
体
正解
```

![ex3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950319/0624d44a-546e-5cdc-975e-8dca4d80221a.png)

正しく動作できている．

という風に実行することができた．
ただし，不正な入力に対する対処などは行っていない．

# 終わりに

Quizknock面白いんでぜひともチャンネル登録して観ていただきたい．
(ちなみに案件でも回し者でもございません．)

# ソースリスト

最後に，省略してしまったhakonoheyafunk.pyの全文を上げておく．

```python:hakonoheyafunk.py
import time

def list_all_explore(list):
    number = 8;
    #縦の調査
    for i in range(0,3):
        ex_list = sorted([list[0][i],list[1][i],list[2][i]])
        number = list_result(ex_list,number)

    #横の調査
    for i in range(0,3):
        ex_list = sorted([list[i][0],list[i][1],list[i][2]])
        number = list_result(ex_list,number)

    #左斜めの調査
    ex_list = sorted([list[0][0],list[1][1],list[2][2]])
    number = list_result(ex_list,number)

    #右斜めの調査
    ex_list = sorted([list[2][0],list[1][1],list[0][2]])
    number = list_result(ex_list,number)

    #正誤判定
    judge(number)

def list_result(list,number):
    if(list == [4,5,6]):
        print('億') 
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [3,6,7]):
        print('想')
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [1,5,7]):
        print('体')   
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [1,2,4]):
        print('暗')
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [5,8,9]):
        print('休')
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [1,3,8]):
        print('具')
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [1,6,9]):
        print('志')
        time.sleep(2)
        number = number - 1;
        return number;
    if(list == [2,7,9]):
        print('枯')
        time.sleep(2)
        number = number - 1;
        return number;
    else:
        return number;

def judge(i):
    if (i==0) : print('正解')
    else : print('不正解')
```