2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python+Flask】しりとりAPIを作ろう #2 ~しりとりプログラム作成

Last updated at Posted at 2022-08-14

はじめに

この記事は第2回目です。
前回は【Python+Flask】しりとりAPIを作ろう #1 ~データ作成編を参照してください。

前回はテキストコーパスからテキストマイニングによって、単語データを収集しました。

今回はその単語データを用いて、しりとりプログラムを作成していきます。

環境

今回もしりとりプログラムを作成するだけなので、google colabを用います。

実際にやってみよう

前回は以下のようなデータを得ることができました。

/content/output.txt
ぁんて
あ
あい
あいえいち
あいかぎ
あいかん
あいがかり
あいきょう
あいくち
あいけん
あいこく
あいことば
あいしゃ
あいしょう
あいじょう
あいじん
...以下省略

これを使ってしりとりプログラムを作成します。

全体のプログラムは以下の様になります。

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

これだけで遊べます。

スクリーンショット 2022-12-18 14.34.52.png

最後に

今回は前回の単語データを用いて、しりとりプログラムを作成しました。

次回はこのしりとりプログラムをAPIに組み込みます。

1回目

3回目

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?