Othello app (iOS app) made with Python (Kivy)

Introduction

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.

environment

python: 3.7.7 kivy: 1.11.1 xcode: 11.7

Creation

Completed form Sep-06-2020 20-30-30.gif

Source code explanation

I will explain while following the order in which the Othello application created this time was developed.

1. Place the Othello board and initial stones

Create up to the following state スクリーンショット 2020-09-06 20.44.30.png

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 byself.grid (GridLayout), and for the squares where stones are already placed, the WhiteStone classfor white stones and theBlackStone 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.

2. Place a stone when you tap the square

Create up to the following state Sep-06-2020 21-29-16.gif

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]))

3. Turn the pinched stone over (do nothing if you tap a square that cannot be turned over)

Create up to the following state Sep-06-2020 21-50-38.gif

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 (callput_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()

4. Addition of pass function and processing at the end of the game

Pass function when there is no place to put stones Sep-06-2020 22-25-46.gif

Win / loss judgment at the end of the game Sep-06-2020 22-29-12.gif

ʻ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.

Whole source code

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()

Build with iOS Simulator

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

At the end

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.

reference

https://qiita.com/sobassy/items/b06e76cf23046a78ba05 https://github.com/PrestaMath/reverse_tile

Recommended Posts

Othello app (iOS app) made with Python (Kivy)
Othello made with python (GUI-like)
Othello game development made with Python
I made blackjack with python!
I made blackjack with Python.
I made wordcloud with Python.
Numer0n with items made in Python
I made a fortune with Python.
I made a daemon with Python
IOS application development with Python (kivy) that even monkeys can do
I made Othello to teach Python3 to children (4)
Simple Slack API client made with Python
HTTP split download guy made with Python
Uncle SES modernizes VBA app with Python
I made a character counter with Python
I made Othello to teach Python3 to children (2)
Shared screen screenshot exe app with python
Daemonize a Python web app with Supervisor
I made Othello to teach Python3 to children (5)
Easy web app with Python + Flask + Heroku
Serverless face recognition API made with Python
Make a desktop app with Python with Electron
I made a roguelike game with Python
I made Othello to teach Python3 to children (3)
I made Othello to teach Python3 to children (1)
I made a simple blackjack with Python
I made a configuration file with Python
I made a neuron simulator with Python
[Python] Python and security-② Port scanning tool made with Python
FizzBuzz with Python3
Scraping with Python
I made a competitive programming glossary with Python
I made a weather forecast bot-like with Python.
Statistics with python
Create an app that guesses students with python
Scraping with Python
Python with Go
Vienna with Python + Flask web app on Jenkins
GUI image cropping tool made with Python + Tkinter
Twilio with Python
Procedure for creating a LineBot made with Python
GUI automation with Python x Windows App Driver
Integrate with Python
Play with 2016-Python
AES256 with python
Tested with Python
python starts with ()
with syntax (Python)
How to create a multi-platform app with kivy
Zundokokiyoshi with python
Decrypt a string encrypted on iOS with Python
Excel with Python
I made a bin picking game with Python
I made a Mattermost bot with Python (+ Flask)
Cast with python
I made a Twitter BOT with GAE (python) (with a reference)
Dynamic HTML pages made with AWS Lambda and Python
I made a Christmas tree lighting game with Python
[Practice] Make a Watson app with Python! # 1 [Language discrimination]
[Mac OS] Use Kivy with PyCharm! [Python application development]
I made a Python3 environment on Ubuntu with direnv.