M4BASIC - ga.m4b
Not logged in
REM This is an implementation of the principles explained at:
REM http://www.puremango.co.uk/2010/12/genetic-algorithm-for-hello-world/
REM
REM We're using ASCII values 32-126 here to store as a string. This probably can be
REM done in a more efficient way.
REM
REM Fill in anything for Target to approach some other string.
REM
REM The DomainSize value determines the total population.
REM The CloneRate value determines the percentage to clone.
REM The MutateRate value determines the percentage of siblings to mutate.
REM
REM PvE, September 2014 - GPL.
REM ----------------------------------------------------------------------------------

CONST Target = "Brave New World!"

CONST DomainSize = 400
CONST CloneRate = 40
CONST MutateRate = 20

DECLARE Population[DomainSize+1] TYPE char*
DECLARE Nextgen[DomainSize+1] TYPE char*
INTEGER gen, ind

CONST TLen = LEN(Target)

REM ----------------------------------------------------------------------------------

FUNC Fitness(char* chromosome)

    INTEGER x, status

    REM Calculate the difference per individual character
    FOR x = 1 TO TLen
        DECR status, ABS(ASC(MID(chromosome, x, 1)) - ASC(MID(Target, x, 1)))
    NEXT

    RETURN status

ENDFUNC

REM ----------------------------------------------------------------------------------

SUB Populate()

    INTEGER x, y

    REM Generate purely random strings
    FOR x = 1 TO DomainSize
        SET Population[x] = ""
        FOR y = 1 TO TLen
            SET Population[x] = CONCAT(Population[x], CHR(RANDOM(94)+32))
        NEXT y
    NEXT x

ENDSUB

REM ----------------------------------------------------------------------------------

SUB Generate()

    INTEGER m1, m2, m3, m4, x, pos, y
    STRING mutant1, mutant2

    FOR x = 1 TO DomainSize STEP 2

        LET m1 = RANDOM(DomainSize)+1
        LET m2 = RANDOM(DomainSize)+1
        LET m3 = RANDOM(DomainSize)+1
        LET m4 = RANDOM(DomainSize)+1

        REM Should we clone or breed?
        IF RANDOM(100)+1 < CloneRate THEN

            REM Cloning
            IF Fitness(Population[m1]) > Fitness(Population[m2]) THEN
                LET Nextgen[x] = Population[m1]
            ELSE
                LET Nextgen[x] = Population[m2]
            ENDIF
            IF Fitness(Population[m3]) > Fitness(Population[m4]) THEN
                LET Nextgen[x+1] = Population[m3]
            ELSE
                LET Nextgen[x+1] = Population[m4]
            ENDIF
        ELSE
            REM Breeding
            LET pos = RANDOM(TLen-2)+2
            SET Nextgen[x] = CONCAT(MID(Population[m1], 1, pos), MID(Population[m2], pos+1, LEN(Population[m2])))
            SET Nextgen[x+1] = CONCAT(MID(Population[m2], 1, pos), MID(Population[m1], pos+1, LEN(Population[m1])))
        ENDIF

        REM Should we apply mutation?
        IF RANDOM(100)+1 < MutateRate THEN

            REM Mutate both children
            LET pos = RANDOM(TLen)+1
            LET mutant1 = "" LET mutant2 = ""

            FOR y = 1 TO TLen
                IF y == pos THEN
                    REM Find current character and add/substract max 5
                    LET m1 = ASC(MID(Nextgen[x], pos, 1)) + RANDOM(11) - 5
                    IF m1 > 126 THEN LET m1 = m1 - 127 + 32 FI
                    IF m1 < 32 THEN LET m1 = m1 + 127 - 32 FI
                    SET mutant1 = CONCAT(mutant1, CHR( m1 ))

                    REM Find current character and add/substract max 5
                    LET m2 = ASC(MID(Nextgen[x], pos, 1)) + RANDOM(11) - 5
                    IF m2 > 126 THEN LET m2 = m2 - 127 + 32 FI
                    IF m2 < 32 THEN LET m2 = m2 + 127 - 32 FI
                    SET mutant2 = CONCAT(mutant2, CHR( m2 ) )
                ELSE
                    SET mutant1 = CONCAT(mutant1, MID(Nextgen[x], y, 1))
                    SET mutant2 = CONCAT(mutant2, MID(Nextgen[x+1], y, 1))
                ENDIF
            NEXT
            SET Nextgen[x] = mutant1
            SET Nextgen[x+1] = mutant2
        ENDIF
    NEXT

    REM Now copy new generation to population
    FOR x = 1 TO DomainSize
        SET Population[x] = Nextgen[x]
    NEXT

ENDSUB

REM ----------------------------------------------------------------------------------

FUNC FindMatch()

    INTEGER x, closest, ind

    LET closest = TLen*-127

    REM Find closest match
    FOR x = 1 TO DomainSize
        IF Fitness(Population[x]) > closest THEN
            LET closest = Fitness(Population[x])
            LET ind = x
        ENDIF
    NEXT

    RETURN ind

ENDFUNC

REM ----------------------------------------------------------------------------------

LET gen = 0
CALL Populate()

REM Continue until a match is found
REPEAT
    CALL Generate()
    INCR gen
    LET ind = FindMatch()
    IF NOT(MOD(gen, 5)) THEN PRINT gen, Population[ind] FORMAT "Generation %ld best: %s\n" FI
UNTIL Fitness(Population[ind]) == 0

REM Print final result!
PRINT "-------------------------------------\n"
PRINT gen, Population[ind] FORMAT "Generation %ld best: %s\n"
PRINT "-------------------------------------\n"

Return to M4BASIC