I created an Othello app using kivy, an open source library for developing multi-tap apps in Python. Finally, I built it with (xcode) in the iOS Simulator.
python: 3.7.7 kivy: 1.11.1 xcode: 11.7
Completed form
I will explain while following the order in which the Othello application created this time was developed.
Create up to the following state
class OthelloApp(App):
title = 'Othello'
def build(self):
return OthelloGrid()
OthelloApp().run()
Returning the OthelloGrid class in the APP class. The processing of this application is done in this OthelloGrid class.
class OthelloGrid(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.num = 8
self.tile = [[' ' for x in range(self.num)] for x in range(self.num)]
self.turn = 'W'
self.grid = GridLayout(cols=self.num, spacing=[3,3], size=(Window.width, Window.height))
for x in range(self.num):
for y in range(self.num):
if x == 3 and y == 3 or x == 4 and y == 4:
self.grid.add_widget(WhiteStone())
self.tile[x][y] = 'W'
elif x == 4 and y == 3 or x == 3 and y == 4:
self.grid.add_widget(BlackStone())
self.tile[x][y] = 'B'
else:
self.grid.add_widget(PutButton(background_color=(0.451,0.3059,0.1882,1), background_normal='', tile_id=[x, y]))
self.add_widget(self.grid)
self.num
is the number of vertical and horizontal squares on the Othello board.
self.tile
is a list for memorizing the state of the board. When white is placed'W'
, when black is placed 'B'
, nothing is placed. Takes the value of when '''.
self.turnremembers whether the current turn is black or white, and initially starts with a white turn. The board that is actually drawn on the screen is defined by
self.grid (GridLayout), and for the squares where stones are already placed, the
WhiteStone classfor white stones and the
BlackStone class for black stones. We are adding_widget and add_widget
PutButton class` to the squares where no stones have been placed yet.
class WhiteStone(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(pos=self.update)
self.bind(size=self.update)
self.update()
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(0.451,0.3059,0.1882,1))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.canvas.add(Color(1,1,1,1))
self.canvas.add(Ellipse(pos=self.pos, size=self.size))
class BlackStone(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(pos=self.update)
self.bind(size=self.update)
self.update()
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(0.451,0.3059,0.1882,1))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.canvas.add(Color(0,0,0,1))
self.canvas.add(Ellipse(pos=self.pos, size=self.size))
class PutButton(Button):
def __init__(self, tile_id, **kwargs):
super().__init__(**kwargs)
self.tile_id = tile_id
The WhiteStone
and BlackStone
classes inherit from the Label class and simply have an elliptical stone ʻEllipse (pos = self.pos) on the massRectangle (pos = self.pos, size = self.size)
. I'm just drawing, size = self.size) `.
The PutButton
class inherits from the Button class, and there is no processing when it is pressed yet.
As tile_id
, the instance itself can remember which position on the grid
.
Create up to the following state
Create a ʻon_press function in the
PutButton` class as shown below, and add the processing when the cell is tapped.
PutButton class
def on_press(self):
put_x = self.tile_id[0]
put_y = self.tile_id[1]
turn = self.parent.parent.turn
self.parent.parent.tile[put_x][put_y] = turn
self.parent.parent.put_stone()
Substitute the number of the tapped cell in put_x
and put_y
, and substitute the current turn in turn
.
Call the put_stone
function by assigning the value of turn
to the location of the tapped cell of tile
in the parent class (ʻOthelloGrid).
put_stone is a function created in ʻOthello Grid
as shown below, and is a function to recreate the board from the contents of tile
.
OthelloGrid class
def put_stone(self):
self.clear_widgets()
self.grid = GridLayout(cols=self.num, spacing=[3,3], size=(Window.width, Window.height))
next_turn = 'W' if self.turn == 'B' else 'B'
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == 'W':
self.grid.add_widget(WhiteStone())
elif self.tile[x][y] == 'B':
self.grid.add_widget(BlackStone())
else:
self.grid.add_widget(PutButton(background_color=(0.451,0.3059,0.1882,1), background_normal='', tile_id=[x, y]))
Create up to the following state
Add functions can_reverse_check
and reverse_list
to the ʻOthelloGrid` class to check if there are stones that can be flipped from the coordinates of the square and the current turn as shown below.
OthelloGrid class
def can_reverse_check(self, check_x, check_y, turn):
check =[]
#Upper left confirmation
check += self.reverse_list(check_x, check_y, -1, -1, turn)
#Confirmed above
check += self.reverse_list(check_x, check_y, -1, 0, turn)
#Upper right confirmation
check += self.reverse_list(check_x, check_y, -1, 1, turn)
#Right confirmation
check += self.reverse_list(check_x, check_y, 0, 1, turn)
#Lower right confirmation
check += self.reverse_list(check_x, check_y, 1, 1, turn)
#Check below
check += self.reverse_list(check_x, check_y, 1, 0, turn)
#Lower left confirmation
check += self.reverse_list(check_x, check_y, 1, -1, turn)
#Left confirmation
check += self.reverse_list(check_x, check_y, 0, -1, turn)
return check
def reverse_list(self, check_x, check_y, dx, dy, turn):
tmp = []
while True:
check_x += dx
check_y += dy
if check_x < 0 or check_x > 7:
tmp = []
break
if check_y < 0 or check_y > 7:
tmp = []
break
if self.tile[check_x][check_y] == turn:
break
elif self.tile[check_x][check_y] == ' ':
tmp = []
break
else:
tmp.append((check_x, check_y))
return tmp
can_reverse_check
calls reverse_list
, which is a function to check if there is a stone that can be turned over in each direction from the square where the stone is to be placed.
As a return value, a list of coordinates of stones that can be turned over is returned.
As shown below, this can_reverse_check
is called when the PutButton
class is tapped (in ʻon_press), and if there is a content in the list of return values, the value of
tileis updated and the board is displayed. Recreate (call
put_stone`).
If the list is empty, do nothing.
PutButton class
def on_press(self):
put_x = self.tile_id[0]
put_y = self.tile_id[1]
check =[]
turn = self.parent.parent.turn
check += self.parent.parent.can_reverse_check(self.tile_id[0], self.tile_id[1], turn)
if check:
self.parent.parent.tile[put_x][put_y] = turn
for x, y in check:
self.parent.parent.tile[x][y] = turn
self.parent.parent.put_stone()
Pass function when there is no place to put stones
Win / loss judgment at the end of the game
ʻExtend the put_stone
of the OthelloGrid` class as follows
OthelloGrid class
def put_stone(self):
pass_flag = True
finish_flag = True
check = []
self.clear_widgets()
self.grid = GridLayout(cols=self.num, spacing=[3,3], size=(Window.width, Window.height))
next_turn = 'W' if self.turn == 'B' else 'B'
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == 'W':
self.grid.add_widget(WhiteStone())
elif self.tile[x][y] == 'B':
self.grid.add_widget(BlackStone())
else:
self.grid.add_widget(PutButton(background_color=(0.451,0.3059,0.1882,1), background_normal='', tile_id=[x, y]))
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == ' ':
finish_flag = False
check += self.can_reverse_check(x, y, next_turn)
if check:
pass_flag = False
break
if finish_flag:
content = Button(text=self.judge_winner())
popup = Popup(title='Game set!', content=content, auto_dismiss=False, size_hint=(None, None), size=(Window.width, Window.height/3))
content.bind(on_press=popup.dismiss)
popup.open()
else:
if pass_flag:
skip_turn_text = 'White Turn' if self.turn == 'B' else 'Black Turn'
content = Button(text='OK')
popup = Popup(title=skip_turn_text+' Skip!', content=content, auto_dismiss=False, size_hint=(None, None), size=(Window.width, Window.height/3))
content.bind(on_press=popup.dismiss)
popup.open()
else:
self.turn = next_turn
self.add_widget(self.grid)
Prepare pass_flag
and finish_flag
and use them to judge whether to pass or end the game.
For all empty squares in tile
(squares with a value of''
), check if there is a stone to turn over when the player puts a stone on that square next turn. If not, skip the next turn.
At that time, the fact that the popup was skipped is displayed on the screen.
If there is no empty space in tile
, it is considered that the game is over, the following judge_winner
function determines which one wins, and Popup displays it on the screen.
OthelloGrid class
def judge_winner(self):
white = 0
black = 0
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == 'W':
white += 1
elif self.tile[x][y] == 'B':
black += 1
print(white)
print(black)
return 'White Win!' if white >= black else 'Black Win!'
Up to this point, the processing of Othello is complete.
We have also added ResetButton and labels that display turns, but please check the entire source code below for that area. (I also give it to git. Https://github.com/fu-yuta/kivy-project/tree/master/Othello)
main.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.graphics import Color, Ellipse, Rectangle
class OthelloGrid(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.num = 8
self.tile = [[' ' for x in range(self.num)] for x in range(self.num)]
self.turn = 'W'
self.grid = GridLayout(cols=self.num, spacing=[3,3], size_hint_y=7)
for x in range(self.num):
for y in range(self.num):
if x == 3 and y == 3 or x == 4 and y == 4:
self.grid.add_widget(WhiteStone())
self.tile[x][y] = 'W'
elif x == 4 and y == 3 or x == 3 and y == 4:
self.grid.add_widget(BlackStone())
self.tile[x][y] = 'B'
else:
self.grid.add_widget(PutButton(background_color=(0.451,0.3059,0.1882,1), background_normal='', tile_id=[x, y]))
self.creat_view('White Turn')
def put_stone(self):
self.grid = GridLayout(cols=self.num, spacing=[3,3], size_hint_y=7)
pass_flag = True
finish_flag = True
check = []
next_turn = 'W' if self.turn == 'B' else 'B'
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == 'W':
self.grid.add_widget(WhiteStone())
elif self.tile[x][y] == 'B':
self.grid.add_widget(BlackStone())
else:
self.grid.add_widget(PutButton(background_color=(0.451,0.3059,0.1882,1), background_normal='', tile_id=[x, y]))
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == ' ':
finish_flag = False
check += self.can_reverse_check(x, y, next_turn)
if check:
pass_flag = False
break
if finish_flag:
content = Button(text=self.judge_winner())
popup = Popup(title='Game set!', content=content, auto_dismiss=False, size_hint=(None, None), size=(Window.width, Window.height/3))
content.bind(on_press=popup.dismiss)
popup.open()
self.restart_game()
else:
if pass_flag:
skip_turn_text = 'White Turn' if self.turn == 'B' else 'Black Turn'
content = Button(text='OK')
popup = Popup(title=skip_turn_text+' Skip!', content=content, auto_dismiss=False, size_hint=(None, None), size=(Window.width, Window.height/3))
content.bind(on_press=popup.dismiss)
popup.open()
else:
self.turn = next_turn
turn_text = 'Black Turn' if self.turn == 'B' else 'White Turn'
self.creat_view(turn_text)
def can_reverse_check(self, check_x, check_y, turn):
check =[]
#Upper left confirmation
check += self.reverse_list(check_x, check_y, -1, -1, turn)
#Confirmed above
check += self.reverse_list(check_x, check_y, -1, 0, turn)
#Upper right confirmation
check += self.reverse_list(check_x, check_y, -1, 1, turn)
#Right confirmation
check += self.reverse_list(check_x, check_y, 0, 1, turn)
#Lower right confirmation
check += self.reverse_list(check_x, check_y, 1, 1, turn)
#Check below
check += self.reverse_list(check_x, check_y, 1, 0, turn)
#Lower left confirmation
check += self.reverse_list(check_x, check_y, 1, -1, turn)
#Left confirmation
check += self.reverse_list(check_x, check_y, 0, -1, turn)
return check
def reverse_list(self, check_x, check_y, dx, dy, turn):
tmp = []
while True:
check_x += dx
check_y += dy
if check_x < 0 or check_x > 7:
tmp = []
break
if check_y < 0 or check_y > 7:
tmp = []
break
if self.tile[check_x][check_y] == turn:
break
elif self.tile[check_x][check_y] == ' ':
tmp = []
break
else:
tmp.append((check_x, check_y))
return tmp
def judge_winner(self):
white = 0
black = 0
for x in range(self.num):
for y in range(self.num):
if self.tile[x][y] == 'W':
white += 1
elif self.tile[x][y] == 'B':
black += 1
print(white)
print(black)
return 'White Win!' if white >= black else 'Black Win!'
def restart_game(self):
print("restart game")
self.tile = [[' ' for x in range(self.num)] for x in range(self.num)]
self.turn = 'W'
self.grid = GridLayout(cols=self.num, spacing=[3,3], size_hint_y=7)
for x in range(self.num):
for y in range(self.num):
if x == 3 and y == 3 or x == 4 and y == 4:
self.grid.add_widget(WhiteStone())
self.tile[x][y] = 'W'
elif x == 4 and y == 3 or x == 3 and y == 4:
self.grid.add_widget(BlackStone())
self.tile[x][y] = 'B'
else:
self.grid.add_widget(PutButton(background_color=(0.451,0.3059,0.1882,1), background_normal='', tile_id=[x, y]))
self.creat_view('White Turn')
def creat_view(self, turn_text):
self.clear_widgets()
self.turn_label = Label(text=turn_text, width=Window.width , size_hint_y=1, font_size='30sp')
self.restart_button = RestartButton(text='Restart')
self.layout = BoxLayout(orientation='vertical', spacing=10, size=(Window.width, Window.height))
self.layout.add_widget(self.turn_label)
self.layout.add_widget(self.grid)
self.layout.add_widget(self.restart_button)
self.add_widget(self.layout)
class WhiteStone(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(pos=self.update)
self.bind(size=self.update)
self.update()
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(0.451,0.3059,0.1882,1))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.canvas.add(Color(1,1,1,1))
self.canvas.add(Ellipse(pos=self.pos, size=self.size))
class BlackStone(Label):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(pos=self.update)
self.bind(size=self.update)
self.update()
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(0.451,0.3059,0.1882,1))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.canvas.add(Color(0,0,0,1))
self.canvas.add(Ellipse(pos=self.pos, size=self.size))
class PutButton(Button):
def __init__(self, tile_id, **kwargs):
super().__init__(**kwargs)
self.tile_id = tile_id
def on_press(self):
print(self.tile_id)
put_x = self.tile_id[0]
put_y = self.tile_id[1]
check =[]
turn = self.parent.parent.parent.turn
check += self.parent.parent.parent.can_reverse_check(self.tile_id[0], self.tile_id[1], turn)
if check:
self.parent.parent.parent.tile[put_x][put_y] = turn
for x, y in check:
self.parent.parent.parent.tile[x][y] = turn
self.parent.parent.parent.put_stone()
class RestartButton(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_press(self):
content = Button(text='OK')
popup = Popup(title='Restart Game!', content=content, auto_dismiss=False, size_hint=(None, None), size=(Window.width, Window.height/3))
content.bind(on_press=popup.dismiss)
popup.open()
self.parent.parent.restart_game()
class OthelloApp(App):
title = 'Othello'
def build(self):
return OthelloGrid()
OthelloApp().run()
I referred to the following article. https://qiita.com/sobassy/items/b06e76cf23046a78ba05
If the Xcode command line tool is not included, execute the following command.
xcode-select --install
Install dependencies
brew install autoconf automake libtool pkg-config
brew link libtool
Install Cython
pip install cython
git clone kivy-ios.
git clone https://github.com/kivy/kivy-ios.git
cd kivy-ios
Execute the following command to build kivy for iOS (it may take tens of minutes to complete)
python toolchain.py build kivy
Once the above is complete, build the kivy program for Xcode.
python toolchain.py create [Xcode project name (arbitrary name)] [Kivy program folder name]
At this time, the name of the kivy program file must be main.py
.
If the Xcode project name is ʻApp, a directory called ʻApp-ios
is created, and ʻApp.xcodeproj` is created in that directory.
Open this project in Xcode.
open App.xcodeproj
If you specify Simulator in Xcode and build it, the application will start up. If you update the kivy program, you must also update the Xcode project with the following command.
python toolchain.py update App
I created an Othello app using kivy, one of the python libraries, and built it with Simulater on iOS. This time, I wrote all the processing in main.py, but kivy is a kv language, and wighet etc. can be written separately, so I would like to consider porting to that. In addition, we would like to incorporate Othello's AI and add player-versus-CPU battle functions in the future.
https://qiita.com/sobassy/items/b06e76cf23046a78ba05 https://github.com/PrestaMath/reverse_tile
Recommended Posts