Skip to content

Instantly share code, notes, and snippets.

@nakagami
Last active May 20, 2021 03:13
Show Gist options
  • Save nakagami/7a7d799bd4bd4ad8fcea96135c4af179 to your computer and use it in GitHub Desktop.
Save nakagami/7a7d799bd4bd4ad8fcea96135c4af179 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
################################################################################
# MIT License
#
# Copyright (c) 2017,2020 Hajime Nakagami<nakagami@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################
import random
import itertools
import copy
EMPTY = 0
BLACK = -1
WHITE = 1
# http://uguisu.skr.jp/othello/5-1.html
WEIGHT1_MATRIX = [
[120, -20, 20, 5, 5, 20, -20, 120],
[-20, -40, -5, -5, -5, -5, -40, -20],
[20, -5, 15, 3, 3, 15, -5, 20],
[5, -5, 3, 3, 3, 3, -5, 5],
[5, -5, 3, 3, 3, 3, -5, 5],
[20, -5, 15, 3, 3, 15, -5, 20],
[-20, -40, -5, -5, -5, -5, -40, -20],
[120, -20, 20, 5, 5, 20, -20, 120],
]
WEIGHT2_MATRIX = [
[30, -12, 0, -1, -1, 0, -12, 30],
[-12, -15, -3, -3, -3, -3, -15, -12],
[0, -3, 0, -1, -1, 0, -3, 0],
[-1, -3, -1, -1, -1, -1, -3, -1],
[-1, -3, -1, -1, -1, -1, -3, -1],
[0, -3, 0, -1, -1, 0, -3, 0],
[-12, -15, -3, -3, -3, -3, -15, -12],
[30, -12, 0, -1, -1, 0, -12, 30],
]
class Reversi:
def __init__(self, p):
self.board = []
if isinstance(p, int): # initialize board
# p is level and 1 or 2
assert p in [1 ,2]
if p == 2:
self.weight_matrix = WEIGHT1_MATRIX
else:
self.weight_matrix = WEIGHT2_MATRIX
for i in range(8):
self.board.append([EMPTY] * 8)
self.board[4][3] = self.board[3][4] = BLACK
self.board[3][3] = self.board[4][4] = WHITE
elif isinstance(p, Reversi): # copy constructor
self.board = copy.deepcopy(p.board)
self.weight_matrix = p.weight_matrix
else:
raise ValueError("invaid parameter")
def count(self, bwe):
"Count pieces or empty spaces in the board"
assert bwe in (BLACK, WHITE, EMPTY)
n = 0
for i in range(8):
for j in range(8):
if self.board[i][j] == bwe:
n += 1
return n
def _has_my_piece(self, bw, x, y, delta_x, delta_y):
"There is my piece in the direction of (delta_x, delta_y) from (x, y)."
assert bw in (BLACK, WHITE)
assert delta_x in (-1, 0, 1)
assert delta_y in (-1, 0, 1)
x += delta_x
y += delta_y
if x < 0 or x > 7 or y < 0 or y > 7 or self.board[x][y] == EMPTY:
return False
if self.board[x][y] == bw:
return True
return self._has_my_piece(bw, x, y, delta_x, delta_y)
def reversible_directions(self, bw, x, y):
"Can put piece on (x, y) ? Return list of reversible direction tuple"
assert bw in (BLACK, WHITE)
directions = []
if self.board[x][y] != EMPTY:
return directions
for d in itertools.product([-1,1,0],[-1,1,0]):
if d == (0, 0):
continue
nx = x + d[0]
ny = y + d[1]
if nx < 0 or nx > 7 or ny < 0 or ny > 7 or self.board[nx][ny] != bw * -1:
continue
if self._has_my_piece(bw, nx, ny, d[0], d[1]):
directions.append(d)
return directions
def _reverse_piece(self, bw, x, y, delta_x, delta_y):
"Reverse pieces in the direction of (delta_x, delta_y) from (x, y) untill bw."
assert bw in (BLACK, WHITE)
x += delta_x
y += delta_y
assert self.board[x][y] in (BLACK, WHITE)
if self.board[x][y] == bw:
return
self.board[x][y] = bw
return self._reverse_piece(bw, x, y, delta_x, delta_y)
def put(self, x, y, bw):
"""
True: Put bw's piece on (x, y) and change board status.
False: Can't put bw's piece on (x, y)
"""
assert bw in (BLACK, WHITE)
directions = self.reversible_directions(bw, x, y)
if len(directions) == 0:
return False
self.board[x][y] = bw
for delta in directions:
self._reverse_piece(bw, x, y, delta[0], delta[1])
return True
def _calc_score(self, bw):
assert bw in (BLACK, WHITE)
my_score = 0
opponent_score = 0
for i in range(8):
for j in range(8):
if self.board[i][j] == bw:
my_score += self.weight_matrix[i][j]
elif self.board[i][j] == bw * -1:
opponent_score += self.weight_matrix[i][j]
return my_score - opponent_score
def find_best_position(self, bw):
"Return the best next position."
assert bw in (BLACK, WHITE)
next_positions = {}
for i in range(8):
for j in range(8):
reversi = Reversi(self)
if reversi.put(i, j, bw):
next_positions.setdefault(
reversi._calc_score(bw), []
).append((i, j))
if next_positions:
next_position = random.choice(next_positions[max(next_positions)])
else:
next_position = None
return next_position
#-------------------------------------------------------------------------------
BLACK_MARK = 'X'
WHITE_MARK = 'O'
def print_board(reversi):
print('\n a b c d e f g h \n +-+-+-+-+-+-+-+-+')
for i, row in enumerate(reversi.board):
print(' %d|' % (i+1), end='')
for r in row:
print('{}|'.format({EMPTY: ' ', BLACK: BLACK_MARK, WHITE: WHITE_MARK}[r]), end='')
print('\n +-+-+-+-+-+-+-+-+')
print()
def input_level():
while True:
s = input('Level? 1/2[1]')
if s == '':
return 1
if s in ('1', '2'):
return int(s)
def input_position(player):
while True:
s = input('{}? [a-h][1-8]'.format(BLACK_MARK if player == BLACK else WHITE_MARK))
if s == '' or (len(s) == 2 and s[0] in list('abcdefgh') and s[1] in list('12345678')):
break
if s == '':
return None
y, x = ord(s[0]) - 97, ord(s[1]) - 49
#print('input_position=', x, y)
return x, y
def print_position(player, xy):
if xy is None:
print('{}: skip'.format(BLACK_MARK if player == BLACK else WHITE_MARK))
else:
print('{}: {}{}'.format(
BLACK_MARK if player == BLACK else WHITE_MARK,
chr(xy[1]+97),
chr(xy[0]+49)
))
def start_game():
reversi = Reversi(input_level())
player_color = BLACK
while not (
reversi.count(EMPTY) == 0 or
reversi.count(BLACK) == 0 or
reversi.count(WHITE) == 0
):
print_board(reversi)
# player's turn
xy = input_position(player_color)
while xy and not reversi.put(xy[0], xy[1], player_color):
xy = input_position(player_color)
print_board(reversi)
# computer's turn
xy = reversi.find_best_position(player_color * -1)
if xy:
reversi.put(xy[0], xy[1], player_color * -1)
print_position(player_color * -1, xy)
print_board(reversi)
if reversi.count(player_color) > reversi.count(player_color * -1):
print('You win!')
elif reversi.count(player_color) < reversi.count(player_color * -1):
print('You lose!')
if __name__ == "__main__":
start_game()
#!/usr/bin/env python3
################################################################################
# MIT License
#
# Copyright (c) 2020 Hajime Nakagami<nakagami@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
################################################################################
from tkinter import *
import tkinter.messagebox
from functools import partial
import reversi
def player_put_action(app, press_x, press_y):
def wrapper(obj=app, x=press_x, y=press_y):
return obj.player_put(x, y)
return wrapper
class App(Frame):
def __init__(self):
super().__init__()
self.master.title('TkReversi')
frame = Frame(self)
self.player_color = reversi.BLACK
self.level = None
l = Label(frame, text="Level:")
l.grid(row=1, column=1, columnspan=2)
self.spin = Spinbox(frame, from_=1, to=2, command=self.restart)
self.spin.grid(row=1, column=4, columnspan=6)
self.buttons = [[None] * 8 for _ in range(8)]
for i in range(8):
for j in range(8):
self.buttons[i][j] = Button(frame, command=player_put_action(self, i, j))
self.buttons[i][j].grid(row=i+2, column=j+1)
b = Button(frame, text="pass", command=self.player_pass)
b.grid(row=10, column=1, columnspan=2)
self._indicator = StringVar()
l = Label(frame, textvariable=self._indicator)
l.grid(row=10, column=3, columnspan=6)
frame.pack()
self.pack()
self.show_message("You: ●")
self.restart()
def computer_put(self):
xy = self.reversi.find_best_position(self.player_color * -1)
if xy:
self.reversi.put(xy[0], xy[1], self.player_color * -1)
def update_board(self):
if len(self.buttons) == 0:
return
for i in range(8):
for j in range(8):
self.buttons[i][j]["text"] = {
reversi.BLACK: "●",
reversi.WHITE: "○",
reversi.EMPTY: " ",
}[self.reversi.board[i][j]]
# check finish
if not (
self.reversi.count(reversi.EMPTY) == 0 or
self.reversi.count(reversi.BLACK) == 0 or
self.reversi.count(reversi.WHITE) == 0
):
return
if self.reversi.count(self.player_color) > self.reversi.count(self.player_color * -1):
tkinter.messagebox.showinfo("TkReverse", "You win!")
self.show_message('You win!')
elif self.reversi.count(self.player_color) < self.reversi.count(self.player_color * -1):
tkinter.messagebox.showinfo("TkReverse", "You lose!")
self.show_message('You lose!')
def restart(self):
level = int(self.spin.get())
if self.level != level:
self.level = level
self.reversi = reversi.Reversi(level)
self.update_board()
def player_put(self, x, y):
if not self.buttons:
return
if self.reversi.put(x, y, self.player_color):
self.computer_put()
self.update_board()
def player_pass(self):
self.computer_put()
self.update_board()
def show_message(self, s):
self._indicator.set(s)
if __name__ == "__main__":
app = App()
app.mainloop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment