Pythonプログラミングで

リバーシを作る

From GitHub

目次

config.py

                    
#! /usr/bin/env python3
# config.py
# programmed by Saito-Saito-Saito
# explained in https://Saito-Saito-Saito.github.io/reversi
# last update: 23/12/2020


import sys
from logging import getLogger, StreamHandler, FileHandler, Formatter, DEBUG, INFO, WARNING, ERROR, CRITICAL


### LOG SETTINGS
DEFAULT_LOG_ADDRESS = 'log.txt'
DEFAULT_LOG_FORMAT = Formatter('%(asctime)s - %(levelname)s - logger:%(name)s - %(filename)s - L%(lineno)d - %(funcName)s - %(message)s')

# set up function
def setLogger(name='default', level=DEBUG, *, fhandler=None, fhandler_level=DEBUG, filename=DEFAULT_LOG_ADDRESS, filemode='w', fileformat=DEFAULT_LOG_FORMAT, shandler=None, shandler_level=CRITICAL, streamformat=DEFAULT_LOG_FORMAT):
    logger = getLogger(name)
    logger.setLevel(level)
    
    # file handler
    fhandler = fhandler or FileHandler(filename, mode=filemode)
    fhandler.setLevel(fhandler_level)
    fhandler.setFormatter(fileformat)
    logger.addHandler(fhandler)
    
    # stream handler
    shandler = shandler or StreamHandler()
    shandler.setLevel(shandler_level)
    shandler.setFormatter(streamformat)
    logger.addHandler(shandler)
    
    return logger


logger = setLogger(__name__)


# board is 8 * 8
SIZE = 8
if int(SIZE / 2) != SIZE / 2:
    logger.error('SIZE VALUE HAS TO BE EVEN')
    print('SYSTEM ERROR')
    sys.exit()


# rows & columns index
ROW = 0
COL = 1


# piece values
EMPTY = 0
B = BLACK = 1
W = WHITE = -1


# game proceeding/set
GAME_PRC = 0
GAME_SET = 1


# for return
SUCCEEDED = True
FAILED = False


# direction is represented as follows: [toROW - frROW, toCOL - frCOL]
WHOLE_DIRECTION = [
    [-1, -1], [-1, 0], [-1, 1],
    [0, -1], [0, 1],
    [1, -1], [1, 0], [1, 1]
]


# whether the index is in the board
def InBoard(subject):
    if 0 <= subject < SIZE:
        return True
    else:
        return False
                    
                

IO.py

                    
#! /usr/bin/env python3
# IO.py
# programmed by Saito-Saito-Saito
# explained in https://Saito-Saito-Saito.github.io/reversi
# last update: 23/12/2020


import config
local_logger = config.setLogger(__name__)



# translate user's input into the index of the square
def InputFormat(s):
    if len(s) != 2:
        local_logger.info('len(s) == {}'.format(len(s)))
        return config.FAILED
    elif s[0].isdecimal() and config.InBoard(int(s[0]) - 1) and ord('a') <= ord(s[1]) <= ord('h'):
        return [int(s[0]) - 1, ord(s[1]) - ord('a')]
    elif s[1].isdecimal() and config.InBoard(int(s[1]) - 1) and ord('a') <= ord(s[0]) <= ord('h'):
        return [int(s[1]) - 1, ord(s[0]) - ord('a')]
    else:
        local_logger.info('OUT OF FORMAT')
        return config.FAILED
                    
                

board.py

                    
#! /usr/bin/env python3
# board.py
# programmed by Saito-Saito-Saito
# explained in https://Saito-Saito-Saito.github.io/reversi
# last update: 23/12/2020

import copy

from config import *

local_logger = setLogger(__name__)


class Board:
    def __init__(self, input_board=[], *, status=GAME_PRC, winner=EMPTY, logger=None):
        if len(input_board) == SIZE:
            self.board = copy.deepcopy(input_board)
        else:
            self.board = []
            for row in range(SIZE):
                self.board.append([0 for col in range(SIZE)])
            self.board[int(SIZE / 2) - 1][int(SIZE / 2) - 1] = WHITE
            self.board[int(SIZE / 2) - 1][int(SIZE / 2)] = BLACK
            self.board[int(SIZE / 2)][int(SIZE / 2) - 1] = BLACK
            self.board[int(SIZE / 2)][int(SIZE / 2)] = WHITE
        # 0:進行中(途中)PRC 1:決着SET
        self.game_status = status
        self.winner = winner
        self.logger = logger or local_logger
            

    def BoardPrint(self, logger=None):
        logger = logger or self.logger
        
        print('\n')
        print('\t    a   b   c   d   e   f   g   h')
        print('\t   -------------------------------')
        for row in range(SIZE):
            print('\t{} |'.format(row + 1), end='')
            for col in range(SIZE):
                if self.board[row][col] == EMPTY:
                    print('   |', end='')
                elif self.board[row][col] == WHITE:
                    print(' ● |', end='')
                elif self.board[row][col] == BLACK:
                    print(' ○ |', end='')
                else:
                    logger.critical('UNEXPECTED PLAYER VALUE in BoardPrint')
                    return False
            print(' {}'.format(row + 1))
            print('\t   -------------------------------')
        print('\t    a   b   c   d   e   f   g   h')
        print('\n')

    
    """
        turnjudge judges whether the piece can be turned
        if player put a piece on [R, C] ...
        1.  check whether [R+direc[ROW], C+direc[COL]]==-player
        2.  if yes and turnjudge(player, R+direc..., C+direc..., direc)==True, you can turn the direction when you put a piece on [R,C]
        3.  if yes but turnjudge(player, R+direc..., C+direc..., direc)==False, you cannot turn the direction when you put a piece on [R, C] (it does not always mean that you cannot put a piece there)
    """
    def turnjudge(self, player, row, col, direction: list, logger=None):
        logger = logger or self.logger
        
        # out of the board
        if not (InBoard(row) and InBoard(col)):
            logger.debug('{}, {}\tOUT OF THE BOARD'.format(row, col))
            return False

        # BLACK, WHITE or EMPTY
        piece = self.board[row][col]
        
        # EMPTY
        if piece == EMPTY:
            logger.debug('{}, {}\tREACHED TO EMPTY'.format(row, col))
            return False
        # OWN
        elif piece == player:
            logger.debug('{}, {}\tREACHED TO OWN PIEE'.format(row, col))
            return True
        # OPPONENT'S
        elif piece == -player:
            return self.turnjudge(player, row + direction[ROW], col + direction[COL], direction, logger)
        # ERROR
        else:
            logger.error('UNEXPECTED VALUE of PLAYER in putjudge')
            return False
        
    
    def turn(self, player, row, col, logger=None):
        logger = logger or local_logger
        
        # out of the board
        if not (InBoard(row) and InBoard(col)):
            logger.info('OUT OF THE BOARD')
            return FAILED

        # there is already a piece
        if self.board[row][col] != EMPTY:
            logger.info('THERE IS ALREADY A PIECE')
            return FAILED

        turned = False
        # searching all the direction for available one
        for direction in WHOLE_DIRECTION:
            focused = [row + direction[ROW], col + direction[COL]]
            if not (InBoard(focused[ROW]) and InBoard(focused[COL])):
                continue
            next_piece = self.board[focused[ROW]][focused[COL]]
            logger.debug('direc = {}, next_piece = {}'.format(direction, next_piece))
            # in case available
            if next_piece == -player and self.turnjudge(player, focused[ROW], focused[COL], direction):
                while self.board[focused[ROW]][focused[COL]] == -player:
                    self.board[focused[ROW]][focused[COL]] = player
                    focused[ROW] += direction[ROW]
                    focused[COL] += direction[COL]
                    turned = True
                
        # in case a piece was turned
        if turned:
            self.board[row][col] = player
            return SUCCEEDED
        # in case any piece was not turned
        else:
            logger.info('THERE IS NO DIRECTION AVAILABLE')
            return FAILED

    
    def passjudge(self, player, logger=None):
        logger = logger or self.logger
        
        # searching all EMPTY squares that can turn
        for row in range(SIZE):
            for col in range(SIZE):
                if self.board[row][col] == EMPTY:
                    for direction in WHOLE_DIRECTION:
                        focused = [row + direction[ROW], col + direction[COL]]
                        if not (InBoard(focused[ROW]) and InBoard(focused[COL])):
                            continue
                        if self.board[focused[ROW]][focused[COL]] == -player and self.turnjudge(player, focused[ROW], focused[COL], direction):
                            logger.info('THERE IS {}, {}'.format(row, col))
                            return False
        
        # completing all the loop, there is no square that you can put a piece
        return True


    def countpiece(self, logger=None):
        logger = logger or self.logger
        
        # count up all pieces
        self.b_count = 0
        self.w_count = 0
        for row in range(SIZE):
            for col in range(SIZE):
                if self.board[row][col] == BLACK:
                    self.b_count += 1
                elif self.board[row][col] == WHITE:
                    self.w_count += 1
        return [self.b_count, self.w_count]
        

    def gamesetjudge(self, logger=None):
        logger = logger or self.logger
        
        # if either can put, it's not gameset
        if self.passjudge(BLACK) == False or self.passjudge(WHITE) == False:
            logger.debug('either can put yet')
            return False
        else:
            self.game_status = GAME_SET
            return True


if __name__ == "__main__":
    setup = [
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 1, 0, 0, 0, 0, 0],
        [0, 0, 0, 1, 1, 1, 1, 0],
        [0, 0, 0, 1, 1, 1, 1, 1],
        [0, 0, 0, 0, 1, 0, 1, 0],
        [0, 0, 0, 0, 0, 1, 0, 0],
        [0, 0, 0, 0, -1, 0, 1, 0]
    ]

    TestBoard = Board(setup)
    TestBoard.BoardPrint()
                    
                

main.py

                    
#! /usr/bin/env python3
# main.py
# programmed by Saito-Saito-Saito
# explained in https://Saito-Saito-Saito.github.io/reversi
# last update: 23/12/2020

from config import *
import IO
import board

# preset
logger = setLogger(__name__)

main_board = board.Board()
player = BLACK

main_board.BoardPrint()


while True:
    ### GAMESET JUDGE
    # NOTE: you must not control game_status here because out of the loop it must be GAME_PRC
    if main_board.gamesetjudge():
        break

    ### PLAYER OUTPUT
    if player == BLACK:
        print('○ TURN', end=' ')
    elif player == WHITE:
        print('● TURN', end=' ')
    else:
        logger.error('UNEXPECTED VALUE of PLAYER in the while loop')
        break

    ### PASS JUDGE
    if main_board.passjudge(player):
        print('BUT YOU CANNOT PUT ANYWHERE (PRESS ENTER TO PASS)')
        input()
        # player change
        player *= -1
        continue

    ### INPUT
    print('(X to give up) >>> ', end='')
    s = input()
    # give up
    if s in ['X', 'x']:
        main_board.winner = -player
        break
    else:
        square = IO.InputFormat(s)
        logger.info('motion = {}'.format(square))
    
    # invalid putting
    if square == False:
        print('INVALID INPUT')
        continue
    elif main_board.turn(player, *square) == FAILED:
        print('INVALID PUTTING')
        continue
    
    ### PLAYER CHANGE
    player *= -1

    main_board.BoardPrint()



print('\nGAME SET')

# in case of give up
if main_board.game_status == GAME_PRC:
    print('INTERRUPTION')
    if main_board.winner == BLACK:
        print('BLACK WINS')
    elif main_board.winner == WHITE:
        print('WHITE WINS')
    else:
        print('SYSTEM ERROR: DRAW')
# the other cases of game set
else:
    counter = main_board.countpiece()
    print('{} - {}'.format(counter[0], counter[1]))
    if counter[0] == counter[1]:
        print('DRAW')
    elif counter[0] > counter[1]:
        print('○ WINS')
    elif counter[0] < counter[1]:
        print('● WINS')
    else:
        logger.error('ILLOGICAL ATTITUDE out of the while loop')

print('\nGAME OVER\n')