Search code examples
grid-layouthexagonal-tiles

Position and snap hex pawns to a hexagonal grid


Good morning I have a project where I must code a game that looks like this: http://www.apppicker.com/apps/1055267582/1010-hex-fit--block-puzzle-mania-archanoid-pixduel-plus-saga-free-game? And I must code the game without classes ("class"), because my school doesn't want me to :(.

Now, I have already drawn the grid perfectly, but unfortunatly I can't seem to make my pawns go on the exact hexagonal "squares"n because it's not a traditional grid. Can someone help me on that? And how can I assign an array with values to it?

And if you can't do anything with this issue can you at least guide me on something else?

(I changed my program a bit from the last version of my post to make it clearer)

Thank in advance for your time.

from tkinter import *
from random import choice #for colors

beige = (255,228,196)

def dessus(evt): #mouse on hexagonal pawn
    forme = "arrow"
    if x_pion - 50 < evt.x < x_pion + 50 and y_pion - 100 < evt.y < y_pion + 100:
        forme = "fleur"
        # print(x_pion , y_pion)
    can.config(cursor = forme)

def glisser(evt): #mouse dragging the hexagonal pawn
    global x, y
    if x == -1 :
        if x_pion - 50 < evt.x < x_pion + 50 and y_pion - 100 < evt.y < y_pion + 100:
            x, y = evt.x, evt.y
    else :
        delta_x, delta_y = evt.x - x, evt.y - y
        can.coords(pion, x_pion + delta_x, y_pion + delta_y)


def coord_case(x_pos,y_pos):
    # return hexagonal coordinates spot from the game board
    coord_x = x_pos//c
    coord_y = y_pos//c
    print(x_pos, y_pos)
    return coord_x, coord_y



def coord_image(x_pos,y_pos):
    # return the coordinates of the image
    x = c * x_pos + dc
    y = c * y_pos + dc
    return x, y


def deposer(evt): #put/release the image (hexagonal pawn) on the screen
    global x_pion, y_pion, x
    if x != -1:
        x_pion, y_pion = x_pion + evt.x - x, y_pion + evt.y - y
        x =- 1

def hexagone(origine, c): #draws the hexagones
    seg = c / 4
    x, y = origine[0], origine[1]
    # hexagone
    can.create_polygon(x,y+seg, x+2*seg,y, x+c,y+seg, x+c,y+3*seg ,x+2*seg,y+c ,x,y+3*seg, x,y+seg,
                       outline='black', width=1, fill="beige")


def ligne_d_hexagones(x, y, c, n): #draws the hexagonal lines
    i = 0
    seg = c / 4
    while i < n:
        hexagone((x + 2, y + 2), c)  # +2 :
        i += 1
        x += 4.25 * seg


def damier(c, nl, nc):
    #finally draws everything (the hexagonal game board)
    x = 2.08 # commencera la ligne
    y = 0.02
    i = 1
    for i in range(nc-1):
        ligne_d_hexagones(x * c, y * c, c, nc // 2)
        i+=1
        y = y + 0.79
        if i < 5:
            nc = nc + 2
            x = x - 0.52
        if i == 5:
            nc = nc - 2
            x = x + 0.52
        if i > 5:
            nc = nc - 2
            x = x + 0.52



c = 70  # size of the hexagones from the board
nl = 10  # number of lines
nc = 10  # number of columns (this valuer will change)
dc = c//2
fen = Tk()
fen.title("HEX1010")

can = Canvas(fen, width=1000, height=700)
can.pack(side=TOP)
damier(c, nl, nc)
can.grid()


x, y = -1, -1
f_pion = PhotoImage(file="HEX.png")
x_pion, y_pion = 600, 600
pion = can.create_image(x_pion, y_pion,image=f_pion)

x, y, num = -1, -1, -1

can.bind('<B1-Motion>',glisser)
can.bind('<ButtonRelease-1>',deposer)
can.bind('<Motion>',dessus)

fen.mainloop()

Solution

  • I eventually found a bit of time to look at your code...

    1- you need to read a little bit about canvas items and how they can be accessed, identified, and made to reveal their state and position, that will help you decide how to structure your app. At this moment, you were mostly trying to account for each item, instead of using the canvas methods.

    2- I added a dictionary to hold a mapping of the grid hexagons to their center positions; the key is the canvas item number of each hexagon.

    3- I identify the hexagon the mouse is over using canvas.find_overlapping, and use this return value to look up into the centers dictionary of tile centers, to snap the 'pion' to the correct position.

    Mostly it does what you wanted, but it is probably a bit brittle: for instance, 2 pions can be placed on top of each other. It may break on some special cases. My suggestion is to rewrite the entire code using a class for the Cases, a class for the Damier, a class for the Pion, and a class for the Pions, and equip them with the methods you need to let them behave the way you want.

    from tkinter import *
    
    
    beige = (255,228,196)
    
    
    def selectioner(evt):
        global x, y, num
        x, y = evt.x, evt.y
        x0, y0, x1, y1 = x - 3, y - 3, x + 3, y + 3
        for item in can.find_overlapping(x0, y0, x1, y1):
            if item in pion:
                num = item
                x, y = can.coords(num)
                break
    
    
    def glisser(evt): #mouse dragging the hexagonal pawn
        global x, y, num
        if num == -1 :
            pass
        else :
            delta_x, delta_y = evt.x - x, evt.y - y
            can.coords(num, x + delta_x, y + delta_y)
    
    
    def deposer(evt): #put/release the image (hexagonal pawn) on the screen
        global num
    
        if num == -1:
            return
    
        x0, y0, x1, y1 = evt.x - 3, evt.y - 3, evt.x + 3, evt.y + 3
        snap = None
        for item in can.find_overlapping(x0, y0, x1, y1):
            try:
                snap = centers[item]
                break
            except KeyError:
                pass        
        if num != -1 and snap is not None:
            xs, ys = snap
            can.coords(num, xs, ys)
        num = -1
    
    
    def hexagone(origine, c): #draws the hexagones
        seg = c / 4
        x, y = origine[0], origine[1]
        center = center_x, center_y = (x + x+c) / 2, (y+seg + y+3*seg) / 2
        p = can.create_polygon(x, y+seg, x+2*seg, y, x+c, y+seg, x+c, y+3*seg , x+2*seg, y+c , x,y+3*seg, x,y+seg,
                           outline='black', width=1, fill="beige")
    #     can.create_oval(center_x-2, center_y-2, center_x+2, center_y+2)
        centers[p] = center
    
    
    def ligne_d_hexagones(x, y, c, n): #draws the hexagonal lines
        i = 0
        seg = c / 4
        while i < n:
            hexagone((x + 2, y + 2), c)  # +2 : 
            i += 1
            x += 4.25 * seg
    
    
    def damier(c, nl, nc):
        # finally draws everything (the hexagonal game board)
        x = 2.08 # commencera la ligne
        y = 0.02
        i = 1
        for i in range(nc-1):
            ligne_d_hexagones(x * c, y * c, c, nc // 2)
            i+=1
            y = y + 0.79
            if i < 5:
                nc = nc + 2
                x = x - 0.52
            if i == 5:
                nc = nc - 2
                x = x + 0.52
            if i > 5:
                nc = nc - 2
                x = x + 0.52
    
    
    centers = {}
    
    c = 70  # size of the hexagones from the board
    nl = 10  # number of lines 
    nc = 10  # number of columns (this valuer will change)
    dc = c//2
    fen = Tk()
    fen.title("HEX1010")
    
    can = Canvas(fen, width=1000, height=700)
    can.pack(side=TOP)
    damier(c, nl, nc)
    can.grid()
    
    pion, x_pion, y_pion = [], [], []
    f_pion = PhotoImage(file="HEX.png")
    for i in range(3):
        idx, idy = 300*(i+1), 300
        x_pion.append(idx)
        y_pion.append(idy)
        pion.append(can.create_image(idx,idy,image=f_pion))
    
    x, y, num = -1, -1, -1
    
    can.bind('<Button-1>', selectioner)
    can.bind('<B1-Motion>', glisser)
    can.bind('<ButtonRelease-1>', deposer)
    
    fen.mainloop()