Recently I am learning to program in Python and in order to practice I tried to program a little game which consists in a board with 9 cells. In this board each player draw an 'X' or 'O' with left or right mouse button. The next code is a simplification with only 3-cell board.

Code 1: View

from tkinter import *
import numpy as np
from line3 import *

juego = Tablero()

root = Tk()

def leftClick1(event):
    canvas1.create_line(0, 50, 50, 0)

def leftClick2(event):
    canvas2.create_line(0, 50, 50, 0)

def leftClick3(event):
    canvas3.create_line(0, 50, 50, 0)

def rightClick1(event):
    create_circle(24, 24, 20, canvas1)

def rightClick2(event):
    create_circle(24, 24, 20, canvas2)

def rightClick3(event):
    create_circle(24, 24, 20, canvas3)

topFrame = Frame(root)
middleFrame = Frame(root)
bottomFrame = Frame(root)

canvas1 = Canvas(topFrame, width=50, height=50)
canvas2 = Canvas(topFrame, width=50, height=50)
canvas3 = Canvas(topFrame, width=50, height=50)

canvas1.bind("<Button-1>", leftClick1)
canvas2.bind("<Button-1>", leftClick2)
canvas3.bind("<Button-1>", leftClick3)

canvas1.bind("<Button-3>", rightClick1)
canvas2.bind("<Button-3>", rightClick2)
canvas3.bind("<Button-3>", rightClick3)




Code 2: Classes and utils

import numpy as np

class Tablero:
    def __init__(self):
        self.tablero = np.zeros((3,3))

    def ponerFicha(self, x, y, tipoFicha):
        if (self.tablero[x][y] == 0):
            self.tablero[x][y] = tipoFicha

# Taken from StackOverFlow question
def create_circle(x, y, r, canvasName): #center coordinates, radius
    x0 = x - r
    y0 = y - r
    x1 = x + r
    y1 = y + r
    return canvasName.create_oval(x0, y0, x1, y1)

I feel I am repeating a lot of code when I programmed two events for each cell.

  • Is it possible to have one listener for all the cells?
  • How could I avoid repeat so much code?
  • \$\begingroup\$ Eva, I have an answer for you if you would ask this question on Code Review. \$\endgroup\$ – Mike - SMT Jul 26 '19 at 13:05

Is it possible to have one listener for all the cells?

You still have to bind to each canvas but we can bind to one function that can handle all your conditions. The best way to do this is to put your canvas widgets into a list and then reference them by index.

How could I avoid repeat so much code?

Its good to try and reduce your code. Try to keep it DRY (Don't Repeat Yourself) is something to strive for and in this case the bast way to do this is to build a single function that can handle all your conditions based on the event being passed and an index value.

Another helpful way to stay DRY is to know when to use a for loop to managed creations of widgets or variables needed. Here I have changed your code to use a for loop to build a list with your canvas widgets and apply the binds and pack all at once.

I have also added a check to see if something has already been written to your canvas so it cannot be overwritten.

By utilizing a list to and building our functionality around that list we can reduce the amount of code we need and at the same time make the boxes infantry scalable.

I have added a variable called np_tuple. All you have to do is change the numbers to change the size of your game.

I have combined all your code into one .py file but you can easily move your Tablero class back out.

from tkinter import *
import numpy as np

class Tablero:
    def __init__(self, np_tuple):
        self.tablero = np.zeros(np_tuple)

    def poner_ficha(self, x, y, tipo_ficha):
        if self.tablero[x][y] == 0:
            self.tablero[x][y] = tipo_ficha
            # print(self.tablero)

root = Tk()
clist = []
np_tuple = (3, 3)
juego = Tablero(np_tuple)

def create_circle(x, y, r, canvas_name):
    canvas_name.create_oval(x - r, y - r, x + r, y + r)

def click(event, xdex, ydex):
    if clist[xdex][ydex][1] == '':
        if event.num == 1:
            clist[xdex][ydex][1] = 'X'
            clist[xdex][ydex][0].create_line(0, 0, 50, 50)
            clist[xdex][ydex][0].create_line(0, 50, 50, 0)
            juego.poner_ficha(xdex, ydex, 1)

            clist[xdex][ydex][1] = 'O'
            create_circle(24, 24, 20, clist[xdex][ydex][0])
            juego.poner_ficha(xdex, ydex,  2)

box_frame = Frame(root)

for x in range(np_tuple[0]):
    prep_list = []
    for y in range(np_tuple[1]):
        prep_list.append([Canvas(box_frame, width=50, height=50), ''])
        prep_list[y][0].create_line(50, 50, 0, 50)
        prep_list[y][0].create_line(50, 50, 50, 0)
        prep_list[y][0].bind("<Button-1>", lambda e, xdex=x, ydex=y: click(e, xdex, ydex))
        prep_list[y][0].bind("<Button-3>", lambda e, xdex=x, ydex=y: click(e, xdex, ydex))
        prep_list[y][0].grid(row=x, column=y)

| improve this answer | |
  • \$\begingroup\$ @EvaMGG Sorry I had to make a quick update to the code. Please take a look now. \$\endgroup\$ – Mike - SMT Jul 26 '19 at 19:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.