Module Python-Screen-Stack-Manager.examples.sudoku

Expand source code
#!/usr/bin/env python
import sys
sys.path.append("../")
import os
import pssm
import platform
import random
if platform.machine() in ["x86", "AMD64", "i686", "x86_64"]:
    device = "Emulator"
else:
    device = "Kobo"

# ###################### - INIT - ###########################################
# Declare the Screen Stack Manager
screen = pssm.PSSMScreen(device, 'Main')
# Clear and refresh the screen
screen.clear()
screen.refresh()
# Now to initialize the keybaord
screen.OSKInit(area=None)

# Variables:
MARGIN = "H*0.0063"
BIG_MARGIN = "H*0.015"
ERROR_INVERT_DURATION = 1
cursor_position = None
elt_grid = [[None for j in range(9)] for i in range(9)]

# ###################### - SUDOKU LOGIC - ####################################
def isWrong(grid, pos, userInput):
    """
    Returns False if grid is valid, else (i, j) where (i, j) indicate the coords
    of the cell which the addition contradicts
    """
    (i0, j0) = pos
    current = grid[i0][j0]
    # In the same col?
    for i in range(9):
        if grid[i][j0] == current and i!=i0:
            return (i, j0)
    # In the same line?
    for j in range(9):
        if grid[i0][j] == current and j != j0:
            return (i0, j)
    # In the same square?
    for i in range(i0 - i0%3, i0 - i0%3 + 3):
        for j in range(j0 - j0%3, j0 - j0%3 + 3):
            if grid[i][j] == current and i != i0 and j != j0:
                return (i, j)
    return False


def generateSudokuGrid(numberOfCells):
    """
    https://stackoverflow.com/questions/45471152/how-to-create-a-sudoku-puzzle-in-python
    """
    if numberOfCells >= 81 or numberOfCells < 0:
        # Return empty grid
        return [["" for j in range(9)] for i in range(9)]
    # Create Board
    base  = 3
    side  = base*base
    # pattern for a baseline valid solution
    def pattern(r,c): return (base*(r%base)+r//base+c)%side
    # randomize rows, columns and numbers (of valid base pattern)
    from random import sample
    def shuffle(s): return sample(s,len(s))
    rBase = range(base)
    rows  = [ g*base + r for g in shuffle(rBase) for r in shuffle(rBase) ]
    cols  = [ g*base + c for g in shuffle(rBase) for c in shuffle(rBase) ]
    nums  = shuffle(range(1,base*base+1))
    # produce board using randomized baseline pattern
    board = [ [str(nums[pattern(r,c)]) for c in cols] for r in rows ]

    # THEN REMOVE 'numberOfCells' elements.
    indexesToKeep = []
    for i in range(9):
        for j in range(9):
            indexesToKeep.append((i,j))
    for n in range(numberOfCells):
        indexesToKeep.pop(random.randrange(len(indexesToKeep)))
    for (i,j) in indexesToKeep:
        board[i][j] = ""
    return board


# ###################### - GUI LOGIC - #######################################
def getBoardLayout(grid):
    global elt_grid
    board_layout = []
    for i in range(9):
        board_row = ["?", (None, BIG_MARGIN)]
        for j in range(9):
            elt = pssm.Button(
                text=str(grid[i][j]),
                user_data = (i, j),
                onclickInside = setCursorPosition
            )
            elt_grid[i][j] = elt
            board_row.append((elt, "?"))
            if j in [2, 5, 8]:
                board_row.append((None, BIG_MARGIN))
            else:
                board_row.append((None, MARGIN))
        board_layout.append(board_row)
        if i in [2, 5, 8]:
            board_layout.append([BIG_MARGIN])
        else:
            board_layout.append([MARGIN])
    return board_layout


def getDigitList():
    digits = [(None,BIG_MARGIN)]
    for i in range(9):
        elt = pssm.Button(
            text=str(i+1),
            user_data=i+1,
            onclickInside=setValue,
            background_color='gray12'
        )
        digits.append((elt,"?"))
        digits.append((None,BIG_MARGIN))
    return digits


@pssm.timer
def setCursorPosition(elt,coords=None):
    """
    Sets the cursor position (add a gray background to the new cell, removes
    the gray background from the previous one).
    Also handles selecting an item for the first time or deselecting an item.

    Note : the most natural way to do it would be to run :
        elt_grid[i][j].update(newAttributes={
            'background_color':'white'
        })
        for both element.
        Yet that is *very* slow. It's much faster to run:
        elt_grid[i][j].update(
            newAttributes={'background_color':'white'},
            reprintOnTop=True
        )
    """
    global cursor_position
    global elt_grid
    if cursor_position:
        (i, j) = cursor_position
        # Reset previous selected item:
        elt_grid[i][j].update(
            newAttributes={'background_color':'white'},
            reprintOnTop=True
        )
    # Set the new selected item (unless you only want to deselect)
    if cursor_position == elt.user_data:
        cursor_position = None
    else:
        cursor_position = elt.user_data
        elt.update(
            newAttributes={'background_color':'gray12'},
            reprintOnTop=True
        )


def setValue(elt,coords=None):
    global cursor_position
    global grid
    user_input = str(elt.user_data)
    if cursor_position:
        (i, j) = cursor_position
        if base_grid[i][j] != "":
            # Can't change base_grid
            return None
        grid[i][j] = user_input
        contradiction = isWrong(grid, cursor_position, user_input)
        if contradiction:
            i2, j2 = contradiction
            # Show indicator
            elt_grid[i][j].update(
                newAttributes={'text': user_input, 'font_color': "gray4"},
                reprintOnTop=True
            )
            elt_grid[i2][j2].update(
                newAttributes={'background_color': "gray10"},
                reprintOnTop=True
            )
            # Then reset
            screen.device.wait(ERROR_INVERT_DURATION)
            screen.startBatchWriting()
            grid[i][j] = ""
            elt_grid[i][j].update(
                newAttributes={'text':""},
                reprintOnTop=True
            )
            elt_grid[i2][j2].update(
                newAttributes={'background_color':"white"},
                reprintOnTop=True
            )
        else:
            elt_grid[i][j].update(
                newAttributes={'text':user_input, 'font_color':"gray4"},
                reprintOnTop=True
            )


def main(numberOfCells=10):
    global grid
    global base_grid
    base_grid = generateSudokuGrid(numberOfCells=numberOfCells)
    grid = base_grid[:]
    board_layout = getBoardLayout(grid)
    digits = getDigitList()

    boardLayout = pssm.Layout(board_layout)
    quitBtn = pssm.Button("Quit", onclickInside=quit)
    main_layout  = [
        [BIG_MARGIN],
        ["?*6", (boardLayout, "?")],
        ["?*0.5", *digits],
        [BIG_MARGIN],
        ["?*1", (None, "?"), (quitBtn, "W/4"), (None,BIG_MARGIN)],
        [BIG_MARGIN]
    ]
    mainLayout = pssm.Layout(main_layout,screen.area)
    screen.addElt(mainLayout)


def quit(elt,coords=None):
    #Closing this FBInk session
    screen.device.closePrintHandler()
    #Closing touch file
    screen.device.closeInteractionHandler()
    os.system("killall python3")


if __name__ == "__main__":
    # Start Touch listener, as a separate thread
    screen.startListenerThread(grabInput=True)
    n = None
    while n is None:
        myPopup = pssm.PoputInput(
            titleText="Difficulty",
            titleFont=pssm.DEFAULT_FONTBOLD,
            titleFontSize="H*0.04",
            mainText="How many cells do you want to be given at start?",
            mainFontSize="H*0.02",
            inputFontSize="H*0.025"
        )
        screen.addElt(myPopup)
        user_input = myPopup.waitForResponse()
        try:
            n = int(user_input)
        except:
            n = None
    main(numberOfCells=n)
    if device == "Emulator":
        # only necessary for the emulator, and must be at the very end
        screen.device.startMainLoop()

Functions

def generateSudokuGrid(numberOfCells)
Expand source code
def generateSudokuGrid(numberOfCells):
    """
    https://stackoverflow.com/questions/45471152/how-to-create-a-sudoku-puzzle-in-python
    """
    if numberOfCells >= 81 or numberOfCells < 0:
        # Return empty grid
        return [["" for j in range(9)] for i in range(9)]
    # Create Board
    base  = 3
    side  = base*base
    # pattern for a baseline valid solution
    def pattern(r,c): return (base*(r%base)+r//base+c)%side
    # randomize rows, columns and numbers (of valid base pattern)
    from random import sample
    def shuffle(s): return sample(s,len(s))
    rBase = range(base)
    rows  = [ g*base + r for g in shuffle(rBase) for r in shuffle(rBase) ]
    cols  = [ g*base + c for g in shuffle(rBase) for c in shuffle(rBase) ]
    nums  = shuffle(range(1,base*base+1))
    # produce board using randomized baseline pattern
    board = [ [str(nums[pattern(r,c)]) for c in cols] for r in rows ]

    # THEN REMOVE 'numberOfCells' elements.
    indexesToKeep = []
    for i in range(9):
        for j in range(9):
            indexesToKeep.append((i,j))
    for n in range(numberOfCells):
        indexesToKeep.pop(random.randrange(len(indexesToKeep)))
    for (i,j) in indexesToKeep:
        board[i][j] = ""
    return board
def getBoardLayout(grid)
Expand source code
def getBoardLayout(grid):
    global elt_grid
    board_layout = []
    for i in range(9):
        board_row = ["?", (None, BIG_MARGIN)]
        for j in range(9):
            elt = pssm.Button(
                text=str(grid[i][j]),
                user_data = (i, j),
                onclickInside = setCursorPosition
            )
            elt_grid[i][j] = elt
            board_row.append((elt, "?"))
            if j in [2, 5, 8]:
                board_row.append((None, BIG_MARGIN))
            else:
                board_row.append((None, MARGIN))
        board_layout.append(board_row)
        if i in [2, 5, 8]:
            board_layout.append([BIG_MARGIN])
        else:
            board_layout.append([MARGIN])
    return board_layout
def getDigitList()
Expand source code
def getDigitList():
    digits = [(None,BIG_MARGIN)]
    for i in range(9):
        elt = pssm.Button(
            text=str(i+1),
            user_data=i+1,
            onclickInside=setValue,
            background_color='gray12'
        )
        digits.append((elt,"?"))
        digits.append((None,BIG_MARGIN))
    return digits
def isWrong(grid, pos, userInput)

Returns False if grid is valid, else (i, j) where (i, j) indicate the coords of the cell which the addition contradicts

Expand source code
def isWrong(grid, pos, userInput):
    """
    Returns False if grid is valid, else (i, j) where (i, j) indicate the coords
    of the cell which the addition contradicts
    """
    (i0, j0) = pos
    current = grid[i0][j0]
    # In the same col?
    for i in range(9):
        if grid[i][j0] == current and i!=i0:
            return (i, j0)
    # In the same line?
    for j in range(9):
        if grid[i0][j] == current and j != j0:
            return (i0, j)
    # In the same square?
    for i in range(i0 - i0%3, i0 - i0%3 + 3):
        for j in range(j0 - j0%3, j0 - j0%3 + 3):
            if grid[i][j] == current and i != i0 and j != j0:
                return (i, j)
    return False
def main(numberOfCells=10)
Expand source code
def main(numberOfCells=10):
    global grid
    global base_grid
    base_grid = generateSudokuGrid(numberOfCells=numberOfCells)
    grid = base_grid[:]
    board_layout = getBoardLayout(grid)
    digits = getDigitList()

    boardLayout = pssm.Layout(board_layout)
    quitBtn = pssm.Button("Quit", onclickInside=quit)
    main_layout  = [
        [BIG_MARGIN],
        ["?*6", (boardLayout, "?")],
        ["?*0.5", *digits],
        [BIG_MARGIN],
        ["?*1", (None, "?"), (quitBtn, "W/4"), (None,BIG_MARGIN)],
        [BIG_MARGIN]
    ]
    mainLayout = pssm.Layout(main_layout,screen.area)
    screen.addElt(mainLayout)
def quit(elt, coords=None)
Expand source code
def quit(elt,coords=None):
    #Closing this FBInk session
    screen.device.closePrintHandler()
    #Closing touch file
    screen.device.closeInteractionHandler()
    os.system("killall python3")
def setCursorPosition(elt, coords=None)

Sets the cursor position (add a gray background to the new cell, removes the gray background from the previous one). Also handles selecting an item for the first time or deselecting an item.

Note : the most natural way to do it would be to run : elt_grid[i][j].update(newAttributes={ 'background_color':'white' }) for both element. Yet that is very slow. It's much faster to run: elt_grid[i][j].update( newAttributes={'background_color':'white'}, reprintOnTop=True )

Expand source code
@pssm.timer
def setCursorPosition(elt,coords=None):
    """
    Sets the cursor position (add a gray background to the new cell, removes
    the gray background from the previous one).
    Also handles selecting an item for the first time or deselecting an item.

    Note : the most natural way to do it would be to run :
        elt_grid[i][j].update(newAttributes={
            'background_color':'white'
        })
        for both element.
        Yet that is *very* slow. It's much faster to run:
        elt_grid[i][j].update(
            newAttributes={'background_color':'white'},
            reprintOnTop=True
        )
    """
    global cursor_position
    global elt_grid
    if cursor_position:
        (i, j) = cursor_position
        # Reset previous selected item:
        elt_grid[i][j].update(
            newAttributes={'background_color':'white'},
            reprintOnTop=True
        )
    # Set the new selected item (unless you only want to deselect)
    if cursor_position == elt.user_data:
        cursor_position = None
    else:
        cursor_position = elt.user_data
        elt.update(
            newAttributes={'background_color':'gray12'},
            reprintOnTop=True
        )
def setValue(elt, coords=None)
Expand source code
def setValue(elt,coords=None):
    global cursor_position
    global grid
    user_input = str(elt.user_data)
    if cursor_position:
        (i, j) = cursor_position
        if base_grid[i][j] != "":
            # Can't change base_grid
            return None
        grid[i][j] = user_input
        contradiction = isWrong(grid, cursor_position, user_input)
        if contradiction:
            i2, j2 = contradiction
            # Show indicator
            elt_grid[i][j].update(
                newAttributes={'text': user_input, 'font_color': "gray4"},
                reprintOnTop=True
            )
            elt_grid[i2][j2].update(
                newAttributes={'background_color': "gray10"},
                reprintOnTop=True
            )
            # Then reset
            screen.device.wait(ERROR_INVERT_DURATION)
            screen.startBatchWriting()
            grid[i][j] = ""
            elt_grid[i][j].update(
                newAttributes={'text':""},
                reprintOnTop=True
            )
            elt_grid[i2][j2].update(
                newAttributes={'background_color':"white"},
                reprintOnTop=True
            )
        else:
            elt_grid[i][j].update(
                newAttributes={'text':user_input, 'font_color':"gray4"},
                reprintOnTop=True
            )