Part 4: Board Game Script

Wrapping up the Sudoku tutorial by putting the finishing touches of the script.

Parsing arguments

We’d like to allow the user to pass in the desired board that he or she wants to solve. We’ll use Python’s argument parser here for that.

Remember, in Part 0 we defined a few global constant variables. One in particular was BOARDS, which was set to a list of strings. This list will define the choices available to the user.

Let’s setup our argument parser:

1
2
3
4
5
6
7

defparse_arguments():""" Parses arguments of the form: sudoku.py <board name> Where `board name` must be in the `BOARD` list """arg_parser=argparse.ArgumentParser()

Here we instantiate ArgumentParser from the argparse library. Now let’s add an argument:

1
2
3
4
5
6
7
8
9
10
11
12

defparse_arguments():""" Parses arguments of the form: sudoku.py <board name> Where `board name` must be in the `BOARD` list """arg_parser=argparse.ArgumentParser()arg_parser.add_argument("--board",help="Desired board name",type=str,choices=BOARDS,required=True)

The argument will have the flag --board, so when the user runs the script, it will look like python sudoku.py --board BOARD_NAME. The BOARD_NAME will be limited to the available choices defined in BOARDS.

We’re also telling the argument parser to expect a string input, and set it to be required (the argument parser will take care of erroring out for us in case the user does not supply a BOARD_NAME or the --board flag.

The help attribute allows the user to run python sudoku.py -h or python sudoku.py --help, so the user will understand what the purpose of the --board flag is.

Lastly, we parse out the values for the arguments. The vars() built-in function creates a dictionary for us, where the key is the argument flag name (without the two leading dashes, board), and the value is the user input. And we return the value of the board:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

defparse_arguments():""" Parses arguments of the form: sudoku.py <board name> Where `board name` must be in the `BOARD` list """arg_parser=argparse.ArgumentParser()arg_parser.add_argument("--board",help="Desired board name",type=str,choices=BOARDS,required=True)# Creates a dictionary of keys = argument flag, and value = argumentargs=arg_parser.parse_args()returnargs['board']

Now in putting it all together, we’ll define the logic for when the user is running this script.

Main function

Like in earlier tutorials, we want to add the boilerplate code, if __name__ == '__main__' to ensure this executes via the command line.

1

if__name__=='__main__':

The first thing we want our script to do when the user calls python sudoku.py ... is to parse the arguments and return the board name for us:

1
2

if__name__=='__main__':board_name=parse_arguments()

Next, with opening the Sudoku board file (the board that the user passed in as an argument), we initialize a game and call the start() method:

Next, we’ll create a “root” widget, which is actually passed in on the next line as the the parent widget when instantiating SudokuUI (no need to assign the newly instantiated SudokuUI class since we will not be calling any methods directly; all methods will be called via the __initUI method).

importargparsefromTkinterimportTk,Canvas,Frame,Button,BOTH,TOP,BOTTOMBOARDS=['debug','n00b','l33t','error']# Available sudoku boardsMARGIN=20# Pixels around the boardSIDE=50# Width of every board cell.WIDTH=HEIGHT=MARGIN*2+SIDE*9# Width and height of the whole boardclassSudokuError(Exception):""" An application specific error. """passdefparse_arguments():""" Parses arguments of the form: sudoku.py <board name> Where `board name` must be in the `BOARD` list """arg_parser=argparse.ArgumentParser()arg_parser.add_argument("--board",help="Desired board name",type=str,choices=BOARDS,required=True)# Creates a dictionary of keys = argument flag, and value = argumentargs=vars(arg_parser.parse_args())returnargs['board']classSudokuUI(Frame):""" The Tkinter UI, responsible for drawing the board and accepting user input. """def__init__(self,parent,game):self.game=gameFrame.__init__(self,parent)self.parent=parentself.row,self.col=-1,-1self.__initUI()def__initUI(self):self.parent.title("Sudoku")self.pack(fill=BOTH)self.canvas=Canvas(self,width=WIDTH,height=HEIGHT)self.canvas.pack(fill=BOTH,side=TOP)clear_button=Button(self,text="Clear answers",command=self.__clear_answers)clear_button.pack(fill=BOTH,side=BOTTOM)self.__draw_grid()self.__draw_puzzle()self.canvas.bind("<Button-1>",self.__cell_clicked)self.canvas.bind("<Key>",self.__key_pressed)def__draw_grid(self):""" Draws grid divided with blue lines into 3x3 squares """foriinxrange(10):color="blue"ifi%3==0else"gray"x0=MARGIN+i*SIDEy0=MARGINx1=MARGIN+i*SIDEy1=HEIGHT-MARGINself.canvas.create_line(x0,y0,x1,y1,fill=color)x0=MARGINy0=MARGIN+i*SIDEx1=WIDTH-MARGINy1=MARGIN+i*SIDEself.canvas.create_line(x0,y0,x1,y1,fill=color)def__draw_puzzle(self):self.canvas.delete("numbers")foriinxrange(9):forjinxrange(9):answer=self.game.puzzle[i][j]ifanswer!=0:x=MARGIN+j*SIDE+SIDE/2y=MARGIN+i*SIDE+SIDE/2original=self.game.start_puzzle[i][j]color="black"ifanswer==originalelse"sea green"self.canvas.create_text(x,y,text=answer,tags="numbers",fill=color)def__draw_cursor(self):self.canvas.delete("cursor")ifself.row>=0andself.col>=0:x0=MARGIN+self.col*SIDE+1y0=MARGIN+self.row*SIDE+1x1=MARGIN+(self.col+1)*SIDE-1y1=MARGIN+(self.row+1)*SIDE-1self.canvas.create_rectangle(x0,y0,x1,y1,outline="red",tags="cursor")def__draw_victory(self):# create a oval (which will be a circle)x0=y0=MARGIN+SIDE*2x1=y1=MARGIN+SIDE*7self.canvas.create_oval(x0,y0,x1,y1,tags="victory",fill="dark orange",outline="orange")# create textx=y=MARGIN+4*SIDE+SIDE/2self.canvas.create_text(x,y,text="You win!",tags="victory",fill="white",font=("Arial",32))def__cell_clicked(self,event):ifself.game.game_over:returnx,y=event.x,event.yif(MARGIN<x<WIDTH-MARGINandMARGIN<y<HEIGHT-MARGIN):self.canvas.focus_set()# get row and col numbers from x,y coordinatesrow,col=(y-MARGIN)/SIDE,(x-MARGIN)/SIDE# if cell was selected already - deselect itif(row,col)==(self.row,self.col):self.row,self.col=-1,-1elifself.game.puzzle[row][col]==0:self.row,self.col=row,colelse:self.row,self.col=-1,-1self.__draw_cursor()def__key_pressed(self,event):ifself.game.game_over:returnifself.row>=0andself.col>=0andevent.charin"1234567890":self.game.puzzle[self.row][self.col]=int(event.char)self.col,self.row=-1,-1self.__draw_puzzle()self.__draw_cursor()ifself.game.check_win():self.__draw_victory()def__clear_answers(self):self.game.start()self.canvas.delete("victory")self.__draw_puzzle()classSudokuBoard(object):""" Sudoku Board representation """def__init__(self,board_file):self.board=self.__create_board(board_file)def__create_board(self,board_file):board=[]forlineinboard_file:line=line.strip()iflen(line)!=9:raiseSudokuError("Each line in the sudoku puzzle must be 9 chars long.")board.append([])forcinline:ifnotc.isdigit():raiseSudokuError("Valid characters for a sudoku puzzle must be in 0-9")board[-1].append(int(c))iflen(board)!=9:raiseSudokuError("Each sudoku puzzle must be 9 lines long")returnboardclassSudokuGame(object):""" A Sudoku game, in charge of storing the state of the board and checking whether the puzzle is completed. """def__init__(self,board_file):self.board_file=board_fileself.start_puzzle=SudokuBoard(board_file).boarddefstart(self):self.game_over=Falseself.puzzle=[]foriinxrange(9):self.puzzle.append([])forjinxrange(9):self.puzzle[i].append(self.start_puzzle[i][j])defcheck_win(self):forrowinxrange(9):ifnotself.__check_row(row):returnFalseforcolumninxrange(9):ifnotself.__check_column(column):returnFalseforrowinxrange(3):forcolumninxrange(3):ifnotself.__check_square(row,column):returnFalseself.game_over=TruereturnTruedef__check_block(self,block):returnset(block)==set(range(1,10))def__check_row(self,row):returnself.__check_block(self.puzzle[row])def__check_column(self,column):returnself.__check_block([self.puzzle[row][column]forrowinxrange(9)])def__check_square(self,row,column):returnself.__check_block([self.puzzle[r][c]forrinxrange(row*3,(row+1)*3)forcinxrange(column*3,(column+1)*3)])if__name__=='__main__':board_name=parse_arguments()withopen('%s.sudoku'%board_name,'r')asboards_file:game=SudokuGame(boards_file)game.start()root=Tk()SudokuUI(root,game)root.geometry("%dx%d"%(WIDTH,HEIGHT+40))root.mainloop()

Sudoku boards

Now let’s make a the Sudoku boards that the user can pass in. Save all of these files within the same directory as your sudoku.py file with the extension .sudoku: