过年宅家躲武肺,学习wxPython,编了个数回(Slither Link)小游戏,踩了好多坑,特记一下

    今年过年真是前所未有的有时间,哪都去不了,于是难得得有时间想学习一下,好多年没编程,没写博客,因为总是编嵌入式的c程序,对界面开发基本没搞过,过去只会用Borland C++,还是6.0,真是太过时了。好在还学过python,听说用python开发界面也很方便了,于是想学习一下,选了一个小游戏“数回”(Slither Link)来作为目标。本以为不会太麻烦,没想到一个不到千行的程序,让我踩了好多的坑,花了足一个星期的时间。可能也是自己年纪大了,脱离编程太久了。感慨之余,把踩的坑记一下,以资来人。

    首先介绍一下数回这个游戏,规则很简单,一个N*M的格子,其中给定一些数字,数字表明这个格子周围有几条边有连续通过,游戏要求画出一条 唯一的,封闭的 曲线,并且满足所有的数字条件,OK了。

                     

 这个游戏很有意思,有的很难,解题时要注意有些定式和技巧,特别是注意唯一性这个特定,很多时候可以帮助解题。

好了游戏不多说了,大家可以去找来玩玩,手机上有很多。

我开始也是玩手机上的数回游戏,然后很多关太难了玩不过去,所以,我其实是想编一个自动求解数回的程序。然而开工之后却发现,我先得有一个展现游戏的界面才行啊!编界面偏偏是我的弱项,于是想放弃,可是想了几天,还是去学学吧,于是开始了这个程序的开发。这一开始,就是不停的踩坑,一个不到千行的程序,竟花了一个星期的时间才初具规模。

这第一个坑,说来搞笑,我听说Tkinter比较简单,于是在办公室一台比较旧的电脑上试了下,安装的是老版的python2.7,试了几个基本的控件和画界面方法,觉得还不错,就决定用tk,然后就放假了,回家后在家里装了个anaconda,使用import Tkinter,竟然失败,仔细对照了在办公室的程序确认大小写没错,于是以为没有装这个模块,于是又去找tkinter的安装,结果用pip找不到,只能放弃了,差点就不想编了,后来看到有个wxPython,于是又去学wxPython,可是安装又是一番折腾,用pip在线装下载总是报错,后来只好去找安装包下载好了,本地安装总算装好了。这是第一个坑,还未开始就踩坑。可是今天我在看python安装目录时竟然发现了有个tkinter的目录,于是试了下全小写的 import tkinter,竟然成功了,真是%&¥……#……#……¥……%%&……*……

不管怎么说,总算开始了,又是一路踩坑,实在太多了,挑几个主要的说一下。

第一个,是wxPython的BoxSizer, 不知怎么回事,在程序开始时里面的控件不能够自动排号,必须要拖动一下,改变一下尺寸才行,于是我不得不在初始化时显式的指定好各控件的位置。

第二个,是用鼠标拖动改变界面大小时,竟然不触发EVT_PAINT事件,后来在EVT_SIZE事件中加了resize函数,好了一点,但还是经常不进OnPaint函数,后来不得不强制加 Refresh()函数。更神奇的是,即使进了OnPaint函数,执行完了绘制工作后,绘制的结果仍然经常会不显示,让我一直以为是我画图操作哪里有问题。后来逼得没办法,在画图完后把图存到一个bmp里,打开bmp看,发现图是正确的。后来在OnPaint里面最后加了对Refresh()调用才好。这真是超出我的认知了!而且在OnPaint里加Refresh调用还会触发一次OnPain,这样就无尽循环了,为此只能又加了个需要刷新的标志,在真正需要刷新时置位,进Onpaint时判断一下,置位了才画,绘图完成后清除,这样才好。

第三个是对python本身理解的问题。为了简化编程,把界面做一个py文件,把地图操作做一个py文件,把地图配置存成一个py文件里面的一个列表,在地图操作py文件里用exec file的方式加载地图配置py文件,然后读取里面的地图配置列表。比如在地图配置文件里面定义地图列表

mapList=[.........]

在地图操作文件里,加载地图配置文件,读取里面的列表,

execfile( "地图配置.py")

for map in mapList: ............

单独调试地图操作文件时,一切正常,可是运行时地图操作文件是在主文件里被import的,这时再执行就显示mapList找不到了,后来猜到是变量局部空间的问题,查了半天才查到解决办法,需要locals()函数先获取局部变量字典,再exec file,再从局部变量字典里通过变量名得到变量。

其它的就不多说了,大大小小的坑踩多了,还是直接上代码吧。

主文件 SlitherLink.py

# -*- coding: utf-8 -*-
"""
Created on Fri Jan 31 18:19:25 2020
Slitherlink

MainFrame

use two panel,one place menus ,one place feild.
@author: pc
"""
version="0.1"

import wx
import copy
#-------------------------------------------------------------
from ini import *
import GameMap as gmap


gMapList=[]
gCurrentMapIdx=-1
gCurrentMap=None
gReDraw=False

gMiniNumRect=40
gCurrentNumRect=40          #current number Square side length
gCurrentRectPos=[0,0]       #current map left top point
gCurrentMinSize=minisize
# menu panel============================================================
class MPanel(wx.Panel):
    def __init__(self,parent,*args,**kwargs):
        super(MPanel,self).__init__(parent,*args,**kwargs)
        self.parent=parent
        self.SetBackgroundColour(wx.WHITE)
        #create a combo box
        box=wx.BoxSizer(wx.HORIZONTAL)
        wid,high=self.GetSize()
        self.cbox=wx.ComboBox(self,pos=(wid-200,2))
        box.AddStretchSpacer(10)
        box.Add(self.cbox,0,wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL,10)
        box.AddSpacer(100)
        self.SetSizer(box)
        self.Bind(wx.EVT_COMBOBOX, self.OnCombox,self.cbox)
        
        #load game map list to combo box
        global gMapList
        self.LoadMap(gMapList,mapfilepath,savefilepath)
        
    
    #load game map list ------------------------------------
    def LoadMap(self,maplist,filename,savefile=None):
        gmap.loadLeves(maplist,filename,savefile)
        mapcnt=len(maplist)
        #add to combo box
        self.cbox.Clear()
        for lv in maplist:
            texts="%d×%d "%(lv.colum,lv.row) + lv.title
            self.cbox.Append(texts)
    
    def OnCombox(self,event):
        global gCurrentMapIdx, gCurrentMap, gMapList,gReDraw
        idx= self.cbox.GetSelection()
        #print("cbox select",idx)
        if idx!=gCurrentMapIdx:
            #change map and redraw
            gCurrentMapIdx=idx
            gCurrentMap=gMapList[idx]
            gReDraw=True
            self.parent.Refresh()
        
# game field panel ======================================================
class FPanel(wx.Panel):
    def __init__(self,parent,*args,**kwargs):
        super(FPanel,self).__init__(parent,*args,**kwargs)
        self.parent=parent
        self.bgColour=wx.Colour(232,232,232)              
        self.SetBackgroundColour(self.bgColour)        
        self.Bind(wx.EVT_PAINT,self.OnPaint)
        
        #set OnMove and click action
        self.MouseItem=[-1,-1,0]  #the mouse position map to squares [row, colum, right|down]
        #self.Bind(wx.EVT_MOTION,self.OnMove)
        self.Bind(wx.EVT_MOUSE_EVENTS,self.OnMouse,self)
        
        self.MouseDownItem=[None,None]          #the items under the mouse when mouse button  is down [left , right]
        
    def OnMove(self,event):
        global gCurrentMapIdx, gCurrentMap, gMapList,gReDraw, gCurrentNumRect,gCurrentRectPos
        x,y=event.GetPosition()
        left,top=gCurrentRectPos
        if gCurrentMap:
            row=gCurrentMap.row
            colum=gCurrentMap.colum
            if x<=left or x>=left+gCurrentNumRect*colum+int(gCurrentNumRect/6) or y<=top or y>=top+gCurrentNumRect*row+int(gCurrentNumRect/6):
                self.MouseItem=[-1,-1,0]
            else:
                squCol=int((x-left)/gCurrentNumRect)
                squRow=int((y-top)/gCurrentNumRect)
                sx=x-left-squCol*gCurrentNumRect
                sy=y-top-squRow*gCurrentNumRect
                #diviion a square to 8 part
                sx= int(sx*8/gCurrentNumRect)
                sy=int(sy*8/gCurrentNumRect)
                if sx<2 and 0<sy<7:     #left side
                    self.MouseItem=[squRow,squCol,2 ]
                elif sx>5 and 0<sy<7:   #right side
                    self.MouseItem=[squRow,squCol+1,2]
                elif sy<2 and 0<sx<7:   #top side
                    self.MouseItem=[squRow,squCol,1]
                elif sy>5 and  0<sx<7:   #down side
                    self.MouseItem=[squRow+1,squCol,1]
                else:
                    self.MouseItem=[squRow,squCol,0]
                #print(x,y,squRow,squCol,sx,sy,self.MouseItem)                
    def OnMouse(self,event):
        global gCurrentMapIdx, gCurrentMap, gMapList,gReDraw, gCurrentNumRect,gCurrentRectPos

        if event.Moving:        #deal  mouse moving 
            self.OnMove(event)
        if event.LeftDown():
            self.MouseDownItem[0]=self.MouseItem[:]
            self.MouseDownItem[1]=None
        if event.RightDown():
            self.MouseDownItem[1]=self.MouseItem[:]
            self.MouseDownItem[0]=None
        #if have finished ,not change map
        if not gCurrentMap or (gCurrentMap.succ and gCurrentMap.finish):
            return
        if event.LeftUp():
            if self.MouseDownItem[0] and self.MouseDownItem[0]==self.MouseItem:
                #print ("l click"
                row,colum,line=self.MouseItem
                if row>=0 and colum>=0 and line>0:
                    con=gCurrentMap.connected[row][colum]
                    if line==1:     #right direction
                        if con&0x10 ==0:    #unlock ,inverse the connect
                            gCurrentMap.setConnect(row,colum,gCurrentMap.Up,gCurrentMap.INVERSE,gCurrentMap.UNCHANGE)
                            gReDraw=True
                            #print(gCurrentMap.connected)
                            self.Refresh()
                            #print(self.toDrawAction)
                    elif line==2:   #down
                        if con&0x1000==0:   #unlock
                            gCurrentMap.setConnect(row,colum,gCurrentMap.Left,gCurrentMap.INVERSE,gCurrentMap.UNCHANGE)
                            gReDraw=True                            
                            self.Refresh()
            self.MouseDownItem[0]=None
        if event.RightUp():
            if self.MouseDownItem[1] and self.MouseDownItem[1]==self.MouseItem:
                row,colum,line=self.MouseItem
                if row>=0 and colum>=0 and line>0:
                    con=gCurrentMap.connected[row][colum]
                    if line==1:     #right direction
                        gCurrentMap.setConnect(row,colum,gCurrentMap.Up,gCurrentMap.UNCHANGE,gCurrentMap.INVERSE)
                        gReDraw=True
                        self.Refresh()
                    elif line==2:   #down
                        gCurrentMap.setConnect(row,colum,gCurrentMap.Left,gCurrentMap.UNCHANGE,gCurrentMap.INVERSE)
                        gReDraw=True                            
                        self.Refresh()
            self.MouseDownItem[1]=None
        if gCurrentMap:
            if gCurrentMap.succ and not gCurrentMap.finish:
                gCurrentMap.finish=True
                #print('succeessful finish')
                if gCurrentMap.answer==None:
                    gCurrentMap.answer=copy.deepcopy(gCurrentMap.connected)
                    for r in range(gCurrentMap.row+1):
                        for c in range(gCurrentMap.colum+1):
                            gCurrentMap.answer[r][c]&=0x101
                            
                wx.MessageBox("Congratulations!","Finish",wx.OK)
            else:
                gCurrentMap.finish=gCurrentMap.succ
        #print(self.MouseDownItem)
        
    # draw feild
    def OnPaint(self,event):
        rect=self.GetClientRect()
        global gCurrentMap, gReDraw,gCurrentNumRect,gCurrentRectPos
        if gCurrentMap==None:
            return
        
        colum =gCurrentMap.colum 
        row = gCurrentMap.row
        
        #dc=wx.PaintDC(self)
        '''bmp=wx.Bitmap(width=rect.width,height=rect.height)
        dc=wx.MemoryDC()
        dc.SelectObject(bmp)'''
        dc=wx.BufferedPaintDC(self)
        
        gbBrush=wx.Brush(self.bgColour)
        gbBrush.SetStyle(wx.BRUSHSTYLE_SOLID)
        dc.SetBrush( gbBrush)

        #print ("fpanel on paint",gReDraw)
        if gReDraw:     #need redraw whole map
            #calculate size , minist  number Square side length is 40,can zoom up by the rate of client size
            #if client size less than the need of minist square ,resize the frame
            x=int((rect.width-20)/colum /4)      #must times of 4
            y=int((rect.height-200)/row /4)       
            m=min(x,y)
            m<<=2
            if m>120:
                m=120
            if m<gMiniNumRect:
                #small than the minist size, need to resize the fame
                m=gMiniNumRect
                gCurrentMinSize=[m*colum+20,m*row+200]
                self.parent.SetSize(gCurrentMinSize)
                return
            
            
            rect=self.GetClientRect()
            left=int( (rect.width- m*colum)/2)
            top= int((rect.height - m* row)/2-50)
            gCurrentRectPos=[left,top]
            gCurrentNumRect=m
            #print(rect ,left ,top)
            #start to draw map
            #print("cls",rect, left,top,m,colum ,row)
            dc.Clear()  #clear panel
            #draw  points and num
            #dc.SetBrush(wx.BLACK_BRUSH)
            psize=int(m/20)*2
                        
            font=self.GetFont()
            font.SetWeight(wx.FONTWEIGHT_BOLD)
            font.SetPointSize(m/2)
            dc.SetFont(font)
            lightPen=wx.Pen(wx.Colour(64,64,64),psize,style=wx.PENSTYLE_SOLID) #use light pen to draw not locked line
            dc.SetPen(lightPen)
            
            dc.SetTextForeground(wx.BLUE)
            
            nmap=gCurrentMap.numMap
            # if see answer is true , draw the anser------------------------
            if gCurrentMap.seeAnswer and gCurrentMap.answer:
                con=gCurrentMap.answer
            else:
                con=gCurrentMap.connected
                
            for x in range(row+1):
                for y in range(colum+1):
                    dc.DrawRectangle( (left+y*m,top+x*m,psize,psize ))  #draw angle point
                    tmp=con[x][y]
                    if tmp&1 !=0:
                        if tmp&0x10 ==0:        #not lock 
                            dc.DrawLine(left+y*m+psize,top+x*m+int(psize/2), left+y*m+m,top+x*m+int(psize/2) )
                        else:
                            dc.DrawRectangle( (left+y*m+psize,top+x*m,m-psize,psize ))
                    if tmp&0x100 !=0:
                        if tmp & 0x1000 ==0:
                            dc.DrawLine(left+y*m+int(psize/2) ,top+x*m+psize ,left+y*m+int(psize/2) , top+x*m+m  )
                        else:
                            dc.DrawRectangle( (left+y*m,top+x*m+psize , psize,m-psize ))

            
            for x in range(row):
                for y in range(colum):
                    number=nmap[x][y]
                    if number>=0:
                        dc.DrawText(repr(number), left+y*m+m/4+psize, top+x*m +int(m/6)-psize )
            # draw locked null connect ,use a red cross 
            redPen=wx.Pen(wx.Colour(255,32,32),int(psize/2),style=wx.PENSTYLE_SOLID) #use light pen to draw not locked line
            dc.SetPen(redPen)
            for x in range(row+1):
                for y in range(colum+1):
                    tmp=con[x][y]
                    if tmp&0xff==0x10:
                        dc.DrawLine(left+y*m + int(m*3/8)+psize/2, top+x*m - int(m/8) , left+y*m + int(m*5/8)+psize/2, top+x*m + int(m/8)  )
                        dc.DrawLine(left+y*m + int(m*3/8)+psize/2, top+x*m + int(m/8) , left+y*m + int(m*5/8)+psize/2, top+x*m - int(m/8)  )
                    if tmp&0xff00==0x1000:
                        dc.DrawLine(left+y*m - int(m/8), top+x*m + int(m*3/8)+psize/2 , left+y*m + int(m/8), top+x*m + int(m*5/8)+psize/2 )
                        dc.DrawLine(left+y*m - int(m/8), top+x*m + int(m*5/8)+psize/2 , left+y*m + int(m/8), top+x*m + int(m*3/8)+psize/2  )
                        
            
            if gCurrentMap and gCurrentMap.succ and gCurrentMap.finish:
                dc.SetTextForeground(wx.RED)
                dc.DrawText("Finished !", int (rect.width/2)-int(1.5*m), 20)
            #end of draw map
            gReDraw = False
            #self.Refresh()
        '''dc2=wx.PaintDC(self)
        print("blit",left,top)        
        dc2.Blit(0,0,rect.width,rect.height,dc,0,0)
        dc.SelectObject(wx.NullBitmap)'''


#MainFrame ==============================================================   
class MFrame(wx.Frame):
    def __init__(self,parent,*args,**kwargs):
        super(MFrame,self).__init__(parent,*args,**kwargs)
        #  add a menu bar
        self.menu=wx.MenuBar()
        self.menu_play=wx.Menu()
        
        self.id_restart=101
        self.menu_play.Append(self.id_restart,"Restart")
        self.id_seeAnswer=102
        self.menu_play.Append(self.id_seeAnswer,"see answer")
        self.menu.Append(self.menu_play,"&Play")
        self.SetMenuBar(self.menu)
        self.Bind(wx.EVT_MENU,self.OnMenu)
        
        # create menu panel                
        self.mPanel=MPanel(self,size=(initsize[0],toolbarHeight))
        
        #create field panel
        self.fPanel= FPanel(self,style=wx.BORDER_DOUBLE,pos=(0,toolbarHeight),size=(initsize[0],initsize[1]-toolbarHeight) )
        
        # add resize event
        self.Bind(wx.EVT_SIZE,self.OnResize)
        
        self.Bind(wx.EVT_CLOSE,self.OnClose)

    #------------------------------------------
    def OnResize(self,event):
        global gReDraw,gCurrentMinSize
        
        wid,high=self.GetSize()
        #print("f size",wid,high)
        #resize the panels,but not small then minsize
        if wid<gCurrentMinSize[0]:
            wid=gCurrentMinSize[0]
        if high<gCurrentMinSize[1]:
            high=gCurrentMinSize[1]
            
        self.SetSize((wid,high))
        self.mPanel.SetSize(wid,toolbarHeight)
        gReDraw =True            
        self.fPanel.SetSize(wid,high-toolbarHeight)
        #must call fpanel's refresh ,or it will not refresh the panel sometime
        self.fPanel.Refresh()

    #menu-------------------------------------------- 
    def OnMenu(self, event):
        global gCurrentMap, gReDraw
        id=event.GetId()
        #print(id)
        if id==self.id_restart:
            if gCurrentMap:
                gCurrentMap.clear()
                gCurrentMap.seeAnswer=False
                gReDraw=True
                self.fPanel.Refresh()
        elif id==self.id_seeAnswer:
            if gCurrentMap:
                gCurrentMap.seeAnswer=True
                gReDraw=True
                self.fPanel.Refresh()                
                
    #on close------------------------------------------
    def OnClose(self,event):
        global gMapList
        if len(gMapList)>0:
            gmap.saveLeves(gMapList,savefilepath)
        event.Skip()        

#Main app=================================================================        
class MyApp(wx.App):
    def OnInit(self):
        self.frame= MFrame(None,title="Slither Link v%s"%version,size=initsize)
        self.SetTopWindow(self.frame)
        
        self.frame.Show()
        
        return True
    
#======================================================================
if __name__=="__main__":
    app=MyApp(False)
    app.MainLoop()

地图管理文件 GameMap.py

# -*- coding: utf-8 -*-
"""
Created on Sat Feb  1 16:55:53 2020
Slitherlink game map class
@author: pc
"""
import os
import copy
import hashlib
class GameMap():
    def __init__(self,numMap=None,title="",difficulty=0,answer=None,connected=None):
        self.numMap=None   #num define
        self.FitMap=None
        self.mapHash=''
        self.succ=False
        self.finish=False
        self.title=title
        self.difficulty=difficulty
        self.answer=answer
        self.seeAnswer=False
        self.connected=None
        self.row=0
        self.colum=0
        #define the direction code and connect action code,used in setConnect function
        self.Up=0
        self.Left=1
        self.Down=2
        self.Right=3
        self.CONNECT=1
        self.DISCONNECT=0
        self.INVERSE=-1
        self.UNCHANGE=-2
        
        
        if numMap:
            self.setMap(numMap,connected)
    #--------------------------------------------------------------------------        
    def setMap(self, numMap,connected=None):
        if numMap:
            row=len(numMap)
            #check map ,must be a 2D matrix
            col=len(numMap[0])
            for r in numMap:
                if col!=len(r):
                    return False
                else:
                    for i in r:
                        if i not in (-1,0,1,2,3):
                            return False
            self.row=row
            self.colum=col
            self.numMap=numMap
            self.clear()
            self.calcuHash()
            if connected:
                succ=True
                #check connected map 
                #connect map is a connected relation of the points
                #as a m*n num map ,it will be a (m+1)*(n+1) points matrix
                #from left-top to right-down , each point can have tow connections that to right and down 
                #each line use one byte , lower byte is right direction,upper byte is down direction,
                #0 mean not connected ,1 means connected,0x10 means must not be connected ,0x11 means must be connected 
                conFlag= (0,1,0x10,0x11) 
                if len(connected)!=self.row+1:
                    succ=False
                else:
                    for r in connected:
                        if not succ or len(r)!=self.colum+1:
                            succ=False
                            break
                        else:
                            for i in r:
                                if i&0xff not in conFlag or i>>8 not in conFlag:
                                    succ=False
                                    break
                if succ :
                    self.connected=connected
                    self.checkFit()

        else:
            self.numMap=None
            self.answer=None
            self.connected=None
            self.row=0
            self.colum=0
            
        return True
    #-----------------------------------------------------------------
    def createFitMap(self):
        self.FitMap=[]
        for i in range(self.row):
            a=[0]*(self.colum)
            self.FitMap.append(a)
        for r in range(self.row+1):
            for c in range(self.colum+1):
                state=self.connected[r][c]
                if state&1 !=0:
                    if r<self.row:
                        self.FitMap[r][c]+=1
                    if r>0:
                        self.FitMap[r-1][c]+=1
                if state&0x100 !=0:
                    if c < self.colum:
                        self.FitMap[r][c]+=1
                    if c>0:
                        self.FitMap[r][c-1]+=1
          
    def checkFit(self):
        self.createFitMap()
        #print(self.FitMap)
        succ=True
        for r in range(self.row):
            if not succ:
                break
            for c in range(self.colum):
                if self.numMap[r][c]>=0:
                    if self.numMap[r][c]!=self.FitMap[r][c]:
                        succ=False
                        break
        #print ("1",succ)
        contmp=copy.deepcopy(self.connected)
        trace=[]
        if succ:
            #print(contmp)
            for r in range(self.row+1):
                for c in range(self.colum+1):
                    if contmp[r][c]!=0:
                        break
                if contmp[r][c]!=0:
                    break
            #print("2",succ,r,c)
            if contmp[r][c]&1 ==0:
                succ=False
            else:
                startpoint=(r,c)
                trace.append(startpoint)
                direction=self.Right
                contmp[r][c]&=0xff00
                c+=1
                while (r,c)!=startpoint:
                    #print(100,succ,r,c,direction,contmp[r][c])
                    if (r,c) in trace:
                        succ=False
                        break
                    trace.append((r,c))
                    tmp=contmp[r][c]
                    if direction==self.Right:
                        if tmp&1 !=0: #go right
                            contmp[r][c]&=0xff00
                            direction=self.Right
                            c+=1
                            continue
                        elif tmp&0x100 !=0:  #go down
                            contmp[r][c]&=0xff
                            direction=self.Down
                            r+=1
                            continue
                        elif r>0 and contmp[r-1][c]&0x100 !=0: #go up
                            r-=1
                            contmp[r][c]&=0xff
                            direction=self.Up
                            continue
                        else:
                            succ=False
                            break
                    elif direction==self.Down:
                        #print(10,r,c ,contmp[r][c])
                        if tmp&1 !=0: #go right
                            #print(11,contmp[r][c])
                            contmp[r][c]&=0xff00
                            direction=self.Right
                            c+=1
                            continue
                        elif tmp&0x100 !=0:  #go down
                            #print(12,contmp[r][c])
                            contmp[r][c]&=0xff
                            direction=self.Down
                            r+=1
                            continue
                        elif c>0 and contmp[r][c-1] &1 !=0:  #go left
                            #print(13,contmp[r][c-1])
                            c-=1
                            contmp[r][c]&=0xff00
                            direction=self.Left
                            continue
                        else:
                            succ=False
                            break
                    elif direction==self.Left:
                        if tmp&0x100 !=0:  #go down
                            contmp[r][c]&=0xff
                            direction=self.Down
                            r+=1
                            continue
                        elif c>0 and contmp[r][c-1] &1 !=0:  #go left
                            c-=1
                            contmp[r][c]&=0xff00
                            direction=self.Left
                            continue
                        elif r>0 and contmp[r-1][c] &0x100 !=0: # go up      
                            r-=1
                            contmp[r][c]&=0xff
                            direction=self.Up
                            continue
                        else:
                            succ=False
                            break
                    elif direction==self.Up:
                        if tmp&1 !=0: #go right
                            contmp[r][c]&=0xff00
                            direction=self.Right
                            c+=1
                            continue
                        elif c>0 and contmp[r][c-1] &1 !=0:  #go left
                            c-=1
                            contmp[r][c]&=0xff00
                            direction=self.Left   
                            continue
                        elif r>0 and contmp[r-1][c] &0x100 !=0: # go up      
                            r-=1
                            contmp[r][c]&=0xff
                            direction=self.Up
                            continue
                        else:
                            succ=False
                            break                            
                #print("3",succ)
                if succ:
                    if (r,c)!=startpoint:
                        succ=False
                    else:
                        for r in range(self.row+1):
                            if not succ:
                                break
                            for c in range(self.colum+1):
                                if contmp[r][c]!=0:
                                    succ=False
                                    break
                #print("4",trace,succ)
        self.succ=succ
        #print ("final",succ)
        return succ
                
                
    #---------------------------------------------------------------
    def setConnect(self,row,colum, direction, state , lock):
        # set the connected state of lines , row and colum defind the number ,drection in self.Up,Left,Down,Right
        #state in   CONNECT DISCONNECT or INVERSE or UNCHAGE
        #lock  in TRUE , FALSE or INVERSE  or UNCHANGE
        #adjust direction to up right and right 
        if not self.connected:
            return False
        if direction==self.Down:
            row+=1
            direction=self.Up
        elif direction==self.Right:
            colum+=1
            direction=self.Left
            
        if row>self.row or colum>self.colum:  
            return False
        elif direction not in (0,1):
            return False
        elif state not in (-2,-1,0,1):
            return False
        elif lock not in (-2,-1,0,1):
            return False
        #if change the connect , close answer show
        self.seeAnswer=False
        curstate=self.connected[row][colum]
        tmp=curstate
        if direction==self.Left:
            tmp>>=8
        else:
            tmp&=0xff
        if lock!=self.UNCHANGE:
            if lock==self.INVERSE:
                tmp^=0x10
            else:
                tmp=(tmp&0xf) |(lock<<4)
        if state !=self.UNCHANGE:
            if state==self.INVERSE:
                tmp^=1
            else:
                tmp=(tmp&0xf0)|state
        if direction==self.Left:
            self.connected[row][colum]= (curstate&0xff ) | tmp<<8
        else:
            self.connected[row][colum]= (curstate&0xff00 ) | tmp
        self.checkFit()
        return True
    #---------------------------------------------------------------------
    def clear(self):
        self.connected=[]
        for i in range(self.row+1):
            a=[0]*(self.colum+1)
            self.connected.append(a)
        self.FitMap=[]
        for i in range(self.row):
            a=[0]*(self.colum)
            self.FitMap.append(a)
        self.succ=False
        self.finish=False
            
    def calcuHash(self):
        s=""
        for r in self.numMap:
            for num in r:
                s=s+repr(num)
        #print(s)
        m=hashlib.md5(s.encode('utf-8'))
        self.mapHash=m.hexdigest()
        #print (s,"\nhash=",self.mapHash)
def loadLeves(tarList, defFile,savefile=None):
    if os.path.isfile(defFile):
        with open(defFile,'r') as f:
            #  this code is very important,if not this code ,when call exec in a function,
            # it will not see the variables in current environment
            loc=locals()
            exec(f.read())
            Levels=loc["Levels"]
            #or you can use a global dic as a parameters when call exec like this
            '''a=1
            
            
            loc={'a':a}
            glb={}
            exec(f.read(),glb,loc)
            Levels=loc["Levels"]'''
            
            try:
                for lv in Levels:
                    if "field" in lv:
                        nummap=lv["field"]
                        if "title" in lv:
                            title=lv["title"]
                        else:
                            title=""
                        if "difficulty" in lv:
                            diff=lv["difficulty"]
                        else:
                            diff=0
                        if "answer" in lv:
                            answer=lv["answer"]
                        else:
                            answer=None
                        if "connected" in lv:
                            con = lv["connected"]
                        else:
                            con=None
                            
                        gmap=GameMap(nummap,title,diff,answer,con)
                        if gmap.numMap:
                            tarList.append(gmap)
            except:
                print("load map error")
        if savefile and os.path.isfile(savefile):
            with open(savefile,'r') as sf:
                loc=locals()
                exec(sf.read())
                saves=loc["saves"]
                try:
                    for sv in saves:
                        for gmap in tarList:
                            if gmap.mapHash==sv["hash"]:
                                if  "connected" in sv:
                                    gmap.connected=sv["connected"]
                                    gmap.checkFit()
                                    gmap.finish=gmap.succ
                                if "answer" in sv:
                                    gmap.answer = sv["answer"]
                                break
                except:
                    print("load save error")
                    
def saveLeves(tarList, savefile):
    if len(tarList)>0:
        f=open(savefile,'w')
        f.write("saves=[ \n")
        for gmap in tarList:
            f.write( "{'hash':'%s' ,\n"% gmap.mapHash)
            if gmap.title:
                f.write("'title': '")
                f.write(gmap.title)
                f.write(" ' ,\n")
            f.write("'connected' :")
            f.write( repr(gmap.connected))
            if gmap.answer:
                f.write(",\n'answer':")
                f.write(repr(gmap.answer))
            f.write("\n},\n")
        f.write("]\n")
        f.close()
    
if __name__=="__main__":
    lvs=[]
    loadLeves(lvs,".\levels.py")            

初始化配置文件 ini.py


initsize=(800,1000)
minisize=(600,800)
toolbarHeight=30

mapfilepath=".\levels.py"
savefilepath=".\saves.py"

地图文件 levels.py,里面有两个测试用的地图配置

# -*- coding: utf-8 -*-
n=-1
Levels=[
       {"size":[7,10],
        "title":"test 1",
       	"difficulty":1,
       "field":[[2,n,1,1,0,1,n],
              [n,n,1,n,n,n,n],
              [2,n,n,1,n,0,1],
              [n,n,3,n,n,2,n],
              [n,n,2,n,n,n,n],
              [n,n,n,n,3,n,n],
              [n,0,n,n,0,n,n],
              [3,2,n,3,n,n,3],
              [n,n,n,n,0,n,n],
              [n,3,2,2,2,n,3]]  
       },
       {"size":[3,3],
       	"title":"test 2",
       	"difficulty":1,
       	"field":[[3,n,3],
       			[3,n,3],
       			[3,n,3]]
       }
       ]

     最后是在线存盘文件saves.py,可以在线生成

saves=[ 
{'hash':'e0cd7477fa558ad58b45633bdf29ba20' ,
'title': 'test 1 ' ,
'connected' :[[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]],
'answer':[[257, 1, 256, 0, 0, 0, 0, 0], [256, 257, 0, 257, 256, 257, 1, 256], [256, 256, 0, 256, 1, 0, 0, 256], [256, 256, 257, 0, 257, 256, 0, 256], [256, 256, 1, 1, 0, 1, 256, 256], [256, 1, 1, 256, 257, 256, 256, 256], [256, 0, 0, 1, 0, 1, 0, 256], [1, 256, 257, 1, 256, 0, 257, 0], [257, 0, 256, 257, 0, 0, 1, 256], [1, 256, 256, 256, 0, 257, 256, 256], [0, 1, 0, 1, 1, 0, 1, 0]]
},
{'hash':'c5dd983b8c34f667a89585c914998a0c' ,
'title': 'test 2 ' ,
'connected' :[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]],
'answer':[[257, 1, 1, 256], [1, 256, 257, 0], [257, 0, 1, 256], [1, 1, 1, 0]]
},
]

最后还有一个坑,我开始用python自带的hash函数算地图hash,结果发现竟然每次运行算出来的都不一样,这真是再次刷新我的认知了,只好换了md5函数。哎,路漫漫其修远兮,深坑更浅坑……

最后的最后,说下这个程序的功能,左键点击可以连线和取消连线,右键是锁定和取消锁定,锁定是对可以确定有连接或不能连接的地方加以标记,锁定后左键点击就不能改变了。就像这个样子。(锁定的线条粗一些,锁定不连接的地方有红叉)

 

成功连接完成后有完成提示,中途正常关闭会保持当前盘面,再次打开后可以继续接着玩。如果成功完成还可以自动作为答案保存,有答案的可以看答案。地图文件里只有两个地图,可以自己加,格式很简单。

好了,接下来该回归编这个程序的最初目的了,就是自动求解数回的算法,等完成了再写一个博客吧。

发布了11 篇原创文章 · 获赞 13 · 访问量 14万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览