File: android-tkinter/CODE/tictactoe.py

"""
=======================================================================
[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('<KeyPress-question>', 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('<Button-1>', 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('<KeyPress-question>', 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...



[Home page] Books Code Blog Python Author Train Find ©M.Lutz