I refactored "I tried to make Othello AI when programming beginners studied python"

I saw @ keisuke1111's "A programming beginner studied python and tried to make Othello AI". Even though I was a beginner, I was interested in tackling challenging issues. However, it seemed that he was having a lot of trouble with the heavy use of global variables, redundant processing, and deep nesting, which are common to beginners. Therefore, I thought that it would be a reference for program creation, so I tried to classify each role so that global variables are not used, and divide the processing so that one function or method fits within 24 lines on one screen of the terminal. I hope it will be useful for future program creation.

I myself am refactoring through trial and error, so if you have any other good ideas, I would appreciate it if you could comment.

othello.py


#!/usr/bin/env python3
# -*- coding:utf-8 -*-

'''
@author: Keisuke Ueda
http://qiita.com/keisuke1111/items/53353896a957136b1d7e
refactoring by @shiracamus
'''

import time
import random
import copy
 
 
class Othello:
 
    def play(self):
        start_time = time.time()
        board = Board()
        player1 = computer = Computer(BLACK, "PC")
        player2 = user = User(WHITE, "you")
        #player1 = user = User(BLACK, "you")
        #player2 = computer = Computer(WHITE, "PC")
 
        turn = 0
        hand1 = hand2 = None
        while board.is_playable() and not hand1 == hand2 == "pass":
            turn += 1
            print("TURN = %s" % turn)
 
            print(board)
            hand1 = player1.play(board)
            print("%s hand: %s" % (player1.name, hand1))
 
            print(board)
            hand2 = player2.play(board)
            print("%s hand: %s" % (player2.name, hand2))
 
        self.show_result(board)
 
        end_time = time.time()
        print("Match time:" + str(end_time - start_time))
 
    def show_result(self, board):
        print("------------------RESULT-------------------")  #Result announcement
        print(board)
        computer_stones = board.count(computer.stone)
        user_stones = board.count(user.stone)
        print("Computer: %s" % computer_stones)
        print("You: %s" % user_stones)
        print()
        if computer_stones > user_stones:
            print("YOU LOST!!!!!")
        elif computer_stones < user_stones:
            print("YOU WON!!!!!")
        else:
            print("DRAW")
 
 
class Stone(str):
    pass
 
BLACK = Stone("●")
WHITE = Stone("○")
BLANK = Stone("×")
OPPONENT = {BLACK: WHITE, WHITE: BLACK}
 
 
class Board:
    SIZE = 8
    DIRECTIONS_XY = ((-1, -1), (+0, -1), (+1, -1),
                     (-1, +0),           (+1, +0),
                     (-1, +1), (+0, +1), (+1, +1))
 
    def __init__(self):
        size = self.SIZE
        center = size // 2
        square = [[BLANK for y in range(size)] for x in range(size)]  #Define the first board
        square[center - 1][center - 1:center + 1] = [WHITE, BLACK]
        square[center + 0][center - 1:center + 1] = [BLACK, WHITE]
        self.square = square
 
    def __str__(self):
        return '\n'.join(''.join(row) for row in self.square)
 
    def __getitem__(self, x):
        return self.square[x]
 
    def is_playable(self):
        return any(col != BLANK
                   for row in self.square
                   for col in row)
 
    def count(self, stone):         #A function that returns how many stones there are
        return sum(col == stone     # True is 1, False is 0
                   for row in self.square
                   for col in row)
 
    def put(self, x, y, stone):     # y,x is the coordinate of the stone to be placed, and stone is WHITE or BLACK.
        self[x][y] = stone
        # reverse
        for dx, dy in Board.DIRECTIONS_XY:
            n = self.count_reversible(x, y, dx, dy, stone)
            for i in range(1, n + 1):
                self[x + i * dx][y + i * dy] = stone
 
    def count_reversible(self, x, y, dx, dy, stone):
        size = self.SIZE
        for n in range(size):
            x += dx
            y += dy
            if not (0 <= x < size and 0 <= y < size):
                return 0
            if self[x][y] == BLANK:
                return 0
            if self[x][y] == stone:
                return n
        return 0
 
    def is_available(self, x, y, stone):
        if self[x][y] != BLANK:
            return False
        return any(self.count_reversible(x, y, dx, dy, stone) > 0
                   for dx, dy in self.DIRECTIONS_XY)
 
    def availables(self, stone):  #Searching for a place to hit
        return [(x, y)
                for x in range(self.SIZE)
                for y in range(self.SIZE)
                if self.is_available(x, y, stone)]
 
 
class Player:   # abstract class
 
    def __init__(self, stone, name):
        self.stone = stone
        self.name = name
 
    def play(self, board):
        availables = board.availables(self.stone)
        if not availables:
            return "pass"
        return self.think(board, availables)
 
 
class Computer(Player):
 
    def think(self, board, availables):
        starttime = time.time()
        print(availables)
        print("thinking……")
        own = self.stone
        opponent = OPPONENT[own]
        evaluations, x, y = AlphaBeta(board, availables, own, opponent)
        print(evaluations)
        board.put(x, y, self.stone)
        endtime = time.time()
        interval = endtime - starttime
        print("%s seconds" % interval)  #Show calculation time
        return x, y
 
 
class User(Player):
 
    def think(self, board, availables):
        while True:
            print("Where you can hit(Y, X): " + str(availables))   #Internal x,X displayed as y,Note that Y is the opposite
            try:
                line = input("Y X or quit: ")
            except:
                print("forced termination")
                exit(1)
            if line == "quit" or line == "exit":
                print("End of abandonment")
                exit(1)
            try:
                x, y = map(int, line.split())
                if (x, y) in availables:
                    board.put(x, y, self.stone)
                    return x, y
                else:
                    print("Can't put there")
            except:
                print("Unknown")
 
 
def AlphaBeta(board, availables, own, opponent):  #Search by Alpha Beta method
    evaluations = AlphaBeta_evaluate1(board, availables, own, opponent)
    maximum_evaluation_index = evaluations.index(max(evaluations))
    x, y = availables[maximum_evaluation_index]
    return evaluations, x, y
 
def AlphaBeta_evaluate1(board, availables, own, opponent):
    def pruning2(max_evaluations3):
        return len(evaluations1) > 0 and max(evaluations1) >= max_evaluations3
    evaluations1 = []
    for x, y in availables:
        board1 = copy.deepcopy(board)
        board1.put(x, y, own)
        evaluations2 = AlphaBeta_evaluate2(board1, own, opponent, pruning2)
        if len(evaluations2) > 0:
            evaluations1 += [min(evaluations2)]
    return evaluations1
 
def AlphaBeta_evaluate2(board, own, opponent, pruning):
    def pruning3(min_evaluations4):
        return len(evaluations2) > 0 and min(evaluations2) <= min_evaluations4
    evaluations2 = []
    for x, y in board.availables(opponent):
        board2 = copy.deepcopy(board)
        board2.put(x, y, opponent)
        evaluations3 = AlphaBeta_evaluate3(board2, own, opponent, pruning3)
        if len(evaluations3) > 0:
            max_evaluations3 = max(evaluations3)
            evaluations2 += [max_evaluations3]
            if pruning(max_evaluations3):
                break
    return evaluations2
 
def AlphaBeta_evaluate3(board, own, opponent, pruning):
    def pruning4(max_evaluations5):
        return len(evaluations3) > 0 and max(evaluations3) >= max_evaluations5
    evaluations3 = []
    for x, y in board.availables(own):
        board3 = copy.deepcopy(board)
        board3.put(x, y, own)
        evaluations4 = AlphaBeta_evaluate4(board3, own, opponent, pruning4)
        if len(evaluations4) > 0:
            min_evaluations4 = min(evaluations4)
            evaluations3 += [min_evaluations4]
            if pruning(min_evaluations4):
                break
    return evaluations3
 
def AlphaBeta_evaluate4(board, own, opponent, pruning):
    def pruning5(evaluation5):
        return len(evaluations4) > 0 and min(evaluations4) <= evaluation5
    evaluations4 = []
    for x, y in board.availables(opponent):
        board4 = copy.deepcopy(board)
        board4.put(x, y, opponent)
        evaluations5 = AlphaBeta_evaluate5(board4, own, opponent, pruning5)
        if len(evaluations5) > 0:
            max_evaluation5 = max(evaluations5)
            evaluations4 += [max_evaluation5]
            if pruning(max_evaluation5):
                break
    return evaluations4
 
def AlphaBeta_evaluate5(board, own, opponent, pruning):
    evaluations5 = []
    for x, y in board.availables(own):
        board5 = copy.deepcopy(board)
        board5.put(x, y, own)
        ev_own = evaluate(board5, own)
        ev_opponent = evaluate(board5, opponent)
        evaluation = ev_own - ev_opponent
        evaluations5 += [evaluation]
        if pruning(evaluation):
            break
    return evaluations5
     

# pp = [45,-11,-16,4,-1,2,-1,-3,-1,0]
EVALUATION_BOARD = (  #Evaluation board showing how many points there are stones in which square
    ( 45, -11,  4, -1, -1,  4, -11,  45),
    (-11, -16, -1, -3, -3, -1, -16, -11),
    (  4,  -1,  2, -1, -1,  2,  -1,   4),
    ( -1,  -3, -1,  0,  0, -1,  -3,  -1),
    ( -1,  -3, -1,  0,  0, -1,  -3,  -1),
    (  4,  -1,  2, -1, -1,  2,  -1,   4),
    (-11, -16, -1, -3, -3, -1, -16, -11),
    ( 45, -11,  4, -1, -1,  4, -11,  45))
 
def evaluate(board, stone):  #Calculate the evaluation value of either stone on any board
    bp = 0
    for x in range(board.SIZE):
        for y in range(board.SIZE):
            if board[x][y] == BLANK:
                pass
            elif board[x][y] == stone:
                bp += EVALUATION_BOARD[x][y] * random.random() * 3
            else:
                bp -= EVALUATION_BOARD[x][y] * random.random() * 3
 
    p = confirm_stone(board, stone)
    q = confirm_stone(board, OPPONENT[stone])
    fs = ((p - q) + random.random() * 3) * 11
 
    b = board.availables(stone)
    cn = (len(b) + random.random() * 2) * 10
 
    evaluation = bp * 2 + fs * 5 + cn * 1
    return evaluation
 
def confirm_stone(board, stone):  #Count the number of fixed stones
    forward = range(0, board.SIZE)
    backward = range(board.SIZE - 1, -1, -1)
    corners = ((+0, +0, forward, forward),
               (+0, -1, forward, backward),
               (-1, +0, backward, forward),
               (-1, -1, backward, backward))
    confirm = 0
    for x, y, rangex, rangey in corners:
        for ix in rangex:
            if board[ix][y] != stone:
                break
            confirm += 1
        for iy in rangey:
            if board[x][iy] != stone:
                break
            confirm += 1
    return confirm
 
 
if __name__ == '__main__':
    Othello().play()

Recommended Posts

I refactored "I tried to make Othello AI when programming beginners studied python"
I tried to make AI for Smash Bros.
[Python] I tried to make a Shiritori AI that enhances vocabulary through battles
When I tried to introduce python3 to atom, I got stuck
Python that I would like to recommend to programming beginners
Continuation ・ I tried to make Slackbot after studying Python3
I tried to make various "dummy data" with Python faker
I tried to touch Python (installation)
I tried to make Othello AI that I learned 7.2 million hands by deep learning with Chainer
python beginners tried to find out
[5th] I tried to make a certain authenticator-like tool with python
I tried to solve the ant book beginner's edition with python
[2nd] I tried to make a certain authenticator-like tool with python
I tried to make a regular expression of "amount" using Python
[Python] I tried to implement stable sorting, so make a note
I tried to make a regular expression of "time" using Python
[3rd] I tried to make a certain authenticator-like tool with python
I tried to make a regular expression of "date" using Python
[Pandas] I tried to analyze sales data with Python [For beginners]
I tried to make a periodical process with Selenium and Python
I tried to make a 2channel post notification application with Python
[Introduction] I want to make a Mastodon Bot with Python! 【Beginners】
I tried to make a todo application using bottle with python
[4th] I tried to make a certain authenticator-like tool with python
[Python] Simple Japanese ⇒ I tried to make an English translation tool
[1st] I tried to make a certain authenticator-like tool with python
I tried to teach Python to those who have no programming experience
I tried to make an image similarity function with Python + OpenCV
I tried to make Othello AI with tensorflow without understanding the theory of machine learning ~ Introduction ~
I tried to make Othello AI with tensorflow without understanding the theory of machine learning ~ Implementation ~
I tried to summarize Python exception handling
I tried to implement PLSA in Python
I made Othello to teach Python3 to children (4)
[Episode 2] Beginners tried Numeron AI with python
[Episode 3] Beginners tried Numeron AI with python
I tried to implement PLSA in Python 2
Python3 standard input I tried to summarize
I made Othello to teach Python3 to children (5)
I tried to implement ADALINE in Python
[Episode 0] Beginners tried Numeron AI with python
[Episode 1] Beginners tried Numeron AI with python
I tried to implement PPO in Python
I tried to make a Web API
[Python] I tried to calculate TF-IDF steadily
I tried to touch Python (basic syntax)
I made Othello to teach Python3 to children (3)
I made Othello to teach Python3 to children (1)
I tried to refer to the fun rock-paper-scissors poi for beginners with Python
I tried to make a face diagnosis AI for a female professional golfer ①
[AWS] [GCP] I tried to make cloud services easy to use with Python
I tried to make a traffic light-like with Raspberry Pi 4 (Python edition)
[Zaif] I tried to make it easy to trade virtual currencies with Python
[Python] When I tried to make a decompression tool with a zip file I just knew, I was addicted to sys.exit ()
I tried to make Othello AI with tensorflow without understanding the theory of machine learning ~ Battle Edition ~
I tried to predict next year with AI
I tried to make a periodical process with CentOS7, Selenium, Python and Chrome
[Python] I tried to solve 100 past questions that beginners and intermediates should solve [Part 5/22]
I tried to make a simple mail sending application with tkinter of Python
[Python] I tried to solve 100 past questions that beginners and intermediates should solve [Part7 / 22]
When I tried to make a VPC with AWS CDK but couldn't make it
AI beginners try to make professional student bots