はじめに
この記事は第2回目です。
前回は【Python+Flask】しりとりAPIを作ろう #1 ~データ作成編を参照してください。
前回はテキストコーパスからテキストマイニングによって、単語データを収集しました。
今回はその単語データを用いて、しりとりプログラムを作成していきます。
環境
今回もしりとりプログラムを作成するだけなので、google colabを用います。
実際にやってみよう
前回は以下のようなデータを得ることができました。
ぁんて
あ
あい
あいえいち
あいかぎ
あいかん
あいがかり
あいきょう
あいくち
あいけん
あいこく
あいことば
あいしゃ
あいしょう
あいじょう
あいじん
...以下省略
これを使ってしりとりプログラムを作成します。
全体のプログラムは以下の様になります。
from typing import List, Tuple
import random
import re
from enum import Enum, unique
@unique
class FlagType(Enum):
USER_WIN = 1
USER_LOSE = 2
CONTINUE = 3
class Siritori:
def __init__(self, txt_file: str):
self.siritori_list = []
# ゲームで使われた単語を入れるリスト
self.game_siritori_list = []
self.old_noun = "しりとり"
self.hiragana_you_on_dict = {"ぁ": "あ","ぃ": "い","ぅ": "う", "ぇ": "え", "ぉ": "お", "っ": "つ", "ゃ": "や", "ゅ": "ゆ", "ょ": "よ", "ゎ": "わ", "ゕ": "か", "ゖ": "け" }
with open(txt_file, "r", encoding='UTF-8') as lines:
# 1行ずつ読み取る
for line in lines:
self.siritori_list.append(line.replace("\n",""))
print("しりとり")
def __return_nextnoun_list(self, text: str) -> List[str] or FlagType:
'''
入力されたテキストの最後の文字から始まる名詞のリストを返す
Args:
text (str): テキスト
Returns:
List[str]: 入力されたテキストの最後の文字から始まる名詞のリスト
'''
pattern=re.compile(r'^'+text[-1])
str_match = [s for s in self.siritori_list if re.match(pattern, s)]
if not str_match:
return FlagType.FINISH.value
return str_match
def return_nextnoun(self, noun: str) -> Tuple[str, FlagType]:
'''
次の語句を返す
Args:
noun (str): プレイヤーが入力した語句
Returns:
Tuple[str, bool]: str: 次の語句 or 終了メッセージ, FlagType: 終了か続行
'''
# 返答できているか確認する
#同じ単語を使っていないかを確かめる
if noun in self.game_siritori_list:
return f"{noun}は、すでに使われているのだ。あなたの負け", FlagType.USER_LOSE.value
# 今回は語尾に「ー」がある場合はその1つ前の文字を参照する
# ※語尾に小文字があるかどうかは確認していない
if noun[-1] == "ー":
# 「ー」を抜いた変数に置き換える
# 例 ルビー → ルビ
noun = noun[0:-1]
# ユーザが前の単語の語尾から始まる単語を入力したか確認する
if not self.old_noun[-1] == noun[0]:
return self.old_noun[-1]+"から始まっていません\n"+"あなたの負け", FlagType.USER_LOSE.value
# プレイヤーの返答が"ん"で終わっているかを確認する
if self.__is_finish_nn(noun):
return "んで終わっています\nあなたの負け", FlagType.USER_LOSE.value
self.game_siritori_list.append(noun)
# ここからはCPU側の処理
first_character_list = self.__return_nextnoun_list(noun)
# 返す語句があるかどうかを確認する
if first_character_list is None:
return "返す語句がありません。\nあなたの勝ち", FlagType.USER_WIN.value
# listをシャッフルする
random.shuffle(first_character_list)
# listの先頭を返し、その要素を削除する.また、その要素が"ん"で終わっているかを確認する
# この時点ではsiritori_listの方は消えてない
next_noun = first_character_list.pop()
# siritori_listの方からも消す
self.siritori_list.remove(next_noun)
if next_noun[-1] == "ー":
# 「ー」を抜いた変数に置き換える
# 例 ルビー → ルビ
next_noun = next_noun[0:-1]
# 拗音が入っている場合 大文字に変換する
if next_noun[-1] in self.hiragana_you_on_dict.keys():
next_noun[-1] = self.hiragana_you_on_dict.get(next_noun[-1])
#同じ単語を使っていないかを確かめる
if next_noun in self.game_siritori_list:
return f"返せる言葉がないのだ\nあなたの勝ち", FlagType.USER_WIN.value
self.old_noun = next_noun
if self.__is_finish_nn(next_noun):
return next_noun+"\n"+"「ん」がついたのであなたの勝ち", FlagType.USER_WIN.value
return next_noun, FlagType.CONTINUE.value
def __is_finish_nn(self, noun: str) -> bool:
if noun[-1] == "ん":
return True
深掘りしていきます
クラスとしてプログラムを作成し、ゲームプロセスは別で書くようにします。
つまり、クラスをインスタンスして、メソッドを呼ぶと、単語と、フラグが返ってきます。
もし、ユーザの入力やプログラムが選択した単語に、"ん"がついていたりしたら、終了メッセージと終了フラグ(FINISH)が返ります。
そのフラグやメッセージを別で実装します。
深掘り1 初期化
self.siritori_list = []
# ゲームで使われた単語を入れるリスト
self.game_siritori_list = []
self.old_noun = "しりとり"
self.hiragana_you_on_dict = {"ぁ": "あ","ぃ": "い","ぅ": "う", "ぇ": "え", "ぉ": "お", "っ": "つ", "ゃ": "や", "ゅ": "ゆ", "ょ": "よ", "ゎ": "わ", "ゕ": "か", "ゖ": "け" }
with open(txt_file, "r", encoding='UTF-8') as lines:
# 1行ずつ読み取る
for line in lines:
self.siritori_list.append(line.replace("\n",""))
print("しりとり")
siritori_listはプログラムが扱う単語リストです。
game_siritori_listはゲームで使われた単語を入れるリストです
インスタンスを生成するときにSiritori("output.txt")の様に、読み込むファイルを選択できる様にしています。
深掘り2 - 次の単語を返す
def __return_nextnoun_list(self, text: str) -> List[str] or FlagType:
'''
入力されたテキストの最後の文字から始まる名詞のリストを返す
Args:
text (str): テキスト
Returns:
List[str]: 入力されたテキストの最後の文字から始まる名詞のリスト
'''
pattern=re.compile(r'^'+text[-1])
str_match = [s for s in self.siritori_list if re.match(pattern, s)]
if not str_match:
return FlagType.USER_WIN.value
return str_match
pattern=re.compile(r'^'+text[-1])
str_match = [s for s in self.siritori_list if re.match(pattern, s)]
例えば「りんご」と入力された場合、
「ご」から始まる単語を検索しています。
if not str_match:
return FlagType.USER_WIN.value
return str_match
「ご」から始まる単語が見つからない場合は、ユーザ勝ちフラグを返しています
深掘り3 - 次の語句を返す
def return_nextnoun(self, noun: str) -> Tuple[str, FlagType]:
'''
次の語句を返す
Args:
noun (str): プレイヤーが入力した語句
Returns:
Tuple[str, bool]: str: 次の語句 or 終了メッセージ, FlagType: 終了か続行
'''
# 返答できているか確認する
#同じ単語を使っていないかを確かめる
if noun in self.game_siritori_list:
return f"{noun}は、すでに使われているのだ。あなたの負け", FlagType.USER_LOSE.value
# 今回は語尾に「ー」がある場合はその1つ前の文字を参照する
# ※語尾に小文字があるかどうかは確認していない
if noun[-1] == "ー":
# 「ー」を抜いた変数に置き換える
# 例 ルビー → ルビ
noun = noun[0:-1]
# ユーザが前の単語の語尾から始まる単語を入力したか確認する
if not self.old_noun[-1] == noun[0]:
return self.old_noun[-1]+"から始まっていません\n"+"あなたの負け", FlagType.USER_LOSE.value
# プレイヤーの返答が"ん"で終わっているかを確認する
if self.__is_finish_nn(noun):
return "んで終わっています\nあなたの負け", FlagType.USER_LOSE.value
self.game_siritori_list.append(noun)
# ここからはCPU側の処理
first_character_list = self.__return_nextnoun_list(noun)
# 返す語句があるかどうかを確認する
if first_character_list is None:
return "返す語句がありません。\nあなたの勝ち", FlagType.USER_WIN.value
# listをシャッフルする
random.shuffle(first_character_list)
# listの先頭を返し、その要素を削除する.また、その要素が"ん"で終わっているかを確認する
# この時点ではsiritori_listの方は消えてない
next_noun = first_character_list.pop()
# siritori_listの方からも消す
self.siritori_list.remove(next_noun)
if next_noun[-1] == "ー":
# 「ー」を抜いた変数に置き換える
# 例 ルビー → ルビ
next_noun = next_noun[0:-1]
# 拗音が入っている場合 大文字に変換する
if next_noun[-1] in self.hiragana_you_on_dict.keys():
next_noun[-1] = self.hiragana_you_on_dict.get(next_noun[-1])
#同じ単語を使っていないかを確かめる
if next_noun in self.game_siritori_list:
return f"返せる言葉がないのだ\nあなたの勝ち", FlagType.USER_WIN.value
self.old_noun = next_noun
if self.__is_finish_nn(next_noun):
return next_noun+"\n"+"「ん」がついたのであなたの勝ち", FlagType.USER_WIN.value
return next_noun, FlagType.CONTINUE.value
このしりとりプログラムの根幹を担っています。ここが最も複雑で大変でした。コードの改良の余地があります。
# 返答できているか確認する
#同じ単語を使っていないかを確かめる
if noun in self.game_siritori_list:
return f"{noun}は、すでに使われているのだ。あなたの負け", FlagType.USER_LOSE.value
# 今回は語尾に「ー」がある場合はその1つ前の文字を参照する
# ※語尾に小文字があるかどうかは確認していない
if noun[-1] == "ー":
# 「ー」を抜いた変数に置き換える
# 例 ルビー → ルビ
noun = noun[0:-1]
# ユーザが前の単語の語尾から始まる単語を入力したか確認する
if not self.old_noun[-1] == noun[0]:
return self.old_noun[-1]+"から始まっていません\n"+"あなたの負け", FlagType.USER_LOSE.value
# プレイヤーの返答が"ん"で終わっているかを確認する
if self.__is_finish_nn(noun):
return "んで終わっています\nあなたの負け", FlagType.USER_LOSE.value
self.game_siritori_list.append(noun)
ユーザの入力に不備がないかを確認しています。
不備がなければgame_siritori_listに追加します
# ここからはCPU側の処理
first_character_list = self.__return_nextnoun_list(noun)
# 返す語句があるかどうかを確認する
if first_character_list is None:
return "返す語句がありません。\nあなたの勝ち", FlagType.USER_WIN.value
# listをシャッフルする
random.shuffle(first_character_list)
# listの先頭を返し、その要素を削除する.また、その要素が"ん"で終わっているかを確認する
# この時点ではsiritori_listの方は消えてない
next_noun = first_character_list.pop()
# siritori_listの方からも消す
self.siritori_list.remove(next_noun)
if next_noun[-1] == "ー":
# 「ー」を抜いた変数に置き換える
# 例 ルビー → ルビ
next_noun = next_noun[0:-1]
# 拗音が入っている場合 大文字に変換する
if next_noun[-1] in self.hiragana_you_on_dict.keys():
next_noun[-1] = self.hiragana_you_on_dict.get(next_noun[-1])
#同じ単語を使っていないかを確かめる
if next_noun in self.game_siritori_list:
return f"返せる言葉がないのだ\nあなたの勝ち", FlagType.USER_WIN.value
self.old_noun = next_noun
if self.__is_finish_nn(next_noun):
return next_noun+"\n"+"「ん」がついたのであなたの勝ち", FlagType.USER_WIN.value
return next_noun, FlagType.CONTINUE.value
次の単語のリストをシャッフルしpopすることで、単語の候補から外れる様にしています。
そして同様にして、次の単語に不備がないかを確認させ、問題がなければ、単語とCONTINEフラグを返しています。
実際に使ってみる
クラスとしてプログラムを作成し、ゲームプロセスは別で書くようにします。と先述しましたが、そのゲームプロセスを書きます。
とは言っても、非常に短いコードで書くことができます。
入力はひらがなのみです
google colabの場合は新しいセルを追加し、以下のように書いてください。
siritori = Siritori("output.txt")
while(True):
noun = input("YOUR TURN>")
next_noun, flag = siritori.return_nextnoun(noun)
print(next_noun)
if flag is not FlagType.CONTINUE.value:
break
これだけで遊べます。
最後に
今回は前回の単語データを用いて、しりとりプログラムを作成しました。
次回はこのしりとりプログラムをAPIに組み込みます。
1回目
3回目