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?