wxPythonで簡単なオセロ

wxPythonで簡単なオセロを実装してみた。NPCなし。一人でぽちぽち遊ぶ。

石を置けるマスのガイド機能つき。

リバーシの実装が79行で、GUIの実装が98行になった。

UI部分が煩雑でちょっと面倒

# coding: utf-8
# wxPython Reversi game
from copy import deepcopy
WHITE = -1
BLANK = 0
BLACK = 1
class Reversi(object):
    def __init__(self):
        self.board = [[0]*8 for x in range(8)]
        self.board[3][3] = WHITE
        self.board[4][4] = WHITE
        self.board[3][4] = BLACK
        self.board[4][3] = BLACK
        self.turns = 1 # turn counter
        self.current = BLACK # current player
        self.nstone = [2, 60, 2] # white, blank, black
        self.nreversed = 0
        self.passed = None
        self.over = False
    def __getitem__(self, i):
        return self.board[i]
    def stones(self, s):
        return self.nstone[1 + s]
    def reverseline(self, x, y, dx, dy):
        if not (0<=x<8) or not (0<=y<8):
            return False
        elif self[x][y] == BLANK:
            return False
        elif self[x][y] == self.current:
            return True
        elif self[x][y] == -self.current:
            ret = self.reverseline(x+dx, y+dy, dx, dy)
            if ret:
                self[x][y] *= -1 # reverse
                self.nreversed += 1
            return ret
    def put(self, x, y, validation=False):
        if self.over or self[x][y] != BLANK:
            return False
        backup = self[x][y]
        self[x][y] = self.current
        self.nreversed = 0
        for dx in [-1, 0, 1]:
            for dy in [-1, 0, 1]:
                self.reverseline(x+dx, y+dy, dx, dy)
        if self.nreversed == 0:
            self[x][y] = backup
            return False
        if validation:
            # despress changing state but only self.board
            return True
        
        self.nstone[1 + BLANK] -= 1
        self.nstone[1 + self.current] += self.nreversed + 1
        self.nstone[1 - self.current] -= self.nreversed
        self.turns += 1
        self.current *= -1 # change turn
        
        # check game is over or not
        for s in [WHITE, BLANK, BLACK]:
            if self.stones(s) == 0:
                self.over = True
                return True
        for i in range(8):
            for j in range(8):
                if self.available(i, j):
                    self.passed = None
                    return True
        self.passed = self.current
        self.current *= -1
        for i in range(8):
            for j in range(8):
                if self.available(i, j):
                    return True
        self.current *= -1
        self.over = True
        return True
    def available(self, x, y):
        if self.over:
            return False
        # backup board
        backup = deepcopy(self.board)
        ret = self.put(x, y, True)
        # restore board
        self.board = backup
        return ret

import wx
def notify(caption, message):
    dialog = wx.MessageDialog(None, message=message, caption=caption, style=wx.OK)
    dialog.ShowModal()
    dialog.Destroy()

          
class Frame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "wxpyreversi")
        # panel
        self.panel = wx.Panel(self, size=(400,600))
        self.panel.Bind(wx.EVT_LEFT_DOWN, self.tryput)
        self.panel.Bind(wx.EVT_PAINT, self.refresh)
        
        self.newgame(None)
        # menubar
        menu = wx.Menu()
        menu.Append(1, u"new game")
        menu.AppendSeparator()
        menu.Append(2, u"quit")
        menubar = wx.MenuBar()
        menubar.Append(menu, u"menu")
        self.SetMenuBar(menubar)
        self.Bind(wx.EVT_MENU, self.newgame, id=1)
        self.Bind(wx.EVT_MENU, self.quit, id=2)
        
        # status bar
        self.CreateStatusBar()

        # ban resize
        self.Bind(wx.EVT_SIZE, lambda e:e)
    
    def quit(self, event):
        self.Close()
    
    def newgame(self, event):
        # initialize reversi and refresh screen
        self.reversi = Reversi()
        self.panel.Refresh()
    
    def tryput(self, event):
        if self.reversi.over:
            return
        # calculate coordinate from window coordinate
        winx,winy = event.GetX(), event.GetY()
        w,h = self.panel.GetSize()
        x = winx / (w/8)
        y = winy / (h/8)
        if not self.reversi.available(x, y):
            return
        ret = self.reversi.put(x, y)
        self.panel.Refresh()
        # if game is over then display dialog
        if self.reversi.over:
            black = self.reversi.stones(BLACK)
            white = self.reversi.stones(WHITE)
            mes = "black: %d\nwhite: %d\n" % (black, white)
            if black == white:
                mes += "** draw **"
            else:
                mes += "winner: %s" % ["black", "white"][black < white]
            notify("game is over", mes)
        elif self.reversi.passed != None:
            notify("passing turn", "pass")

    def refresh(self, event):
        dc = wx.PaintDC(self.panel)
        self.SetStatusText("current player is " + ["White", "Black"][self.reversi.current==BLACK])
        w,h = self.panel.GetSize()

        # background        
        dc.SetBrush(wx.Brush("#228b22"))
        dc.DrawRectangle(0,0,w,h)
        # grid
        dc.SetBrush(wx.Brush("black"))
        px,py = w/8,h/8
        for i in range(8):
            dc.DrawLine(i*px,0, i*px, h)
            dc.DrawLine(0, i*py, w, i*py)
        dc.DrawLine(w-1, 0, w-1, h-1)
        dc.DrawLine(0, h-1, w-1, h-1)
        # stones
        brushes = {WHITE: wx.Brush("white"), BLACK: wx.Brush("black")}
        for i in range(8):
            for j in range(8):
                c = self.reversi[i][j]
                if c != BLANK:
                    dc.SetBrush(brushes[c])
                    dc.DrawEllipse(i*px, j*py, px, py)
                elif self.reversi.available(i, j):
                    dc.SetBrush(wx.Brush("red"))
                    dc.DrawCircle(i*px+px/2, j*py+py/2, 3)
    

if __name__ == '__main__':
    app = wx.PySimpleApp()
    Frame().Show()
    app.MainLoop()