Last active
May 20, 2021 03:13
-
-
Save nakagami/7a7d799bd4bd4ad8fcea96135c4af179 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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