连点成图:享受创建图形的乐趣

      定位

      本文适合于想要使用 wxpython 进行 python gui 编程的筒鞋。 读完本文后,应当能了解 wxpython 编写基本 GUI 的知识,以及熟悉策略设计模式的使用。   

       缘起

       连点成图:享受创建图形的乐趣

         发现这个游戏还挺有意思的, 不如自己来开发个?   主要设计思路如下:

          1.  使用 wxpython 做基本的 GUI , 可以生成 n * n 的初始点阵图 , n = (2, 25)  ;

          2.  使用 Python 实现策略模式; 编写了一个可扩展的小框架, 只要编写 xxxStrategyOfLinkpoints , 并添加到 strategyManager.strategiesForlinkPoints 即可; 实现委托模式, 绘图策略相关的接口均由 LinkPointStrategy 来提供, 由 StrategyManager 来管理; StrategyManager  使用单例模式来实现;

          3.  点阵图的算法, 程序员自然是使用程序生成, 既有趣也很锻炼算法思维连带激发创造力哦。已经对点阵图的点联接实现了抽象, 只需要当做 (0,0) - (n-1,n-1) 的二维矩阵来考虑。 算法需要生成一个列表, 列表中的每个元素是一个点对元组 ((x1,y1), (x2, y2)), 每个点是一个元组,包含该点的逻辑坐标。 不需要涉及点的实际坐标。

          4.  实现拖拽的创作模式, 在鼠标按下时获取位置, 检测是否有效点范围并记录起始点, 在鼠标释放时记下结束点并取出起始点进行绘图。 

               不足之处是, 在鼠标释放之前, 所绘制的直线是不可见的, 这一点用户体验不佳。暂时没有找到方法在鼠标移动过程中实时绘线后再删除。

          5.  回退机制: 只能回到过去和现在, 无法回到过去的未来。比如从 A 到 X, 然后回退到 R, 接着前进到 S', 那么之前的 S 到 X 的部分都将丢失。

               鉴于数据量比较少, 采用简单的方案。 保持一份工作状态的列表以及一个指向某个工作状态的指针。 回退或前进只是移动指针。如果回退后push, 那么需要将回退前的回退点之后的部分截掉。

          6.  保存和从文件恢复功能。 采用可读的文本形式, 便于分析和调试。

 

         改进点:

             1.  从欣赏模式到创作模式或保存文件的过程中会有“未响应”的问题, 用户体验不佳;

             2.  创建常用简单图案的函数, 更方便地使用算法组合出更复杂更美妙的图案;

             3.  关卡设置。

                       

             你也来试试吧! 如果有兴趣做成移动游戏, 那就更棒了!

          

       连点成图:享受创建图形的乐趣

      

      源程序:

  linkpointsUI.py 

      

# -*- coding: utf8 -*-
# -------------------------------------------------------------------------------
# Name:        linkpointsUI.py
# Purpose:     a game which links points to a gragh and enjoy
#
# Author:      qin.shuq
#
# Created:      12/06/2014
# Copyright:   (c) qin.shuq 2014
# Licence:     ONLY BE USED FOR STUDY
#-------------------------------------------------------------------------------


import wx
import time
import math
import os
import threading
import copy
from LinkPointStrategy import *


class LinkPointsFrame(wx.Frame):
    '''
       generate dotSize * dotSize dotted graph and app ui
    '''
    def __init__(self, parent, title, dotSize=18, uiSize=(810,560)):
        wx.Frame.__init__(self, parent, title=title, size=uiSize)
        self.mainPanel = None            # 主面板,用于绘制点阵图
        self.dc = None                   # 用于绘制图形的对象
        self.dotSize = dotSize           # 点阵图大小设定,形成 dotSize * dotSize 点阵图
        self.displayDemoTimer = None     # 欣赏模式下自动显示已有创作的定时器
        self.validPointsRange = set()    # 拖拽模式时有效点的范围  set([px, py])
        self.isCreateMode = False        # 是否创作模式
        self.origin = 10                 # 原点的实际坐标
        self.pointRadius = 3             # 点使用实心圆圈表示,增大点击范围
        self.mousePostion = MousePositionEachPressAndRelease()   # 拖拽时记录下鼠标所在位置
        self.currWork = []               # 记录创作模式的当前工作以便于保存
        self.history = WorkHistory()     # 记录当前工作状态的历史,便于回退及前进

        panelSize = self.GetClientSize()

        topBottomMargin = 20
        leftRightMargin = 30
        uiWidth = panelSize[0] - leftRightMargin
        panelHeight = panelSize[1] - topBottomMargin

        self.intervalBetweenPoints = (panelHeight-self.origin*2) / (self.dotSize-1)

        self.validPointsRange = self.obtainRealCoordsOfDottedPoints()

        self.mainPanelSize = (panelHeight, panelHeight)
        self.ctrlPanelSize = (uiWidth - self.mainPanelSize[0], panelHeight)

        self.initUI()
        self.Centre()


    def initUI(self):

        ### UI Design follows top-down thinking and down-top building

        bigPanel = wx.Panel(self, name="WhileWindow")
        font = wx.Font(12, wx.ROMAN, wx.NORMAL, wx.NORMAL)

        hboxLayout = wx.BoxSizer(wx.HORIZONTAL)
        self.mainpanel = wx.Panel(bigPanel, name="mainPanel", size=self.mainPanelSize)
        self.mainpanel.SetBackgroundColour('#fffff0')

        self.mainpanel.Bind(wx.EVT_KEY_DOWN, self.onKeyDown)
        self.mainpanel.Bind(wx.EVT_LEFT_DOWN, self.mouseLeftPressHandler)
        #self.mainpanel.Bind(wx.EVT_MOTION, self.mouseMoveHandler)
        self.mainpanel.Bind(wx.EVT_LEFT_UP, self.mouseLeftReleaseHandler)


        ctrlPanel = wx.Panel(bigPanel, name="ctrlPanel", size=self.ctrlPanelSize)

        hboxLayout.Add(self.mainpanel, 0, wx.EXPAND|wx.ALL, 10)
        hboxLayout.Add(ctrlPanel, 0, wx.EXPAND|wx.ALL, 10)
        bigPanel.SetSizer(hboxLayout)

        topPanel = wx.Panel(ctrlPanel, name="topPanel")
        tipInfo ="How to Play: 

Just link points to build a graph, 
So Easy And Enjoy Yourself !

"
        keyInfo = "Press ESC to quit. 
Press z to back.
Press x to forward.
"
        staticText = wx.StaticText(topPanel, label=decodeUTF8(tipInfo+keyInfo))
        staticText.SetFont(font)

        btnBoxSizer = wx.GridSizer(8,2, 10, 5)

        buttonSize = (100, 30)
        enterCreateModeBtn = wx.Button(ctrlPanel, name="createMode", label=decodeUTF8("创作模式"), size=buttonSize)
        enterDemoModeBtn = wx.Button(ctrlPanel, name="demoMode", label=decodeUTF8("欣赏模式"), size=buttonSize)
        saveBtn = wx.Button(ctrlPanel, name="SaveWork", label=decodeUTF8("保存工作"), size=buttonSize)
        restoreBtn = wx.Button(ctrlPanel, name="restore", label=decodeUTF8("恢复已存工作"), size=buttonSize)

        self.Bind(wx.EVT_BUTTON, self.enterCreateMode, enterCreateModeBtn)
        self.Bind(wx.EVT_BUTTON, self.enterDemoMode, enterDemoModeBtn)
        self.Bind(wx.EVT_BUTTON, self.saveWork, saveBtn)
        self.Bind(wx.EVT_BUTTON, self.restoreWork, restoreBtn)

        btnBoxSizer.Add(enterCreateModeBtn, 0, wx.ALL)
        btnBoxSizer.Add(enterDemoModeBtn, 0, wx.ALL)
        btnBoxSizer.Add(saveBtn,0, wx.ALL)
        btnBoxSizer.Add(restoreBtn,0, wx.ALL)

        vboxLayout = wx.BoxSizer(wx.VERTICAL)
        vboxLayout.Add(topPanel, 1, wx.EXPAND|wx.ALL, 5)
        vboxLayout.Add(btnBoxSizer, 1, wx.EXPAND|wx.ALL, 5)
        ctrlPanel.SetSizer(vboxLayout)

        self.Show(True)

        # show demo
        self.displayDemoTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.displayDemoGraph, self.displayDemoTimer)
        self.createDemoForUsage()
        self.displayDemoInTimer()


    def enterCreateMode(self, event):
        self.mainpanel.SetFocus()   # 使键盘事件获得响应
        self.isCreateMode = True
        if self.displayDemoTimer:
            self.displayDemoTimer.Stop()
        self.createNewDottedGraph()
        self.history.clear()
        self.currWork = []


    def enterDemoMode(self, event):
        self.mainpanel.SetFocus()
        self.isCreateMode = False
        self.displayDemoTimer.Start(100, oneShot=True)


    def createNewDottedGraph(self):
        '''
            清空屏幕, 重新绘制点阵图
        '''
        if self.dc:
            self.dc.Clear()
        self.dc = wx.ClientDC(self.mainpanel)
        self.dc.SetPen(wx.Pen('GREEN'))
        self.dc.SetBrush(wx.Brush('GREEN'))
        for xcoord in range(self.origin, self.mainPanelSize[0] + self.intervalBetweenPoints, self.intervalBetweenPoints):
            for ycoord in range(self.origin, self.mainPanelSize[1] + self.intervalBetweenPoints, self.intervalBetweenPoints):
                self.dc.DrawPoint(xcoord, ycoord)
                self.dc.DrawCircle(xcoord,ycoord, self.pointRadius)


    def createDemoForUsage(self):
        '''
            展示创建图案的接口用法
        '''
        self.createNewDottedGraph()
        linkpointsStrategy = LinkPointStrategy(self.dotSize)
        allLines = linkpointsStrategy.obtainAllLinesByLinkPoints()
        self.drawGraph(allLines)

        ### demo for registering user-defined strategy
        def myStrategy(allPoints, size):
            return [(point, (point[0]+1, point[1]+1)) for point in allPoints if (point[0] == point[1] and point[0]<size-1)]

        LinkPointStrategy.registerStrategy("my", myStrategy)
        LinkPointStrategy.setStrategy("my")
        self.createNewDottedGraph()
        self.drawGraph(linkpointsStrategy.obtainAllLinesByLinkPoints())


    def displayDemoGraph(self, event):
        linkpointsStrategy = LinkPointStrategy(self.dotSize)
        allStrategies = linkpointsStrategy.getAllStrategies()
        for strategyName in allStrategies:
            self.createNewDottedGraph()
            linkpointsStrategy.setStrategy(strategyName)
            self.drawGraph(linkpointsStrategy.obtainAllLinesByLinkPoints())
            time.sleep(2)


    def displayDemoInTimer(self):
        '''
            欣赏模式下使用定时器自动展示已创建的图案
        '''
        self.displayDemoTimer.Start(100, oneShot=True)


    def drawGraphForRealCoords(self, allLines):
        '''
           根据已生成的所有线的设置绘制图案
           一条线是一个元组: ((x1,y1), (x2, y2)) xi, yi 是实际坐标
        '''
        for line in allLines:
            self.dc.DrawLine(line[0][0], line[0][1], line[1][0], line[1][1])

    def drawGraph(self, allLines):
        '''
           根据已生成的所有线的设置绘制图案
           一条线是一个元组: ((x1,y1), (x2, y2)) xi, yi 是逻辑坐标
        '''
        #print '***************************************'
        for line in allLines:
            #print line[0][0], ' ', line[0][1], ' ', line[1][0], ' ', line[1][1]
            x1 = self.obtainRealCoords(line[0][0])
            y1 = self.obtainRealCoords(line[0][1])
            x2 = self.obtainRealCoords(line[1][0])
            y2 = self.obtainRealCoords(line[1][1])
            self.dc.DrawLine(x1, y1, x2, y2)


    def mouseLeftPressHandler(self, event):
        '''
           拖拽时鼠标按下时的动作
        '''
        if self.isCreateMode:
            pos = event.GetPosition()
            nearestPoint = self.nearestPoint(pos)
            if nearestPoint:
                self.mousePostion.pushPressPos(nearestPoint[0], nearestPoint[1])
            else:
                showMsgDialog('请将鼠标放于点的位置进行拖拽!', '提示')


    def mouseMoveHandler(self, event):
        '''
           拖拽时鼠标移动的动作
        '''
        pass
        # if event.Dragging() and event.LeftIsDown():
        #     pressPos = self.mousePostion.getPressPos()
        #     lastPos = self.mousePostion.getLastMovePos()
        #     moveNowPos = event.GetPosition()
        #     self.mousePostion.pushMovePos(moveNowPos[0], moveNowPos[1])
        #     #self.dc.DrawLine(pressPos[0], pressPos[1], moveNowPos[0], moveNowPos[1])
        # event.Skip()


    def mouseLeftReleaseHandler(self, event):
        '''
           拖拽时鼠标释放时的动作
        '''
        if self.isCreateMode:
            nearestStart = self.mousePostion.getPressPos()
            releasePos = event.GetPosition()
            nearestEnd = self.nearestPoint(releasePos)
            if nearestEnd:
                self.dc.DrawLine(nearestStart[0], nearestStart[1], nearestEnd[0], nearestEnd[1])
                self.currWork.append((nearestStart, nearestEnd))
                self.history.push(copy.copy(self.currWork))
            else:
                showMsgDialog('请将鼠标放于点的位置进行拖拽!', '提示')


    def onKeyDown(self, event):
        #self.history.show()
        kc=event.GetKeyCode()
        if kc == wx.WXK_ESCAPE:
            ret = wx.MessageBox(decodeUTF8("确定要退出程序吗?"), decodeUTF8("询问"),
                                wx.YES_NO|wx.NO_DEFAULT,self)
            if ret == wx.YES:
                self.Close()

        if kc == 90:  # press z
            lastWork = self.history.back()
            if lastWork is None:
                showMsgDialog('已经位于最开始的地方,无法回退!', '提示')
                return
            self.createNewDottedGraph()
            self.drawGraphForRealCoords(lastWork)
            self.currWork = copy.copy(lastWork)
        elif kc == 88:  # press x
            nextWork = self.history.forward()
            if nextWork is None:
                showMsgDialog('已经位于最后的状态,无法向前!', '提示')
                return
            self.createNewDottedGraph()
            self.drawGraphForRealCoords(nextWork)
            self.currWork = copy.copy(nextWork)
        #self.history.show()


    def obtainRealCoordsOfDottedPoints(self):
        '''
           获取点阵图中所有点的实际坐标
        '''
        validPointsRange = set()
        for localXCoord in range(self.dotSize):
            for localYCoord in range(self.dotSize):
                validPointsRange.add((self.obtainRealCoords(localXCoord), self.obtainRealCoords(localYCoord)))
        return validPointsRange


    def nearestPoint(self, point):
        '''
            鼠标按下或释放时判断鼠标位置是否处于有效点的范围,并获取最近的有效点用于连线
             如果鼠标位置未处于有效点的位置,则返回 None
        '''
        if point in self.validPointsRange:
            return point
        tolerance = self.intervalBetweenPoints/4  ### 允许用户点击离有效点范围很近的地方
        for validPoint in self.validPointsRange:
            if self.distance(point, validPoint) <= self.pointRadius + tolerance:
                return validPoint
        return None

    def distance(self, point1, point2):
        return math.hypot(point1[0]-point2[0], point1[1]-point2[1])

    def obtainRealCoords(self, localCoord):
        '''
            将逻辑坐标 (x,y) 转换为 实际坐标 (real_x, real_y).
            eg. 假设原点坐标是 (15,15), 点间隔为 (30, 30), 则 (1,1) -> (45,45)
            这样在绘图时就可以专注于以逻辑坐标来思考,摒弃实际坐标的细节干扰
        '''
        return self.origin+localCoord*self.intervalBetweenPoints

    def saveWork(self, event):
        self.mainpanel.SetFocus()
        file_wildcard = "files(*.lp)|*.lp|All files(*.*)|*.*"
        dlg = wx.FileDialog(self, "Save as ...", os.getcwd(), "default.lp",
                            style = wx.SAVE | wx.OVERWRITE_PROMPT, wildcard = file_wildcard)
        f_work = None
        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return
        filename = dlg.GetPath()
        if not os.path.splitext(filename)[1]: #如果没有文件名后缀
            filename = filename + '.lp'
        f_work = open(filename, 'w')
        dlg.Destroy()

        f_work.write("LINK POINTS FILE.
")
        for (startPoint , endPoint) in self.currWork:
            f_work.write(str(startPoint[0]) + ' ' + str(startPoint[1]) + ' ' + str(endPoint[0]) + ' ' + str(endPoint[1]) + '
')
        f_work.close()
        showMsgDialog('工作保存成功!^_^', '提示')

    def restoreWork(self, event):
        self.mainpanel.SetFocus()
        file_wildcard = "files(*.lp)|*.lp|All files(*.*)|*.*"
        dlg = wx.FileDialog(self, "Open file...", os.getcwd(), style = wx.OPEN, wildcard = file_wildcard)
        if dlg.ShowModal() != wx.ID_OK:
            dlg.Destroy()
            return

        filename = dlg.GetPath()
        f_work = open(filename)
        lines = f_work.readlines()
        f_work.close()
        dlg.Destroy()

        self.history.clear()
        if lines[0].strip() != 'LINK POINTS FILE.':
            showMsgDialog('文件类型无效,请打开后缀为.lp的文件!', '提示')
        else:
            self.createNewDottedGraph()
            self.currWork = []
            for line in lines[1:]:
                pointCoords = line.strip().split(' ')
                if len(pointCoords) != 4:
                    showMsgDialog('文件内容已损坏!', '提示')
                    return
                startPointX = pointCoords[0]
                startPointY = pointCoords[1]
                endPointX = pointCoords[2]
                endPointY = pointCoords[3]
                try:
                    self.dc.DrawLine(int(startPointX), int(startPointY), int(endPointX), int(endPointY))
                    self.currWork.append( ((int(startPointX), int(startPointY)), (int(endPointX), int(endPointY))) )
                except:
                    showMsgDialog('文件内容已损坏!', '提示')
                    return
            self.history.push(self.currWork)
            showMsgDialog('成功恢复工作,自动进入创作模式!^_^ ', '提示')
            self.isCreateMode = True


class MousePositionEachPressAndRelease(object):
    '''
        mousePosition: [(xpress, ypress), (xlastMove, ylastMove)]
    '''
    def __init__(self):
        self.mousePosition = []

    def pushPressPos(self, xcoord, ycoord):
        self.mousePosition.insert(0, (xcoord, ycoord))

    def pushMovePos(self, xcoord, ycoord):
        self.mousePosition.insert(1, (xcoord, ycoord))

    def getPressPos(self):
        return self.mousePosition[0]

    def getLastMovePos(self):
        return  self.mousePosition[1]

class WorkHistory(object):
    '''
        保存工作快照列表,实现回退功能
    '''
    def __init__(self):
        self.worksnapshots = [[]]
        self.currPoint = 0

    def push(self, currWork):
        ### 如果在回退操作之后立即 push , 则回退之前从回退点之后的动作都将清空
        self.currPoint+=1
        self.worksnapshots = self.worksnapshots[0: self.currPoint]
        self.worksnapshots.append(currWork)


    def back(self):
        if self.currPoint <= 0:
            return None
        else:
            self.currPoint-=1
            return self.worksnapshots[self.currPoint]

    def forward(self):
        if self.currPoint >= len(self.worksnapshots)-1:
            return None
        else:
            self.currPoint+=1
            return self.worksnapshots[self.currPoint]

    def clear(self):
        self.worksnapshots = [[]]
        self.currPoint = 0

    def show(self):
        print "curr point: ", self.currPoint
        for snapshot in self.worksnapshots:
            print snapshot

# utils
def decodeUTF8(msg):
    return msg.decode('utf8')

def showMsgDialog(msg, title):
    dialog = wx.MessageDialog(None, decodeUTF8(msg), decodeUTF8(title), wx.YES_DEFAULT)
    dialog.ShowModal()
    dialog.Destroy()

def main():

    app = wx.App(False)
    frame = LinkPointsFrame(None, decodeUTF8('连点成图: 享受创建图形的乐趣'))
    app.MainLoop()


if __name__ == '__main__':
    main()

    LinkPointStrategy.py

    

# -*- coding: utf8 -*-
# -------------------------------------------------------------------------------
# Name:        LinkPointStrategy.py
# Purpose:     varieties of algorithms for linking points
#
# Author:      qin.shuq
#
# Created:     11/29/2014
# Copyright:   (c) qin.shuq 2014
# Licence:     ONLY BE USED FOR STUDY
# -------------------------------------------------------------------------------

def linesForDiamond(centerPoint, radius):
    '''
       centerPoint: (localXCoord, localYCoord)
       radius: the distance from points in diamond to centerpoint
    '''
    centerx = centerPoint[0]
    centery = centerPoint[1]
    leftPoint = (centerx-1, centery)
    rightPoint = (centerx+1, centery)
    topPoint = (centerx, centery-1)
    bottomPoint = (centerx, centery+1)
    return [(leftPoint, topPoint), (topPoint, rightPoint), (rightPoint, bottomPoint), (bottomPoint, leftPoint)]

def repeatedDiamondStrategy(allPoints, size):
    allLines = []
    radius = 2
    for point in allPoints:
        if not isOutOfBound(point, radius, size):
            allLines.extend(linesForDiamond(point, radius))
    return allLines

def isOutOfBound(centerPoint, radius, dotSize):
    if centerPoint[0] <= radius-1 or centerPoint[0] + radius >= dotSize:
        return True
    if centerPoint[1] <= radius-1 or centerPoint[1] + radius >= dotSize:
        return True
    return False

def simpleLoopStrategyOfLinkpoints(allPoints, size):
    pairs = []
    for i in range(size):
        if i*2 <= size-1:
            pairs.append((i, size-1-i))
    allLines = []
    for pair in pairs:
        allLines.append( ((pair[0], pair[0]), (pair[0], pair[1])) )
        allLines.append( ((pair[0], pair[0]), (pair[1], pair[0])) )
        allLines.append( ((pair[0], pair[1]), (pair[1], pair[1])) )
        allLines.append( ((pair[1], pair[0]), (pair[1], pair[1])) )
    return allLines


def loopStrategyOfLinkpoints(allPoints, size):
    pairs = []
    for i in range(size):
        if i*2 <= size-1:
            pairs.append((i, size-1-i))
    allLines = []
    for pair in pairs:
        begin = (pair[0], pair[0])
        end = (pair[1], pair[1])
        for localXCoord in range(pair[0], pair[1], 1):
            allLines.append(((pair[0], localXCoord), (pair[0], localXCoord+1)))
            allLines.append(((pair[1], localXCoord), (pair[1], localXCoord+1)))
        for localYCoord in range(pair[0], pair[1], 1):
            allLines.append(((localYCoord, pair[0]), (localYCoord+1, pair[0])))
            allLines.append(((localYCoord, pair[1]), (localYCoord+1, pair[1])))
    return allLines


def defaultStrategyOfLinkpoints(allPoints, size):
    return [( point, (point[0]+1, point[1]+1) )
                for point in allPoints if not isRightOrButtomBoundPoint(point, size)]


def isRightOrButtomBoundPoint(point, size):
    localXCoord = point[0]
    localYCoord = point[1]
    return localXCoord == size-1 or localYCoord == size-1


def singleton(cls):
    '''
       Implements Singleton pattern in python.
       From  http://blog.csdn.net/ghostfromheaven/article/details/7671853
    '''
    instances = {}
    def _singleton(*args, **kw):
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return _singleton

@singleton
class StrategyManager(object):

    def __init__(self):
        self.strategiesForlinkPoints = {
            'default': defaultStrategyOfLinkpoints,
            'loop': loopStrategyOfLinkpoints,
            'simpleLoop': simpleLoopStrategyOfLinkpoints,
            'diamond': repeatedDiamondStrategy
        }
        self.DEFAULT_STRATEGY = self.strategiesForlinkPoints['default']
        self.CURR_STRATEGY = self.DEFAULT_STRATEGY

    def getStrategy(self, strategyName):
        strategyForLinkPoints = self.strategiesForlinkPoints.get(strategyName)
        if strategyForLinkPoints is None:
            raise Exception('No stragegy named "%s". You can write one. ' % strategyName)
        return strategyForLinkPoints

    def registerStrategy(self, strategyName, strategyForLinkPoints):
        oldStrategy = self.strategiesForlinkPoints.get(strategyName)
        if oldStrategy:
            self.strategiesForlinkPoints['old_' + strategyName] = oldStrategy
        self.strategiesForlinkPoints[strategyName] = strategyForLinkPoints

    def setCurrStrategy(self, strategyName):
        self.CURR_STRATEGY = self.getStrategy(strategyName)

    def getCurrStratety(self):
        return self.CURR_STRATEGY

    def getAllStrategies(self):
        return self.strategiesForlinkPoints.keys()


class LinkPointStrategy(object):
    '''
       just think in a dotted graph of  (0,0) - (dotSize-1, dotSize-1) with interval of points = 1
       (0,0), (0,1), ... , (0, dotSize-1)
       (1,0), (1,1), ... , (1, dotSize-1)
        ... ,  ... , ... ,  ...
       (dotSize-1,0), (dotSize-1, 1), ..., (dotSize-1, dotSize-1)
       and output a set of [((x1,y1), (x2,y2)), ..., ((xm,ym), (xn,yn))]
    '''

    strategyManager = StrategyManager()

    def __init__(self, dotSize):
        self.dotSize = dotSize
        self.allPoints = []

        for localXCoord in range(dotSize):
            for localYCoord in range(dotSize):
                self.allPoints.append((localXCoord, localYCoord))


    @classmethod
    def setStrategy(cls, strategyName):
        cls.strategyManager.setCurrStrategy(strategyName)

    @classmethod
    def getStrategy(cls, strategyName):
        return cls.strategyManager.getStrategy(strategyName)

    @classmethod
    def registerStrategy(cls, strategyName, strategyFunc):
        cls.strategyManager.registerStrategy(strategyName, strategyFunc)

    @classmethod
    def getAllStrategies(cls):
        return cls.strategyManager.getAllStrategies()

    def obtainAllLinesByLinkPoints(self):
        '''
           generate all lines between points according to given strategy which is a algorithm of linking points
           line: a tuple of (x1, y1, x2, y2)
           note: (x1, y1, x2, y2) are local coordinates which will be converted into real coordinates upon drawing
        '''
        currStrategy = LinkPointStrategy.strategyManager.getCurrStratety()
        return currStrategy(self.allPoints, self.dotSize)

if __name__ == '__main__':
    strategyManager = StrategyManager()
    anoStrategyManager = StrategyManager()
    assert id(strategyManager) == id(anoStrategyManager)