7

I am working on a python checkers game for college. I have the board drawn, using tk, but I can't seem to implement a movement function for the pieces. If anyone see any errors in my code, or can offer help, I would appreciate. Here is the complete source. Thanks in advance.

I know that this draws the checker pieces. I don't know how to redraw the pieces, without deleting the other pieces. I have looked online at the move function, and tried simple test that worked, yet I have not been able to use it in my code.

I do know of recursion, however, I need the basic function to work, i.e. actually moving a piece on the screen, before I implement any more functions.

lst2 = []

#counter variable
i=0

#board variable is what stores the X/O/- values.
# It's a 2D list. We iterate over it, looking to see
# if there is a value that is X or O. If so, we draw
# text to the screen in the appropriate spot (based on
# i and j.
while i < len(board):
  j=0
  while j < len(board[i]):

    if board[i][j] == 2:
      lst2.append(canvas.create_oval((i+1)*width + width/2 + 15,
        (j+1)*height + height/2 +15,(i+1)*width + width/2 - 15,
        (j+1)*width + width/2 - 15, fill="Red",outline='Black'))
    elif board[i][j] == 4:
      lst2.append(canvas.create_oval((i+1)*width + width/2 + 15,
        (j+1)*height + height/2 +15,(i+1)*width + width/2 - 15,
        (j+1)*width + width/2 - 15, fill="Red",outline='Black'))
    elif board[i][j] == 1:
      lst2.append(canvas.create_oval((i+1)*width + width/2 + 15,
        (j+1)*height + height/2 +15,(i+1)*width + width/2 - 15,
        (j+1)*width + width/2 - 15, fill="Black",outline='Black'))
    elif board[i][j] == 3:
      lst2.append(canvas.create_oval((i+1)*width + width/2 + 15,
        (j+1)*height + height/2 +15,(i+1)*width + width/2 - 15,
        (j+1)*width + width/2 - 15, fill="Black",outline='Black'))

    j+=1

  i+=1
smci
  • 32,567
  • 20
  • 113
  • 146
  • 3
    This is not a question. You don't just dump your whole program and ask for help. Can you give us the problem you're having? If anything, please narrow your code segment down to the part relevant to moving a piece, because I don't have time to sift through it all. – machine yearning Jul 19 '11 at 01:06
  • 1
    @Tim Post, at least give the guy a day or two to edit the question to fix it? – smci Jul 19 '11 at 20:49
  • @Tim Post, I object to the sudden closing without giving him time to respond, I [raised this for discussion on MSO](http://meta.stackexchange.com/questions/99048/how-much-chance-warning-should-newbies-be-given-before-closing-as-not-a-real-que). – smci Jul 19 '11 at 22:44
  • 5
    Questions can be edited after closing. If it gets fixed, flag it to be reopened. – Bill the Lizard Jul 20 '11 at 03:30
  • I posted the whole due to the nature of my not understanding on how to move the pieces on the display. I can move the pieces in the board[] function, I just can't display it. I have figured out how to display the pieces, I just can't move it. Thanks for the help. – William Armstrong Jul 20 '11 at 16:28
  • @Tim Post, I have edited the question, how do I flag it, or get it flagged to be reopened? Thanks. – William Armstrong Jul 20 '11 at 16:47
  • @William, consider using enum to self-document the values used in board[][] (`RED_PIECE=1,RED_KING=2,BLACK_PIECE=3,BLACK_KING=4` or whatever), but a possible bug is both the 1 and 3 cases draw `...,fill="Black",outline='Black'`. Mods, will you please reopen this thing? – smci Jul 20 '11 at 22:28
  • I updated my answer yet again, please edit your question with clarifications, and hopefully the causes people to vote to reopen. – smci Jul 20 '11 at 22:56
  • @William Armstrong - Thank you for editing. I've re-opened the question. – Tim Post Jul 20 '11 at 23:32
  • @William Armstrong: I downvoted the question because it's not at all clear what is being asked. You ask about a movement function but your code only shows creating objects. Very confusing. Are you struggling with redrawing the whole board, or do you want a way to interactively move an oval on the screen? – Bryan Oakley Jul 22 '11 at 16:25
  • @BryanOakley: that's because you're looking at the 7th revision of the question. The earlier revisions contained more code, which is still referenced in the question. – smci Feb 10 '17 at 00:21
  • 1
    @smci: I don't see the point you're making. That comment was five and a half years ago, and the only edit after I made that comment was the edit you just made. I also removed my downvote after one of the edits, though I don't know which. – Bryan Oakley Feb 10 '17 at 00:35

2 Answers2

34

You can move an item on a canvas using the coords and/or move methods to change the coordinates from what they are to what you want them to be.

Here's a simple example showing how to create and move an item on a canvas:

import tkinter as tk     # python 3
# import Tkinter as tk   # python 2

class Example(tk.Frame):
    """Illustrate how to drag items on a Tkinter canvas"""

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)

        # create a canvas
        self.canvas = tk.Canvas(width=400, height=400, background="bisque")
        self.canvas.pack(fill="both", expand=True)

        # this data is used to keep track of an
        # item being dragged
        self._drag_data = {"x": 0, "y": 0, "item": None}

        # create a couple of movable objects
        self.create_token(100, 100, "white")
        self.create_token(200, 100, "black")

        # add bindings for clicking, dragging and releasing over
        # any object with the "token" tag
        self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
        self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
        self.canvas.tag_bind("token", "<B1-Motion>", self.drag)

    def create_token(self, x, y, color):
        """Create a token at the given coordinate in the given color"""
        self.canvas.create_oval(
            x - 25,
            y - 25,
            x + 25,
            y + 25,
            outline=color,
            fill=color,
            tags=("token",),
        )

    def drag_start(self, event):
        """Begining drag of an object"""
        # record the item and its location
        self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
        self._drag_data["x"] = event.x
        self._drag_data["y"] = event.y

    def drag_stop(self, event):
        """End drag of an object"""
        # reset the drag information
        self._drag_data["item"] = None
        self._drag_data["x"] = 0
        self._drag_data["y"] = 0

    def drag(self, event):
        """Handle dragging of an object"""
        # compute how much the mouse has moved
        delta_x = event.x - self._drag_data["x"]
        delta_y = event.y - self._drag_data["y"]
        # move the object the appropriate amount
        self.canvas.move(self._drag_data["item"], delta_x, delta_y)
        # record the new position
        self._drag_data["x"] = event.x
        self._drag_data["y"] = event.y

if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • 1
    I know this is in the past, but in the off chance you're still using this style, I want to mention that I don't think the "On..." function names are good. First, they're capped and so most people would immediately think class, you're breaking traditions that if you ask me are helpful for more quickly understanding. I can see why you want to do it, but the biggest problem with it is you're repeating yourself. the tag_bind function tells you all that information, it'd be better to instead explain what the function is doing, what it encapsulates, not what calls it, given that can change. – GRAYgoose124 Jan 18 '17 at 20:24
  • @GRAYgoose124: you are absolutely correct about the use of uppercase for function names. Thanks for pointing it out. I don't know why I chose that naming scheme. I updated the answer to use all lowercase. I don't quite understand the other point you were trying to make. I don't see where you think I'm repeating myself. All of the code is necessary for a fully functioning example, and I felt a fully functioning example was best in this case. – Bryan Oakley Jan 18 '17 at 20:45
  • I was nearly hitting the comment limit so I'm sorry if I wasn't being clear. Let me elaborate. `...tag_bind("token", ""...` conveys all the information about how it's being activated that the name `on_token_press` conveys. Instead, I think it's more prudent to name the function based on what it's doing, not how it's being called. In this case, I think `drag_object` would be more enlightening towards what is happening. Read your current `tag_bind` declaration. Then compare it to this: `...tag_bind("token", "", self.drag_object)...` – GRAYgoose124 Jan 19 '17 at 00:02
  • In my case, it's immediately obvious what action that binding is linking to what buttons. In your case, it's saying which binding is being linked, and then the function doesn't convey information about what it's doing. If you ever change the binding, then you need to change the function name to match the `onX` style. That's what I mean about repeating yourself, I don't feel the code is as capable of self-documentation this way and as it is now the function name doesn't depend on the function's internals, but instead how it's being called, an external factor which muddles the separation. – GRAYgoose124 Jan 19 '17 at 00:06
  • I referenced your answer in a new question: https://stackoverflow.com/questions/59463144/python-tkinter-drag-object-embed-a-class-within-a-function – WinEunuuchs2Unix Dec 24 '19 at 02:21
2

6TH EDIT: Here are two solutions for you:

  1. (as Bryan suggests) either remember the old location of the moved piece, undraw it there (=> draw it in background color), redraw it at new location
  2. the simpler: clear and redraw entire board

5TH EDIT: Ok thanks for stripping the code down.

Explain exactly what is the problem with your board-drawing code? 'Moved piece is not deleted from old location'? 'All pieces are drawn at wrong coordinates or colors'? ...? It's not acceptable to just keep dumping code and saying "This code doesn't work".

"I don't know how to redraw the pieces, without deleting the other pieces." I think that's your problem right there. If you declare and call redrawBoard(), it should redraw ALL the pieces(!), not just the moved piece. Agree? i.e. you must iterate over all of board[][] and call drawPiece() on every piece. But your code appears to do that already?

Let me suggest you how to clean up your existing board-drawing code, and in that process you will almost surely find your bug. Obviously you need to clear and redraw the screen every time there is a move (or promotion), do you actually do that? Declare a fn redrawBoard() for that. If you do not do a clear, then after a move the piece will be displayed in its old AND new locations, which would be wrong obviously? (The comment about Frame rate is how often canvas will be updated each second. makes me wonder when you redraw, you do not need to redraw 10 times a second, unless you also have a clock or other changing data. But, hey, that also works.)

First, strongly suggest you use an enum to self-document the values used in board[][]

class Checkers():
    EMPTY=0
    RED_PIECE=1
    RED_KING=2
    BLACK_PIECE=3
    BLACK_KING=4

Next, you can greatly clean up the board-drawing code. Since all 4 piece-drawing cases call a common-case, make it a fn, and make that fn uncluttered:

def drawPiece(i,j,fillColor,outlineColor):
    """Draw single piece on screen."""
    x = (i+1)*width + width/2
    y = (j+1)*height + height/2
    lst2.append(canvas.create_oval(x+15,y+15,x-15,y-15,fill=fillColor,outline=outlineColor))

Now, the board-drawing code that calls these strictly really only has two cases: (2,4) or (1,3) assuming you got the enum right:

and by the way, never use a while-loop where a more legible for-loop would do:

for i in range(len(board)):
    for j in range(len(board[i])):
        if board[i][j] in (RED_PIECE,RED_KING):
            drawPiece(i,j,'Red','Black')
        elif board[i][j] in (BLACK_PIECE,BLACK_KING):
            drawPiece(i,j,'Black','Black')

Is that decomposition not infinitely easier to read and debug? It's self-documenting. Now your bug should practically leap out at you.

(By the way, you're currently drawing kings the exact same as pieces, but I guess you'll fix that later.)


4TH EDIT: You had us looking at the wrong fns, grr... you say your bug is actually in the board-drawing code. Would you please correct the title which still says "Implement a movement function"?


ORIGINAL REPLY: What machine yearning said, this is not a question - not yet: Tell us what you currently try, and why it doesn't work. Also, remove all unrelated code.

Looks like you're having difficulties with function moveTo(i,j) - but what exactly? (The globals secondPass, secondPosition signal you might be having trouble... do you know recursion? If not, no worries.)

Also, as a stylistic thing, and to make your life easy, this implementation is not OO, the globals scream bad decomposition. Try rewriting as a class Checkers, make board etc. a member, write an init() method. I would rename the function grid(x,y) to initialize(nrows,ncols).

(And cough, cough! Signs you adapted this from someone else...)

#Frame rate is how often canvas will be updated
# each second. For Tic Tac Toe, 10 should be plenty.
FRAME_RATE = 10
Community
  • 1
  • 1
smci
  • 32,567
  • 20
  • 113
  • 146