Python で論理クイズ Solve Logic Quiz with Python

はじめに

Python は手軽に書けるスクリプト言語です. 論理クイズは与えられた説明と矛盾しない解答を論理的に導くクイズです. 今回は Python を使って論理クイズを解きます.

Python

文法が分かりやすく,シンプルに書くことができ,標準ライブラリも充実していて,とても使いやすいと感じています. 今回は itertools モジュールを使って,組み合わせを総当りすることによって,論理クイズを解きます.

論理クイズ

ここで扱う論理クイズでは,初期設定と容疑者達の証言をもとに事件の真相を論理的に導きます.

初期設定とは容疑者の集合および嘘つきの人数であり,最初に与えられます.

証言とは事件に関する主張を1つ以上並べたもので,容疑者は最大で1つの証言を行います.

嘘つきとは容疑者のうち,証言をし,それが真相と矛盾するような人物のことです.

正直者とは容疑者のうち,嘘つきではない人物のことです.

真相とは1人の真犯人と0人以上の嘘つきの組み合わせのうち,初期設定,嘘つきの証言の否定および正直者の証言のどれとも矛盾しない唯一のものです.

ソースコード

次のソースコードは論理クイズを解く Python3 のプログラムです. suspectsは容疑者とその証言を表します. 容疑者名をキーとし,lambda 式を値とする辞書で実装します. ここで,lambda 式は嘘つきの名前のタプルおよび犯人の名前を受け取り,証言の真偽を返します. liarsCountは嘘つきの人数です.

このプログラムは容疑者の中から犯人を1人選ぶ組み合わせおよび嘘つきを人数分選ぶ組み合わせの直積の中から, 初期設定,嘘つきの証言の否定および正直者の証言のどれとも矛盾しないものを標準出力に出力します. 出力されるものが1つならそれが真相です.

# -*- coding: utf-8 -*-
import itertools as it

def implies(p, q):
    return (not p) or q

def give_testimony(testimony, is_liar):
    return not testimony if is_liar else testimony

# 容疑者名と証言の辞書
suspects = {}
# 嘘つきの人数
liarsCount = 0

for truth in it.product(
        it.combinations(suspects.keys(), liarsCount),
        suspects.keys()):
    liars, criminal = truth
    isConsistent = all([give_testimony(t(liars, criminal), s in liars)
                        for s, t in suspects.items()])
    if isConsistent:
        print("嘘つきは{},犯人は{}.".format("と".join(liars), criminal))

実行

今回は説法系推理アドベンチャシリーズの3つのゲーム,

で出題される論理クイズを解きます. これらのゲームは私が作成し,ウディフェスまたはウディコンにエントリしたゲームです. 以下ではこれらのゲームのネタバレがあります. 未プレイの方は以下の文章を読む前に,プレイするかプレイ動画を見てください.

愛と血の修羅場(サスペンス)

嘘つきは1人,容疑者と証言をまとめると以下のようになります.

  • 恵伊 : 史衣と出井は嘘をつかず,史衣と出井は犯人ではない.
  • 美衣 : 恵伊と良威のどちらかは正直者.
  • 史衣 : 犯人は出井か恵伊.
  • 出井 : 出井と美衣はどちらも犯人ではない.
  • 良威 : 犯人は美衣.

本ゲームで正解となる真相は,「嘘つきは良威,犯人は恵伊.」です.

上のプログラムのsuspectsおよびliarsCount

#容疑者名と証言の辞書
suspects = {
    "恵伊": lambda ls, c: ("史衣" not in ls) and ("出井" not in ls) and ("史衣" != c) and ("出井" != c),
    "美衣": lambda ls, c: ("恵伊" not in ls) or ("良威" not in ls),
    "史衣": lambda ls, c: ("恵伊" == c) or ("出井" == c),
    "出井": lambda ls, c: ("美衣" != c) and ("史衣" != c),
    "良威": lambda ls, c: ("美衣" == c)
}
#嘘つきの人数
liarsCount = 1

と初期化し,実行すると,嘘つきは良威,犯人は恵伊.と出力されます. よって,「嘘つきは良威,犯人は恵伊.」が唯一の真相であることが確認できます.

恋と友情の常識(ファイト)

嘘つきは2人,容疑者と証言をまとめると以下のようになります.

  • 恵伊 : 良威は正直者.
  • 美衣 : 恵伊は嘘つきではない,または史衣は嘘つきではない.
  • 史衣 : 出井は嘘つき.
  • 出井 : 美衣と恵夫はどちらも犯人ではない.
  • 良威 : 犯人は恵夫.
  • 恵夫 : 良威は嘘つき.

本ゲームで正解となる真相は「嘘つきは出井と恵夫,犯人は恵夫.」です.

上のプログラムのsuspectsおよびliarsCount

#容疑者名と証言の辞書
suspects = {
    "恵伊": lambda ls, c: "良威" not in ls,
    "美衣": lambda ls, c: ("恵伊" not in ls) or ("史衣" not in ls),
    "史衣": lambda ls, c: "出井" in ls,
    "出井": lambda ls, c: ("美衣" != c) and ("恵夫" != c),
    "良威": lambda ls, c: "恵夫" == c,
    "恵夫": lambda ls, c: "良威" in ls,
}
#嘘つきの人数
liarsCount = 2

と初期化し,実行すると,嘘つきは出井と恵夫,犯人は恵夫.と出力されます. よって,「嘘つきは出井と恵夫,犯人は恵夫.」が唯一の真相であることが確認できます.

罪と幸せの四苦八苦(ノアズアーク)

嘘つきは2人,容疑者と証言をまとめると以下のようになります.

  • 美衣 : 犯人は嘘つきの中の誰かで,出井は犯人ではない.
  • 史衣 : 犯人は恵夫.
  • 出井 : 永一と史衣はどちらも嘘つき.
  • 恵夫 : (証言無し)
  • 永一 : 出井が正直者ならば出井は犯人ではなく,美衣は正直者.

本ゲームで正解となる真相は「嘘つきは史衣と出井,犯人は史衣」です.

上のプログラムのsuspectsおよびliarsCount

#容疑者名と証言の辞書
suspects = {
    "美衣": lambda ls, c: (c in ls) and ("出井" != c),
    "史衣": lambda ls, c: "恵夫" == c,
    "出井": lambda ls, c: ("永一" in ls) and ("史衣" in ls),
    "恵夫": lambda ls, c: True,
    "永一": lambda ls, c: implies("出井" not in ls, "出井" != c) and ("美衣" not in ls),
}
#嘘つきの人数
liarsCount = 2

と初期化し,実行すると,嘘つきは史衣と出井,犯人は史衣.と出力されます. よって,「嘘つきは史衣と出井,犯人は史衣」が唯一の真相であることが確認できます.

まとめ

辞書やリストをシンプルに記述でき,その取り扱いも標準ライブラリが充実しているため,とても楽でした. PyCharmで書けば,静的チェックもしてくれたので,助かりました.