Skip to content

Finished User Interface Setup section in Part 1: Sudoku Board Setup #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 160 additions & 2 deletions website/_posts/GUI/2013-05-06-Part-1-Sudoku-Board-Setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def parse_arguments():

You can read more about Python's argparse module by working through this [howto](http://docs.python.org/2/howto/argparse.html). Basically, `argparse` allows us to define the usage of arguments for the command line, as well as setting up how many arguments to expect (`add_argument`), the flags for the arguments (`--level`), defaults (`default=-1`), etc, as well as returning the exact parsed argument we want, `args['level']` and `args['board']`.

You'll notice in the second argument, `--board`, callese the `check_negative` function. This is just to check in case a user passes a negative number for a board number (only positive numbers are allowed):
You'll notice in the second argument, `--board`, calls the `check_negative` function. This is just to check in case a user passes a negative number for a board number (only positive numbers are allowed):

```python
def check_negative(value):
Expand Down Expand Up @@ -116,4 +116,162 @@ class SudokuUI(Frame):
self.__initUI()
```

We inherit from Tkinter's `Frame` class, and initiallize the board
We inherit from Tkinter's `Frame` class, and initiallize the board.


```python
def __initUI(self):
self.parent.title("Sudoku")
self.pack(fill=BOTH, expand=1)
self.canvas = Canvas(
self, width=WIDTH, height=HEIGHT,
highlightthickness=0
)
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)
```

We give our GUI the title Sudoku.

The packer allows you to specify the relative positioning of widgets within their container. To make our lives easier it takes a qualitative relationship specification and determines the exact placement coordinates for us. A widget will appear only after its geometry is specified. In this case, when the packer’s `pack()` method is applied to it. We use the packer options fill, expand and side. You can learn more about the packer options [here](http://effbot.org/tkinterbook/pack.htm).

We first create the `Canvas` widget, a rectangular area intended for drawing pictures or other complex layouts. On it we can place graphics, text, widgets, or frames. We then create a `Button` widget with the text Clear Answers. When it is clicked, `clear_answers()` is called. We then call `draw_grid()` and `draw_puzzle()`.

The last two lines use the concepts of bindings and events. We specify an event like `"<Button-1>"` that always triggers a certain function.`<Button-1>` corresponds to clicking on the mouse. Functions called in this way are commonly known as callbacks. You can learn more about bindings and events by reading the [documentation](http://docs.python.org/2/library/tkinter.html#bindings-and-events).

```python
def __draw_grid(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This first line is missing four spaces, like other code snippets in this file (the first and last one are good).

"""
Draws grid divided with blue lines into squares 3x3
"""
for i in xrange(10):
color = "blue" if i % 3 == 0 else "gray"

x0 = MARGIN + i * SIDE
y0 = MARGIN
x1 = MARGIN + i * SIDE
y1 = HEIGHT - MARGIN
self.canvas.create_line(x0, y0, x1, y1, fill=color)

x0 = MARGIN
y0 = MARGIN + i * SIDE
x1 = WIDTH - MARGIN
y1 = MARGIN + i * SIDE
self.canvas.create_line(x0, y0, x1, y1, fill=color)
```
We draw the Sudoku grid. The method `create_line(x0, y0, x1, y1, ..., xn, yn, option, ...)` creates a line object that goes through a series of points on the Canvas. You can read more about the different Canvas objects [here](http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/canvas.html)

```python
def __draw_puzzle(self):
self.canvas.delete("numbers")
for i in xrange(9):
for j in xrange(9):
answer = self.game.answer[i][j]
if answer != 0:
x = MARGIN + j * SIDE + SIDE / 2
y = MARGIN + i * SIDE + SIDE / 2
original = self.game.puzzle[i][j]
color = "black" if answer == original else "slate gray"
self.canvas.create_text(
x, y, text=answer, tags="numbers", fill=color
)
```

We first delete all existing numbers on the board. We then iterate through all the cells in the board and find the ones whose value in the answer list is nonzero. You will find out later that the board files have zeros in the cells that are empty when the game starts. That is why the cell with nonzero values `black` text while the remaining cells have `slate gray` text. A `tag` is a string you can associate with objects on the `Canvas`. `Tags` are useful because they allow you to perform operations on all the objects with the same tag, such as changing their color or deleting them.

`draw_puzzle()` is called when the board is initialized, when a key is pressed and when the user clears their answers.

```python
def __draw_cursor(self):
self.canvas.delete("cursor")
if self.row >= 0 and self.col >= 0:
x0 = MARGIN + self.col * SIDE + 1
y0 = MARGIN + self.row * SIDE + 1
x1 = MARGIN + (self.col + 1) * SIDE - 1
y1 = MARGIN + (self.row + 1) * SIDE - 1
self.canvas.create_rectangle(
x0, y0, x1, y1,
outline="red", tags="cursor"
)
```

We highlight the curently selected box in red.

```python
def __draw_victory(self):
# create oval
x0 = y0 = MARGIN + SIDE * 2
x1 = y1 = MARGIN + SIDE * 7
self.canvas.create_oval(
x0, y0, x1, y1,
tags="victory", fill="dark orange", outline="orange"
)
# create text
x = y = MARGIN + 4 * SIDE + SIDE / 2
self.canvas.create_text(
x, y,
text="You win!", tags="victory",
fill="white", font=("Arial", 32)
)
```

When the user solves the puzzle an orange oval appears on the puzzle with the text "You win!".

```python
def __cell_clicked(self, event):
if self.game.game_over:
return
x, y = event.x, event.y
if (MARGIN < x < WIDTH - MARGIN and
MARGIN < y < HEIGHT - MARGIN):
self.canvas.focus_set()

# get row and col numbers from x,y coordinates
row, col = (y - MARGIN) / SIDE, (x - MARGIN) / SIDE

# if cell was selected already - deselect it
if (row, col) == (self.row, self.col):
self.row, self.col = -1, -1
elif self.game.puzzle[row][col] == 0:
self.row, self.col = row, col
else:
self.row, self.col = -1, -1

self.__draw_cursor()
```

We identify which cell the user clicks on and select the cell if it was not already selected. Otherwise we unselect the cell.

```python
def __key_pressed(self, event):
if self.game.game_over:
return
if self.row >= 0 and self.col >= 0 and event.char in "1234567890":
self.game.answer[self.row][self.col] = int(event.char)
self.col, self.row = -1, -1
self.__draw_puzzle()
self.__draw_cursor()
if self.game.check_win():
self.__draw_victory()
```

We set the cell value equal to what the user inputs only when they type an integer 1-9. We then check to see if the user has solved the puzzle. If so, we call `draw_victory()`.

```python
def __clear_answers(self):
self.game.set_answer_to_puzzle()
self.canvas.delete("victory")
self.__draw_puzzle()
```

We call `set_answer_to_puzzle()` which creates a two dimensional array of the correct answers for all the cells in the board. We then deletes all objects on the canvas with the tag victory. In our case it's just the happy orange oval with the text "You win!". We then draw the puzzle.