Start_python’s diary

ふたり暮らし

アラフィフ夫婦のフリーランスプラン

Python FlaskとBrythonで三目並べの人工知能AIを作る(前編)

f:id:Start_python:20210306161352j:plain

ゲームスタート

 

 

はじめに

今回は、Q学習やミニマックス法などの機械学習を使わずに一から人工知能を作っていきます。人間側で全て指示を与えるタイプの一昔前のAIで、自ら学習することはできません。

 

1手目と2手目は条件式で指示を与えます。

 

下の図でマスの位置番号と呼び方を決めておきます。

f:id:Start_python:20200320153830p:plain

赤・・「中心」 青・・「角」 白・・「辺」と呼ぶことにします。
駒の種類は「大」「中」「小」です。

 

1手目を考える

先手の場合

「大」を中心に打てばよさそうですが、ランダムで角にも打ちます。

後手の場合

相手の「中」を調べてその位置に「大」をかぶせて打ちます。相手の「中」がなければ「大」を中心か角に打ちます。

 

# コンピュータの1手目
if self.ai_step == 1:
    # 相手の「中」を調べる
    for i in range(9):
        if document[str(i)].classList.contains("blue_m"):
            self.masu_no = i
    # 相手の「大」が真ん中にあるか調べる
    n = random.randrange(7)
    if self.masu_no == 9:
        if n <= 4:
            if not document[str(n * 2)].classList.contains("blue_l"):
                self.masu_no = n * 2
            elif not document["4"].classList.contains("blue_l"):
                self.masu_no = 4
            else:
                self.masu_no = 6
        else:
            if not document["4"].classList.contains("blue_l"):
                self.masu_no = 4
            else:
                self.masu_no = 6
    # 「大」を動かす
    self.koma_no = "box3"
    self.koma = "red_l"
    document[self.koma_no].classList.remove(self.koma)
    self.AI_set()

 

解説

変数「n」をランダムで0~6の整数に決めます。

「n」が0~4(4以下)の場合は「n×2」の位置に打ちます。
  ただし、相手の「大」が既にある場合は「中心」に打ちます。
  さらに、中心に相手の「大」がある場合は「6の位置」に打ちます。

「n」が5か6(4より大きい)の場合は「中心」に打ちます。
  ただし、相手の「大」が既にある場合は「6の位置」に打ちます。

※先手の時は、2,5,6で「中心」、0,1,3,4で「角」になります。

 

2手目を考える

先手の場合

相手の「中」を調べてその位置に「大」をかぶせて打ちます。
相手の「中」がない場合で
  中心に駒がない場合は「中心」に「大」を打ちます。
  中心に駒がある場合は「角」に「大」を打ちます。

 

後手の場合

相手の「中」を調べてその位置に「大」をかぶせて打ちます。
相手の「中」がなければ相手がリーチしているか調べて、リーチしていれば防ぐ位置に「中」を打ちます。(相手の「中」は無いはずなので「大」と「大」のリーチという考えです。ただし「小」が入っていると簡単に負けます。)
相手の「中」がなく、相手がリーチしていない場合で
  中心に駒がない場合は「中心」に「大」を打ちます。
  斜めに「青赤青」と並んでいる場合は「辺」に「大」を打ちます。
  そうでない場合は「角」に「中」か「大」を打ちます。

 

・・・少しややこしくなってきました。ソースコードはこちらです。

 

# コンピュータの2手目
if self.ai_step == 2:
    # 相手の「中」を調べる
    for i in range(9):
        if document[str(i)].classList.contains("blue_m") \
        and str(self.board[i])[0] != "2":
            self.masu_no = i
            self.koma_no = "box6"
            self.koma = "red_l"
    # 相手のリーチを調べる
    if self.masu_no == 9:
        # 9マス分を調べる
        self.koma_no = "box2"
        self.koma = "red_m"
        a = "1"
        b = "2"
        if ((str(self.board[1])[0] == a and str(self.board[2])[0] == a) \
        or (str(self.board[3])[0] == a and str(self.board[6])[0] == a) \
        or (str(self.board[4])[0] == a and str(self.board[8])[0] == a)) \
        and str(self.board[0])[0] != b:
            self.masu_no = 0
        elif ((str(self.board[0])[0] == a and str(self.board[2])[0] == a) \
        or (str(self.board[4])[0] == a and str(self.board[7])[0] == a)) \
        and str(self.board[1])[0] != b:
            self.masu_no = 1
        elif ((str(self.board[0])[0] == a and str(self.board[1])[0] == a) \
        or (str(self.board[5])[0] == a and str(self.board[8])[0] == a) \
        or (str(self.board[4])[0] == a and str(self.board[6])[0] == a)) \
        and str(self.board[2])[0] != b:
            self.masu_no = 2
        elif ((str(self.board[4])[0] == a and str(self.board[5])[0] == a) \
        or (str(self.board[0])[0] == a and str(self.board[6])[0] == a)) \
        and str(self.board[3])[0] != b:
            self.masu_no = 3
        elif ((str(self.board[3])[0] == a and str(self.board[5])[0] == a) \
        or (str(self.board[1])[0] == a and str(self.board[7])[0] == a) \
        or (str(self.board[0])[0] == a and str(self.board[8])[0] == a) \
        or (str(self.board[2])[0] == a and str(self.board[6])[0] == a)) \
        and str(self.board[4])[0] != b:
            self.masu_no = 4
        elif ((str(self.board[3])[0] == a and str(self.board[4])[0] == a) \
        or (str(self.board[2])[0] == a and str(self.board[8])[0] == a)) \
        and str(self.board[5])[0] != b:
            self.masu_no = 5
        elif ((str(self.board[7])[0] == a and str(self.board[8])[0] == a) \
        or (str(self.board[0])[0] == a and str(self.board[3])[0] == a) \
        or (str(self.board[2])[0] == a and str(self.board[4])[0] == a)) \
        and str(self.board[6])[0] != b:
            self.masu_no = 6
        elif ((str(self.board[6])[0] == a and str(self.board[8])[0] == a) \
        or (str(self.board[1])[0] == a and str(self.board[4])[0] == a)) \
        and str(self.board[7])[0] != b:
            self.masu_no = 7
        elif ((str(self.board[6])[0] == a and str(self.board[7])[0] == a) \
        or (str(self.board[2])[0] == a and str(self.board[5])[0] == a) \
        or (str(self.board[0])[0] == a and str(self.board[4])[0] == a)) \
        and str(self.board[8])[0] != b:
            self.masu_no = 8
        # リーチでなければ
        else:
            self.koma_no = "box6"
            self.koma = "red_l"
            n = random.choice([0, 2, 6, 8])
            if not (document["4"].classList.contains("blue_l") \
            or document["4"].classList.contains("red_l")):
                self.masu_no = 4
            elif (document["0"].classList.contains("blue_l") \
            and document["4"].classList.contains("red_l") \
            and document["8"].classList.contains("blue_l")) \
            or (document["2"].classList.contains("blue_l") \
            and document["4"].classList.contains("red_l") \
            and document["6"].classList.contains("blue_l")):
                self.masu_no = 3     
            elif not (document[str(n)].classList.contains("blue_l") \
            or document[str(n)].classList.contains("red_l")):
                self.koma_no = "box2"
                self.koma = "red_m"
                self.masu_no = n
            else:
                if n == 2 or n == 8:
                    self.masu_no = 0
                else:
                    self.masu_no = n + 2
    # 駒を動かす
    document[self.koma_no].classList.remove(self.koma)
    timer.set_timeout(self.AI_set, 100)

 

解説

斜めに「青赤青」と並んでいる場合の「辺」は(ランダムで辺を選んだら良かったのですがそんなに頻度もなさそうなので)「3の位置」に固定しました。

「角」に打つ時の位置はランダムにしました。random.choice()で「角」を選んで、その位置に「大」がなければ「中」を打ちます。その位置に「大」があれば別の「角」に「大」を打ちます。

 

 

3手目からは

  • 「元の位置(self.koma_no)」から大きさ(self.koma)を調べる
  • 「元の位置(self.koma_no)」候補は最大6つ
  • 「移動先(self.masu_no)」候補は最大9つ
  •   合計54パターンを総調べ
  •   勝てる手があればその位置に打つ
  •   次の相手の手まで読む
  •   負ける手があればその手は打たない
  •   勝負が決まらなければ・・・
  •   負ける手しかなければ・・・

この流れで考えたいと思います。

 

 

後編に続きます。