0

I'm using the code from this answer to make clickable numbered tiles with Python Turtle Graphics. For some reason my code creates tiles with the color of the second tileset (red) rather than the first (blue) when the text for the tiles is the same.

I've tried checking if somehow the different colored tiles are somehow referencing the same object, but that doesn't seem to be the case. Can anyone please tell me how to fix this?

from tileset_so import TileSet
from turtle import Screen

SIZE = 4
screen = Screen()

# Tiles
tileset1 = TileSet(background_color="blue")
tileset2 = TileSet(background_color="red")

tiles1 = []
tiles2 = []

blue_tile = tileset1.make_tile("here")
red_tile = tileset2.make_tile("here") # works fine with "there"
blue_tile.goto(100, 100)
red_tile.goto(-100, 100)

print(id(blue_tile), id(red_tile))
print(id(tileset1), id(tileset2))

screen.mainloop()
# tileset.py

from turtle import Screen, Turtle, Shape
from PIL import Image, ImageDraw, ImageFont, ImageTk

DEFAULT_FONT_FILE = "C:\Windows\Fonts\courbd.ttf"  # adjust for your system
DEFAULT_POINT_SIZE = 32
DEFAULT_OUTLINE_SIZE = 1
DEFAULT_OUTLINE_COLOR = 'black'
DEFAULT_BACKGROUND_COLOR = 'white'

class Tile(Turtle):
    def __init__(self, shape, size):
        super().__init__(shape)
        self.penup()

        self.size = size

    def tile_size(self):
        return self.size

class TileSet():

    def __init__(self, font_file=DEFAULT_FONT_FILE, point_size=DEFAULT_POINT_SIZE, background_color=DEFAULT_BACKGROUND_COLOR, outline_size=DEFAULT_OUTLINE_SIZE, outline_color=DEFAULT_OUTLINE_COLOR):
        self.font = ImageFont.truetype(font_file, point_size)
        self.image = Image.new("RGB", (point_size, point_size))
        self.draw = ImageDraw.Draw(self.image)

        self.background_color = background_color
        self.outline_size = outline_size
        self.outline_color = outline_color

    def register_image(self, string):
        width, height = self.draw.textsize(string, font=self.font)
        image = Image.new("RGB", (width + self.outline_size*2, height + self.outline_size*2), self.background_color)
        draw = ImageDraw.Draw(image)
        tile_size = (width + self.outline_size, height + self.outline_size)
        draw.rectangle([(0, 0), tile_size], outline=self.outline_color)
        draw.text((0, 0), string, font=self.font, fill="#000000")
        photo_image = ImageTk.PhotoImage(image)
        shape = Shape("image", photo_image)
        Screen()._shapes[string] = shape  # underpinning, not published API

        return tile_size

    def make_tile(self, string):
        tile_size = self.register_image(string)
        return Tile(string, tile_size)
Robin Andrews
  • 3,514
  • 11
  • 43
  • 111
  • Could you show the code where you actually *use* the tiles? Or else, how exactly are you determining that the colour is wrong? – Karl Knechtel Sep 05 '20 at 11:44
  • It's the top snippet. – Robin Andrews Sep 05 '20 at 12:08
  • I meant, the code where you *draw* the tiles. I see code that determines what they should look like, sets their position, and checks that they are distinct objects. When you register the images in the `Screen()._shapes`, I assume that's so you can *do something with them later*. What is that thing? – Karl Knechtel Sep 05 '20 at 17:08

2 Answers2

0
Screen()._shapes[string] = shape  # underpinning, not published API
                                                  ^^^^^^^^^^^^^^^^^

Then you don't get to complain when it behaves differently from what you expect, right?

The Screen() instance is singleton. Each time this line is reached, therefore, the same dictionary has this assignment made. So if the two tiles - even in different tilesets - have the same name, then the second replaces the first in this dictionary.

As to why that matters - well, we'd have to look at the same place that gave you the idea to write this line in the first place, I suppose.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
0

It's a bug in my TileSet code example.

Here's a workaround: Change make_tile() in the TileSet code to read:

def make_tile(self, string, id):
    tile_size = self.register_image(string, id)
    return Tile(string + ':' + id, tile_size)

Change these two lines in the TileSet register_image funtion:

def register_image(self, string):

    Screen()._shapes[string] = shape  # underpinning, not published API

To instead read:

def register_image(self, string, id):

    Screen()._shapes[string + ':' + id] = shape  # underpinning, not published API

And finally change your calls to make_tile() to include a unique description:

blue_tile = tileset1.make_tile("here", "blue here")
red_tile = tileset2.make_tile("here", "red here")

This should clear up the problem with turtle's _shapes dictionary.

cdlane
  • 40,441
  • 5
  • 32
  • 81