Anagram viewer

What do you think about the following program? Sorry for the bad English and the German comments, but i am German :wink:
Touch on the screen to see the animation. You can change all parameters between this ---------- lines -----------


--# Main
-- Anagram

displayMode(FULLSCREEN)

function setup()
    --------------------
    wordList = {"A cool (not-only) anagram program","it moves and fades letters","to animate the change of two or more words","by David Knothe","and again:"}
    repeating = true
    
    In,Out,InOut,OutIn = 1,2,3,4
    fade = {power = 2, effect = In, duration = 2, delay = 1}
    effectOnlyForFade = true
    
    fontSize(40)
    --------------------
    
    actWord,actTime,startPosition,startLetter,endPosition,endLetter = prepareForNext(0,repeating,wordList)
    isAnimating = false
    hasAnimated = false
end


function draw()
    background(0)
    
    textMode(CORNER)
    
    -- Animation:
    if isAnimating then
        actTime = actTime + DeltaTime/fade.duration
    elseif hasAnimated then
        actTime = fade.duration + fade.delay
    end
    t = math.min(1,math.max(0,(actTime-fade.delay)/fade.duration)) -- 0 <= t <= 1
    
    if fade.effect == In then
        a = t^fade.power
    elseif fade.effect == Out then
        a = t^(1/fade.power)
    elseif fade.effect == InOut then
        a = ( math.abs(2*t-1)^(1/fade.power)*sign(2*t-1) )/2+.5
    elseif fade.effect == OutIn then
        a = ( math.abs(2*t-1)^(fade.power)*sign(2*t-1) )/2+.5
    else
        a = t
    end
    
    translate(WIDTH/2,HEIGHT/2-fontSize()/2)
    for i in pairs(startPosition) do
        if effectOnlyForFade then -- Position linear
            position = startPosition[i]*(1-t)+endPosition[i]*t
        else
            position = startPosition[i]*(1-a)+endPosition[i]*a
        end
        
        if startLetter[i] == endLetter[i] then
            fill(255)
            text(startLetter[i],position,0)
        else
            fill(255,255*(1-a))
            text(startLetter[i],position,0)
            fill(255,255*a)
            text(endLetter[i],position,0)
        end
    end
    
    if t == 1 then
        if actWord == #wordList-1 and not repeating then -- Zu Ende
            isAnimating = false
            hasAnimated = true
        else
            actWord,actTime,startPosition,startLetter,endPosition,endLetter = prepareForNext(actWord,repeating,wordList)
        end
    end
end


function prepareForNext(place,repeating,wordList)
    place = place + 1 -- Nächstes Wort
    if place%#wordList == 0 then
        startWord = wordList[#wordList]
    else
        startWord = wordList[place%#wordList]
    end
    endWord = wordList[place%#wordList+1]
    
    time = 0
    
    startPlace,startLetter,endPlace,endLetter = createMoveLists(startWord,endWord) -- Listennamen müssten eigentlich im Plural sein, aber so liest es sich besser als bspw. "Position an der Stelle i"
    
    startWordPositions = getPositionsOnScreen(startWord)
    endWordPositions = getPositionsOnScreen(endWord)
    
    startPosition = getElementsInListWithIndicesFromList(startWordPositions,startPlace)
    endPosition = getElementsInListWithIndicesFromList(endWordPositions,endPlace)
    
    return place,time,startPosition,startLetter,endPosition,endLetter
end


function createMoveLists(startWord,endWord)
    -- Listen, die zurückgegeben werden:
    startPlace_ = {}
    startLetter_ = {}
    endPlace_ = {}
    endLetter_ = {}
    
    startLetterIsUsed = {} -- i.tes Element gibt an, ob der i.te Buchstabe im Startwort bereits verwendet wurde
    endLetterIsUsed = {}
    
    for i=1,string.len(startWord) do -- Jeden Buchstaben im Startwort analysieren
        startLetter = string.sub(startWord,i,i)
        possibleFirstChoiceEndLetters = {} -- Die für diesen Buchstaben möglichen Endplätze
        possibleSecondChoiceEndLetters = {} -- Zweite Wahl: Buchstabe ändert Großschreibung
        
        for j=1,string.len(endWord) do -- Schauen, welcher Buchstabe im Endwort ähnlich ist
            endLetter = string.sub(endWord,j,j)
            
            if startLetter == endLetter and not endLetterIsUsed[j] then
                table.insert(possibleFirstChoiceEndLetters,j)
            elseif string.lower(startLetter) == string.lower(endLetter) and not endLetterIsUsed[j] then
                table.insert(possibleSecondChoiceEndLetters,j)
            end
        end
        
        randomEndPlace = nil
        if #possibleFirstChoiceEndLetters > 0 then -- Zufälligen Endplatz für Buchstaben auswählen
            randomEndPlace = possibleFirstChoiceEndLetters[math.random(#possibleFirstChoiceEndLetters)]
        elseif #possibleSecondChoiceEndLetters > 0 then
            randomEndPlace = possibleSecondChoiceEndLetters[math.random(#possibleSecondChoiceEndLetters)]
        end
        
        if randomEndPlace then
            startLetterIsUsed[i] = true
            endLetterIsUsed[randomEndPlace] = true
            table.insert(startPlace_,i)
            table.insert(endPlace_,randomEndPlace)
            table.insert(startLetter_,startLetter)
            table.insert(endLetter_,string.sub(endWord,randomEndPlace,randomEndPlace))
        end
    end
    
    notUsedStartLetters = {} -- Schauen, ob es nicht benutzte Buchstaben gibt
    notUsedEndLetters = {}
    for i=1,string.len(startWord) do
        if not startLetterIsUsed[i] then
            table.insert(notUsedStartLetters,i)
        end
    end
    for i=1,string.len(endWord) do
        if not endLetterIsUsed[i] then
            table.insert(notUsedEndLetters,i)
        end
    end
    
    if #notUsedStartLetters >= #notUsedEndLetters then -- Jedem Endbuchstaben einen Startbuchstaben zuordnen
        for _,endPlace in pairs(notUsedEndLetters) do
            randomStartPlaceIndex = math.random(#notUsedStartLetters)
            startPlace = notUsedStartLetters[randomStartPlaceIndex]
            table.insert(startPlace_,startPlace)
            table.insert(endPlace_,endPlace)
            table.insert(startLetter_,string.sub(startWord,startPlace,startPlace))
            table.insert(endLetter_,string.sub(endWord,endPlace,endPlace))
            table.remove(notUsedStartLetters,randomStartPlaceIndex)
        end
        for _,startPlace in pairs(notUsedStartLetters) do -- Immer noch übrig gebliebene Startbuchstaben
            table.insert(startPlace_,startPlace)
            table.insert(endPlace_,math.random(string.len(endWord)))
            table.insert(startLetter_,string.sub(startWord,startPlace,startPlace))
            table.insert(endLetter_,"")
        end
    else -- Jedem Startbuchstaben einen Endbuchstaben zuordnen
        for _,startPlace in pairs(notUsedStartLetters) do
            randomEndPlaceIndex = math.random(#notUsedEndLetters)
            endPlace = notUsedEndLetters[randomEndPlaceIndex]
            table.insert(startPlace_,startPlace)
            table.insert(endPlace_,endPlace)
            table.insert(startLetter_,string.sub(startWord,startPlace,startPlace))
            table.insert(endLetter_,string.sub(endWord,endPlace,endPlace))
            table.remove(notUsedEndLetters,randomEndPlaceIndex)
        end
        for _,endPlace in pairs(notUsedEndLetters) do -- Immer noch übrig gebliebene Endbuchstaben
            table.insert(startPlace_,math.random(string.len(startWord)))
            table.insert(endPlace_,endPlace)
            table.insert(startLetter_,"")
            table.insert(endLetter_,string.sub(endWord,endPlace,endPlace))
        end
    end
    
    return startPlace_,startLetter_,endPlace_,endLetter_
end


function getPositionsOnScreen(word)
    posList = {0}
    
    for i=1,string.len(word) do
        char = string.sub(word,i,i)
        posList[i+1] = posList[i]+textSize(char) -- Position des nächsten Buchstabens anhand der Größe des aktuellen bestimmen
    end
    
    shift = posList[#posList]/2
    for i in pairs(posList) do
        posList[i] = posList[i] - shift
    end
    
    return posList
end


function getElementsInListWithIndicesFromList(valueList,positionsList)
    endList = {}
    
    for i,p in pairs(positionsList) do
        endList[i] = valueList[p]
    end
    
    return endList
end


function sign(x)
    if x == x and x ~= 0 then
        return x/math.abs(x)
    end
    return 0
end


function touched(touch)
    if not hasAnimated then
        isAnimating = true
    end
end

@someone very nice looking animation. Great job! Welcome on board.

@someone, I’m testing your code. I can´t put some letter like ‘ç’ or ‘á’ :frowning:

@someone It is very suitable for animated display text. Thanks for sharing. :slight_smile:

@erickyamato Yes I know but I think thats not my fault - Codea doesn’t support those letters.

@matox @Jmv38 Thank you! :slight_smile:

@Someone, these letters are very important (to me, because I’m brazilian and portuguese has those letters) :confused:

But, this is an amazing code! :slight_smile:

@Someone Codea does support those letters. The issue is that string.sub works on the byte level, not the character level. Strings are encoded in utf-8 so you need to recognise a utf-8 encoded character and remove a whole one, which can be one or more bytes. My anagram program (which is one of the examples in Codea) contains code to handle utf8 strings.

@Andrew_Stacey Ok you’re right. Sorry for that - If you want you could implement it, but I think I won’t do it because it would destroy the shortness of the code :wink: @erickyamato

@Andrew_Stacey I tryied to use some letters on your code, but it happens too :confused:

@erickyamato That’s a very helpful bug report! What letters, what code, and what did you try?

@Andrew_Stacey I sent you a message!

I used your code(anagrams) I’m brazilian and here in Brazil, we have some words like ‘maçã’ = ‘apple’.

I tryied to put those words on Words class

Ah, I remember now. There was a bug with my utf8 code that has since been fixed, but it didn’t get backported into the Anagrams code. My latest version works just fine with your test word. It’s bundled as part of “Library Base” on Codea Community.

@Andrew_Stacey Could you please share the link?