Python Черепаха записывает значение в контейнере - PullRequest
5 голосов
/ 02 августа 2020

Я хочу иметь возможность создать несколько черепах, которые отображают значения путем подкласса turtle.Turtle.

Эти черепахи должны отображать свое значение в виде текста с центром в их собственной форме. Я также хочу иметь возможность точно позиционировать черепах, поэтому важна установка / определение их ширины и высоты относительно заданного размера шрифта.

Это моя попытка:

enter image description here

I think this answer is relevant: Как узнать размер в пикселях указанного c текста на графике черепахи в python? , но он довольно старый, и ограничивающая рамка вокруг текста aws отсутствует правильное положение, используя python 3.8.

import turtle

FONT_SIZE = 32

class Tile(turtle.Turtle):
    def __init__(self):
        super().__init__(shape="square")
        self.penup()
    
    def show_value(self, val):
        self.write(val, font=("Arial", FONT_SIZE, "bold"), align="center")


screen = turtle.Screen()
vals = [5, 7, 8, 2]
for i in range(len(vals)):
    tile = Tile()
    tile_size = (FONT_SIZE / 20)
    tile.shapesize(tile_size)
    tile.fillcolor("red" if i % 2 == 0 else "blue")
    tile.setx(i * FONT_SIZE)
    tile.show_value(vals[i])
turtle.done()

1 Ответ

1 голос
/ 14 августа 2020

Было бы очень полезно иметь объекты Turtle, содержащие текст, например целочисленные значения, которые можно использовать для отображения различных головоломок и игр, и к которым могут быть прикреплены собственные обработчики щелчков.

Вот загвоздка и (две) причины, которые подходят к использованию штампа (), как предложено в других ответах, не будут работать. Во-первых, вы не можете щелкнуть на скрытой черепахе:

from turtle import *

def doit(x, y):
    print("Just do it!")

yertle = Turtle()
# comment out the following line if you want `onlick()` to work
yertle.hideturtle()
yertle.shape('square')
yertle.stamp()
yertle.onclick(doit)

done()

Штампы не являются интерактивными объектами. Во-вторых, вы даже не можете нажать на черепаху, которая находится за чернилами, оставленными этой или другой черепахой:

from turtle import *

def doit(x, y):
    print("Just do it!")

yertle = Turtle()
yertle.shape('square')
yertle.fillcolor('white')
yertle.onclick(doit)

myrtle = Turtle()
myrtle.shape('turtle')
myrtle.penup()
myrtle.sety(-16)
# comment out the following line if you want `onlick()` to work
myrtle.write('X', align='center', font=('Courier', 32, 'bold'))
myrtle.goto(100, 100)  # move myrtle out of the way of clicking

done()

Если вы нажмете на букву «X», ничего не произойдет, если вам не удастся ударить часть квадрата сразу за буквой. Я считаю, что, хотя мы думаем о 'X' как о мертвых чернилах над нашей живой черепахой , на уровне tkinter они оба похожи, возможно, оба способны принимать события, поэтому один скрывает щелчок на другом.

Итак, как мы можем это сделать? Подход, который я собираюсь использовать, заключается в создании плитки черепахи с изображением, где изображения создаются путем записи в растровые изображения:

tileset.py

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

DEFAULT_FONT_FILE = "/Library/Fonts/Courier New Bold.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)

Помимо изображения, единственные отличия экземпляра Tile от экземпляра Turtle - это дополнительный метод tile_size() для возврата его ширины и высота как правило, c черепахи не могут этого сделать в случае изображений. А перо плитки - вверх в начале, вместо вниз .

Я нарисовал пару вопросов и ответов SO:

И пока я на нем, этот ответ был обновлен, чтобы быть более независимым от системы:

Чтобы продемонстрировать, как работают мои наборы плиток , вот хорошо известный 15 пазлов реализовано с их помощью. Он создает два набора плиток, один с белым фоном и один с красным (розовым) фоном:

from tileset import TileSet
from turtle import Screen
from functools import partial
from random import shuffle

SIZE = 4
OFFSETS = [(-1, 0), (0, -1), (1, 0), (0, 1)]

def slide(tile, row, col, x, y):
    tile.onclick(None)  # disable handler inside handler

    for dy, dx in OFFSETS:
        try:
            if row + dy >= 0 <= col + dx and matrix[row + dy][col + dx] == None:
                matrix[row][col] = None
                row, col = row + dy, col + dx
                matrix[row][col] = tile
                width, height = tile.tile_size()
                x, y = tile.position()
                tile.setposition(x + dx * width, y - dy * height)
                break
        except IndexError:
            pass

    tile.onclick(partial(slide, tile, row, col))

screen = Screen()

matrix = [[None for _ in range(SIZE)] for _ in range(SIZE)]

white_tiles = TileSet(background_color='white')
red_tiles = TileSet(background_color='pink')

tiles = []
parity = True

for number in range(1, SIZE * SIZE):
    string = str(number).rjust(2)
    tiles.append(white_tiles.make_tile(string) if parity else red_tiles.make_tile(string))
    parity = not parity

    if number % SIZE == 0:
        parity = not parity

shuffle(tiles)

width, height = tiles[0].tile_size()
offset_width, offset_height = width * 1.5, height * 1.5

for row in range(SIZE):
    for col in range(SIZE):
        if row == SIZE - 1 == col:
            break

        tile = tiles.pop(0)
        width, height = tile.tile_size()
        tile.goto(col * width - offset_width, offset_height - row * height)
        tile.onclick(partial(slide, tile, row, col))
        matrix[row][col] = tile

screen.mainloop()

Если вы нажмете на числовую плитку рядом с пустым пространством, она переместится в пустое пространство, иначе ничего не произойдет. Этот код не гарантирует решения головоломки - половина не будет решена из-за случайного перемешивания. Это просто демонстрация, мельчайшие детали и сами плитки оставлены на ваше усмотрение.

введите описание изображения здесь

...