0

I've made 3 functions to draw a hilbert curve. this is more a math problem but i'll include the coding just in case. I use the turtle python module to draw.

1st one, this one let me derive a list.

def derive(c1,c2,l,l1,l2):
    """
    derive list l by list l1 for every c1 element and by list2 for every
    c2 element. designed for hilbert fractal and dragon fractal

    :param c1: element by which l2 will derive l1
    :type c1: str
    :param c2: element by which l2 will derive l1
    :type c2: str
    :param l: list to be derived
    :type l: list
    :param l1: list to derive with
    :type l1: list
    :param l2: list to derive with
    :type l2: list
    :return: list
    :CU: l1 and l2 must only contain these elements : 'R','L','F','+','-'
    :Example:

    >>>derive('-','A',['F','-','F'],['+','+','F'])
    ['F', '+', '+', 'F', 'F']

    """

    assert type(l) in {list} and type(l1) in {list} and type(l2) in {list},'parameter l1 or l2 must be a list'
    assert type(c1) in {str} and type(c2) in {str},'parameter c1 and c2 must be a string'

    lret = []

    for e in l:
        if e != c1 and e!= c2:
#            assert type(e) in {str},'parameter l must only contain str'
#            assert e == 'R' or e == 'L' or e == 'F' or e == '+' or e == '-','parameter l1 elements must be \'R\' or \'L\' or \'F\' or \'-\' or \'+\'' 
            lret.append(e)
        elif e == c1:
            for e1 in l1:
#                assert type(e1) in {str},'parameter l1 must only contain str'
#                assert e1 == 'R' or e1 == 'L' or e1 == 'F' or e1 == '+' or e1 == '-','parameter l1 elements must be \'R\' or \'L\' or \'F\' or \'-\' or \'+\'' 
                lret.append(e1)
        elif e == c2:
            for e2 in l2:
#                assert type(e2) in {str},'parameter l2 must only contain str'
#                assert e2 == 'R' or e2 == 'L' or e2 == 'F' or e2 == '+' or e2 == '-','parameter l1 elements must be \'R\' or \'L\' or \'F\' or \'-\' or \'+\'' 
                lret.append(e2)


    return lret

2nd one, this one derive nth time

def derive_n(n,c1,c2,l,l1,l2):
    """
    derive list l1 by list l2 for every c element n-th times

    :param c1: element by which l1 will derive l
    :type c1: str
    :param c2: element by which l2 will derive l
    :type c2: str
    :param l: list to be derived
    :type l: list
    :param l1: list to derived with
    :type l1: list
    :param l2: list to derive with
    :type l2: list
    :param n: number of time l1 will be derived
    :type n: int
    :return: list
    :CU: n >= 0
    :Example:

    >>>derive_n(0,'F','L',['F','-','F'],['F','+','F'],['L','R'])
    ['F', '-', 'F']

    >>>derive_n(1,'F','L',['F','-','F'],['F','+','F'],['L','R'])
    ['F', '+', 'F', '-', 'F', '+', 'F']

    >>>derive_n(2,'F','L',['F','-','F'],['F','+','F'],['L','R'])
    ['F', '+', 'F', '+', 'F', '+', 'F', '-', 'F', '+', 'F', '+', 'F', '+', 'F']


    """

    if n == 0:
        return l
    else:
        return derive(c1,c2,derive_n(n-1,c1,c2,l,l1,l2),l1,l2)

the 3rd function draws the curve :

def draw(il,l,a):
    """
    draw a fractal by following parameter il

    :param il: instruction list
    :type il: list
    :param l: length for forward() function
    :type l: float or int
    :param a: angle for left and right function in degree
    :type a: float or int
    :CU: l > 0 
    """
    assert type(a) in {int,float},'parameter a must be an int or float'
    assert type(l) in {int,float},'parameter l must be an int or float'
    assert type(il) in {list},'parameter il must be a list'
    assert l > 0,'parameter l must be strictly superior to 0'

    board_reset()

    pendown()

    for e in il:
        if e == 'F':
            forward(l)
        elif e == '+':
            left(a)
        elif e == '−':
            right(a)


    penup()

boardrese() is a function to reinitialize to drawing board.

this is a project i have to do for class. i almost finished it but according to my professor, no matter how many time you derive the list, the drawing must always fill a square with a size that doesn't change.

basically, I need to do some math on the length parameter for the draw function. i just don't know what. i've tried l/n, l/(number of time 'F' appears in the final list), l/(length of final list )...

thanks

2 Answers2

0

I've separately implemented code to draw a Hilbert curve using turtle, as I had a bunch of reusable code lying around from my Processing l-systems project (if you really want to can refer to it for inspiration). It uses a recursive generator rather than a list - this means it's pretty efficient in memory, and only needs to keep track of a couple of stack frames, and only the single "action" it's going to return. It's also got a hacky little argparser for my own testing purposes.

import turtle
import sys

from enum import Enum

screen = turtle.Screen()
t = turtle.Turtle()
t.speed(0)
t.pu()
t.setpos(-screen.window_width() * 0.5 + 50,
         -screen.window_height() * 0.5 + 50)
t.pd()

class HilA(Enum):
    F = 0
    L = 1
    R = 2
    A = 3
    B = 4

RULES = {HilA.A: [HilA.L, HilA.B, HilA.F, HilA.R, HilA.A, HilA.F, HilA.A, HilA.R, HilA.F, HilA.B, HilA.L],
         HilA.B: [HilA.R, HilA.A, HilA.F, HilA.L, HilA.B, HilA.F, HilA.B, HilA.L, HilA.F, HilA.A, HilA.R]}

def gen_l(depth, pattern=RULES[HilA.A], rules=RULES):
    if depth > 1:
        for i in pattern:
            if i in rules:
                yield from gen_l(depth - 1, pattern=rules[i], rules=rules)
            else:
               yield i
    else:
        yield from pattern

def draw_hil(depth):
    line_length = (min(screen.window_width(), screen.window_height()) - 100) * 2 ** -depth
    for action in gen_l(depth):
        if action == HilA.F:
            t.forward(line_length)
        elif action == HilA.L:
            t.left(90)
        elif action == HilA.R:
            t.right(90)

try:
    draw_hil(int(sys.argv[1]))
except (TypeError, IndexError):
    draw_hil(5)
print("done")
input()

However, you can pretty much ignore all this - the important part is

    line_length = (min(screen.window_width(), screen.window_height()) - 100) * 2 ** -depth

This boils down to

    line_length = width / (2 ** depth)

This confines it pretty well for anything over depth ~= 2. For depth = 1, it's a little off due to the very fast shrinking factor in the "connecting" lines vs the structural lines.

Note that this doesn't account for any angle other than 90, but as far as I'm concerned that wouldn't make much sense, as only the angle 90 will produce a square to bound itself in. If you do need other angles, you might need some trig for that.

Izaak van Dongen
  • 2,450
  • 13
  • 23
  • i tried dividing the length by 2 ** depth but it doesn't work. it gets bigger and bigger. :/ –  Oct 05 '17 at 18:56
  • edit to previous comment : actually, i think my professor said that the figure will get closer to a certain size but never actually reach that size. i will try with higher depth and see if it worked –  Oct 05 '17 at 19:03
0

One approach is not to code around the issue but rather change your coordinate system to accommodate. E.g in Python turtle:

recursions = 5  # or whatever

size = 2 ** recursions

screen = Screen()  # whatever window size you want via .setup()
screen.setworldcoordinates(0, 0, size, size)
cdlane
  • 40,441
  • 5
  • 32
  • 81
  • im not sure if i did it right but i tried doing what you did there but it only made turtle go somewhere in the bottom of the screen –  Oct 05 '17 at 19:00
  • @oozmakoopa, depending on what coordinates you're using, you may want `screen.setworldcoordinates(-size // 2, -size // 2, size // 2, size // 2)` instead. – cdlane Oct 05 '17 at 20:45