""" ======================================================================= [SA] Sep-2017: Standalone release of PyCalc, PyClock, PyPhoto, PyToe. Copyright 2017 M.Lutz, from book "Programming Python, 4th Edition". License: provided freely, but with no warranties of any kind. This is main logic; see tictactoe_lists.py for the meat of the system. 2.0: added DemoMode config: if True, displays N preset boards/games. # ANDROID version, Jan 2019 (see "# ANDROID" for changes) ======================================================================= """ # [PP4E] this file has been updated for Python 3.X import sys, copy # ANDROID: tkinter import must be on a line by itself, import tkinter # ANDROID: else the GUI support is not loaded/present from tictactoe_lists import * # move-mode subclasses from tictactoe_lists import helpdisplay # common help utility from getConfigs import getConfigs # file-or-args configs from getConfigs import attrsToDict, dictToAttrs # for passing **kargs # [SA] Mac and Linux port things RunningOnMac = sys.platform.startswith('darwin') RunningOnLinux = sys.platform.startswith('linux') RunningOnWindows = sys.platform.startswith('win') # [SA]: set window icons on Windows and Linux from windowicons import trySetWindowIcon #---------------------------------------------------------------------- # Game object generator - external interface #---------------------------------------------------------------------- def TicTacToe(root, Mode, **args): # this consumes Mode classname = 'TicTacToe' + Mode # e.g., -mode Minimax try: classobj = eval(classname) # get class by string name except: print('Bad Mode option value:', mode) raise # reraise else: # [SA] was eval(classname)(**args) return classobj(root, **args) # run class constructor (3.x: was apply()) #---------------------------------------------------------------------- # Configurations interface - from file or cmdline args, with defaults #---------------------------------------------------------------------- defaultConfigs = dict( DemoMode=False, # use preset boards? InitialSize=None, # WxH, '200x300' BgColor='wheat', FgColor='black', Font=('courier', 50, 'bold'), # use 'family...' if arg Degree=3, # 3 across = tic-tac-toe GoesFirst='user', # 'user' or 'machine' UserMark='X', # 'X' or 'O' Mode='Minimax') # 5 classes in module def userconfigs(): """ [SA] new common gadgets utility: file or cmdline e.g., python3 tictactoe.py -configs ~/myconfigs.py e.g., python3 tictactoe.py -Degree 4 -Mode Expert2 -Font 'menlo 40' """ configs = getConfigs('PyToe', defaultConfigs) # load from file or args configs.Degree = int(configs.Degree) # str->int iff needed return configs #---------------------------------------------------------------------- # Board builders - 1/configs for normal mode, N/presets for demo mode #---------------------------------------------------------------------- def makeBoard(configs, kind=Tk): """ create 1 board, according to configs """ root = kind() trySetWindowIcon(root, 'icons', 'pygadgets') # [SA] for win+lin if configs.InitialSize: # None size is probably best root.geometry(configs.InitialSize) frm = TicTacToe(root, **attrsToDict(configs)) # build board frame on root if kind == Tk: setAppleReopen(root) # works on Tk only, DemoMode is just one process # [SA] question=? but portable, help key in all gadgets root.bind('', lambda event: helpdisplay(root)) def demoMode(configs): """ create N boards per preset configs for variety and demo """ class DemoWindow(GuiMakerWindowMenu): """ a Frame on a simple Tk root: global help and quit, quit closes all windows; boards are Toplevels: board=>quit closes just itself; """ appname = 'PyToe' # for new guimaker def start(self): self.helpButton = False # [SA] Mac OS help automatic in guimaker if not RunningOnMac: self.menubar = [('Help', 0, [('About', 0, self.onAbout, '*-h')] )] def makeWidgets(self): greeting = Label(self, text='Welcome to PyToe DemoMode', padx=5, pady=3) greeting.config(font=('times', 20, 'italic')) greeting.pack(expand=YES, fill=BOTH) greeting.bind('', lambda event: helpdisplay(root)) # for fun def onAbout(self): helpdisplay(self) onHelp = onAbout # [SA] for Mac OS, new guimaker root = Tk() root.title('PyToe 2.0') trySetWindowIcon(root, 'icons', 'pygadgets') # for Win+Lin setAppleReopen(root) # for Mac frm = DemoWindow(root) # [SA] question=? but portable, help key in all gadgets root.bind('', lambda event: helpdisplay(root)) # attrs may have been set in file or cmd teal = 'teal' if tkinter.TkVersion >= 8.6 else '#006e6d' # in 8.6+ modconfigs = [ dict(Degree=5, Mode='Expert2', BgColor=teal, FgColor='cyan'), dict(Degree=4, Mode='Expert2', BgColor='wheat', FgColor='black'), dict(Degree=3, Mode='Minimax', BgColor='#173166', FgColor='white'), dict(Degree=2, Mode='Minimax', BgColor='#633025', FgColor='white')] # popup smallest last = on top: last made is drawn first # Linux scatters the boards, but best on all platforms if RunningOnMac or RunningOnWindows or RunningOnLinux: modconfigs = reversed(modconfigs) # overide settings or defaults for modconfig in modconfigs: democonfigs = attrsToDict(configs) democonfigs.update(modconfig) democonfigs = dictToAttrs(democonfigs) democonfigs.InitialSize = None makeBoard(democonfigs, kind=Toplevel) # else covered and not focused on Windows and Linux if RunningOnWindows or RunningOnLinux: root.focus() # this works and suffices on both #frm.lift() # root.lift leaves hidden on Windows #frm.focus_force() # so does root.after_idle(root.lift) def setAppleReopen(root): """ configure standard events """ if RunningOnMac: # Mac requires menus, deiconifies, focus # [SA] reopen auto on dock/app click and fix tk focus loss on deiconify def onReopen(): root.lift() root.update() temp = Toplevel() temp.lower() temp.destroy() root.createcommand('::tk::mac::ReopenApplication', onReopen) #---------------------------------------------------------------------- # Main logic - fetch options from file or command-line, run game #---------------------------------------------------------------------- if __name__ == '__main__': configs = userconfigs() if configs.DemoMode: demoMode(configs) else: makeBoard(configs) mainloop() # and wait for user to play...