classdef bauagent < handle
    %BAUAGENT Diese Klasse definiert die Eigenschaften des einzelnen Agenten
    %   Hier werden die Eigenschaften festgelegt, die den einzelnen Agenten
    %   ausmachen, der für den Bau der Wabenstruktur zuständig ist.
    %   Für jeden einzelnen Agenten wird ein Objekt dieser Klasse erzeugt.
    
    properties
        Position % Vektor, welcher die Position des Agenten in
        % X-Y(-Z)-Koordinaten enthält. Je nach Umgebung
        % zwei- oder dreidimensional.
        Ausrichtung % Blickrichtung des Agenten im Gradmaß (0 bis 360)
        % Null entspricht Blick waagerecht nach rechts
        RuleSet1
        % Enthält die Ausgangsregeln, nach denen gebaut oder nicht
        % gebaut wird. Hier werden nur Regeln festgelegt, mit
        % denen begonnen wird, danach werden die Regeln in das
        % Array classifierset kopiert und nur noch von dort
        % ausgelesen und auch durch den GA verändert.
        % Könnte auch aus Datei gelesen werden...
        RuleSet2 % Regelsatz der zweiten Hierarchieebene
        BuildCounter % Zählt die Anzahl der gesetzten Bausteine.
        Abstand % Ein Abstandsvektor, der die absoluten Abstandswerte zu
        % allen Zellenmittelpunkten enthält
        Speed = 1; % relative Geschwindigkeit, mit der sich der Agent bewegt
        
        Markierung % graphisches Objekt zur Positionsmarkierung
        Index % enthält die laufende Nummer des Agenten
        Farbe % die Farbe zur Markierung
        classifierset % ein Array, in dem die Objekte vom Typ 'classifier'
        % abgelegt werden
        numberofclassifiers % Anzahl der vorhandenen Regeln
        matchset % Array in dem die Indizes aller passenden Klassifikatoren für einen Zeitschritt abgelegt werden
        % - wird nach jedem Zeitschritt gelöscht
        bucket % enthält den Index-Wert des letzten benutzten Klassifikators
        % wird für den bucket-brigade-Algorithmus benötigt
        classifierfitnessstring % enthält die Fitnessstrings aller Regeln im Gray code hintereinander
    end
    
    methods
        function obj = bauagent(InitialPosition, indexNummer) % Konstruktor
            global Grenzen;
            global ring;
            
            obj.Ausrichtung = 0;
            % obj.RuleSet = ['BBB***1'; 'B*BB**1'; 'B**BB*1'; ...
            %                'B***BB1'; '*BBB**1'; '*B*BB*1'; ...
            %                '*B**BB1'; '**BBB*1'; '**BB*B1'; ...
            %                '***BBB1'; '*BB***2'; '****BB2'; ...
            %                '***B**3'; 'B*****3' ];
            
            % obj.RuleSet = ['B*****1'; '***B**4'; '*B****2'; ...
            %                '****B*5']; % '**B***3'; '*****B6' ]; % Regelwerk zum Testen
            
            % obj.RuleSet = ['BBB***3'; '*BBB**3'; '**BBB*3'; ...
            %               '***BBB3'; '*BB***1'; '****BB1'; ...
            %               '***B**2'; 'B*****2' ]; % simulation01.avi - 31x31 - 500 steps
            
            obj.RuleSet1 = ['BBBBBB1'; 'BBBBB01'; '0BBBBB1'; ...
                'B0BBBB1'; 'BB0BBB1'; 'BBB0BB1'; ...
                'BBBB0B1'; 'BBBB001'; '0BBBB01'; ...
                '00BBBB1'; 'B00BBB1'; 'BB00BB1'; ...
                'BBB00B1'; 'BBB0001'; '0BBB001'; ...
                '00BBB01'; '000BBB1'; 'B000BB1'; ...
                'BB000B1'; 'BB00001'; '0BB0001'; ...
                '00BB001'; '000BB01'; '0000BB1'; 'B0000B1'; ...
                'B000000'; '0B00000'; '00B0000'; ...
                '000B000'; '0000B00'; '00000B0'];
            
            obj.RuleSet2 = ['1100002'; '0110002'; '0011002'; ...
                '0001102'; '0000112'; '1000012'; ...
                '2110002'; '0211002'; '0021102'; ...
                '0002112'; '1000212'; '1100022'; ...
                '1120002'; '0112002'; '0011202'; ...
                '0001122'; '2000112'; '1200012'; ...
                '2112002'; '0211202'; '0021122'; ...
                '2002112'; '1200212'; '1120022'; ...
                '2120002'; '0212002'; '0021202'; ...
                '0002122'; '2000212'; '1200022'; ...
                '1200002'; '0120002'; '0012002'; ...
                '0001202'; '0000122'; '2000012'; ...
                '2100002'; '0210002'; '0021002'; ...
                '0002102'; '0000212'; '1000022'; ...
                '2111002'; '0211102'; '0021112'; ...
                '1002112'; '1100212'; '1110022'; ...
                '1112002'; '0111202'; '0011122'; ...
                '2001112'; '1200112'; '1120012'; ...
                '2111102'; '0211112'; '1021112'; ...
                '1102112'; '1110212'; '1111022'; ...
                '1111202'; '0111122'; '2011112'; ...
                '1201112'; '1120112'; '1112012'; ...
                '2111112'; '1211112'; '1121112'; ...
                '1112112'; '1111212'; '1111122'; ...
                '2111202'; '0211122'; '2021112'; ...
                '1202112'; '1120212'; '1112022'];
            
            if (ring == 1)
                obj.updateclassifierset(obj.RuleSet1);
            elseif (ring == 2)
                obj.updateclassifierset(obj.RuleSet2);
            end
            
            
            if nargin > 0
                obj.Index = indexNummer; % übergibt den Index aus dem Vektor
                % von Agenten als Eigenschaft
            else
                obj.Index = 0;
            end
            
            if (mod(obj.Index, 7) == 1)
                obj.Farbe = 'blue';
            elseif (mod(obj.Index, 7) == 2)
                obj.Farbe = 'cyan';
            elseif (mod(obj.Index, 7) == 3)
                obj.Farbe = 'green';
            elseif (mod(obj.Index, 7) == 4)
                obj.Farbe = 'white';
            elseif (mod(obj.Index, 7) == 5)
                obj.Farbe = 'black';
            elseif (mod(obj.Index, 7) == 6)
                obj.Farbe = 'yellow';
            else
                obj.Farbe = 'magenta';
            end
            
            obj.BuildCounter = 0; % Anzahl der gebauten Bausteine auf Null
            
            if nargin > 0
                obj.Position = InitialPosition;
            else
                % obj.Position = rand(1,2);
                % obj.Position(1,1) = obj.Position(1,1) * Grenzen(1); % Setzt die Position des Agenten auf einen
                % obj.Position(1,2) = obj.Position(1,2) * Grenzen(2); % beliebigen Punkt in den Grenzen des Gitters
                obj.Position(1,1) = 0;
                obj.Position(1,2) = 0;
            end
            
            obj.Markierung = line(obj.Position(1), obj.Position(2), ...
                1.1, 'Marker','.', 'MarkerFaceColor',obj.Farbe, ...
                'Color',obj.Farbe );
            % aktuelle Position des Agenten wird graphisch
            % markiert
            % set(obj.Markierung, 'XDataSource','agent.Position(1)');
            % set(obj.Markierung, 'YDataSource','agent.Position(2)');
            % Verknüpfung des Grafikobjekts mit der Position
        end
        
        % function obj = bauagent() % dummy konstruktor
        % end
        
        function obj = movebuild(agent) % Diese Funktion wird einmal pro Zeitschritt für jeden Agenten aufgerufen
            % Die Umgebung wird verarbeitet und nach den Regeln des Agenten
            % entschieden, ob der Agent an der aktuellen Position eine Wabe baut
            % oder sich weiterbewegt.
            global Wabengitter;
            global Knotenzahl;
            global Grenzen;
            global raumplot;
            global bebautezellen;
            global animation;
            global gadurchlauf;
            global numberofgaruns;
            
            % matchset muss gelöscht werden (empty matrix)
            agent.matchset = [];
            
            agent.Ausrichtung = mod(agent.Ausrichtung + rand*360, 360); % dreht den Agenten in eine zufällige Richtung
            
            Zelle = agent.indexPosition;      % Index der Zelle, auf dessen Fläche sich der Agent befindet
            Nachbarn = agent.nachbarn(Zelle); % erzeugt Liste der Indizes der den Agenten unmittelbar
            % umgebenden Zellen
            
            % refreshdata(agent.Markierung); % Markierung wird neu gezeichnet
            
            
            for nachbarZelle=1:6
                builtFlag = 0;
                validFlag = 1; % Initialisierung
                
                bauPosition = Nachbarn(nachbarZelle);
                % setze potentielle Position für neuen Baustein auf einen
                % der sechs Nachbarzellen der aktuellen Position
                
                if ( (bauPosition < 1) || (bauPosition > Knotenzahl) || ... % Grenzen des Gitters
                        (Wabengitter(4,bauPosition) ~= 0) || ... % Bebauung der Zelle
                        (Wabengitter(3,bauPosition) == 0) ) % und ist die Wabe noch im Gitter?
                    validFlag = 0;
                    
                end
                
                bauNachbarn = agent.nachbarn(bauPosition); % erzeugt Liste der unmittelbaren Nachbarn der
                % potentiellen
                % Bauposition
                
                for ruleNumber=1:agent.numberofclassifiers % jeder Klassifikator wird auf Validität geprüft
                    
                    
                    if (validFlag == 0)
                        break; % wenn sich die Position außerhalb des
                        % Gitters befindet oder schon bebaut ist,
                        % wird die nächste Position geprüft
                    end
                    
                    validFlag = 1;
                    
                    for ruleCellNumber=1:6 % jede Position der Regel einzeln geprüft
                        if ( (validFlag == 0) || ...
                                (bauNachbarn(ruleCellNumber) > Knotenzahl) || ...
                                (bauNachbarn(ruleCellNumber) < 1) )
                            break; % wenn schon die letzte Stelle der Regel ungültig war,
                            % braucht diese nicht mehr getestet zu
                            % werden - wenn die Position außerhalb
                            % des Gitters liegt, auch nicht
                        end
                        
                        if strcmp(agent.classifierset(ruleNumber).detector(ruleCellNumber), 'B')
                            if ( Wabengitter(4,bauNachbarn(ruleCellNumber)) == 1 ...
                                    || Wabengitter(4,bauNachbarn(ruleCellNumber)) == 2 ...
                                    || Wabengitter(4,bauNachbarn(ruleCellNumber)) == 3 ...
                                    || Wabengitter(4,bauNachbarn(ruleCellNumber)) == 4 ...
                                    || Wabengitter(4,bauNachbarn(ruleCellNumber)) == 5 ...
                                    || Wabengitter(4,bauNachbarn(ruleCellNumber)) == 6 ...
                                    || Wabengitter(4,bauNachbarn(ruleCellNumber)) == 7 )
                                validFlag = 1; % wenn die Regel an dieser Stelle 'B' enthält,
                                % wird validFlag 'true' gesetzt, falls ein
                                % beliebiger Baustein (1-7) vorhanden ist.
                            else
                                validFlag = 0;
                            end
                        elseif strcmp(agent.classifierset(ruleNumber).detector(ruleCellNumber), '*')
                            validFlag = 1; % enthält die Regel an der betreffenden Stelle ein '*'
                            % so ist der Zustand der Zelle
                            % egal.
                        else
                            validFlag = strcmp(num2str(Wabengitter(4,bauNachbarn(ruleCellNumber))), ...
                                agent.classifierset(ruleNumber).detector(ruleCellNumber));
                            % in allen anderen Fällen (genaue
                            % Spezifikation in der Regel) muss
                            % der Zelleninhalt exakt mit der
                            % Regelposition übereinstimmen.
                        end
                        
                    end
                    
                    if (validFlag == 1)
                        
                        agent.classifierset(ruleNumber).bid = ...
                            gray2bin(agent.classifierset(ruleNumber).strength,'fsk',256) * ... % Rückkonvertierung von Gray
                            log2(agent.classifierset(ruleNumber).specificity + 1);
                        % bid-Wert wird berechnet:
                        % bid(r) = strength(r) * log2(specificity(r))
                        
                        agent.matchset(length(agent.matchset)+1) = ruleNumber;
                        % wenn die Regel des Klassifikators gültig ist,
                        % wird seine Indexnummer in matchset übernommen
                        
                    end
                    
                    validFlag = 1; % sonst wird die nächste Regel nicht angewendet
                end
                % an dieser Position sind alle Regeln ausgewertet und
                % matchset sollte alle passenden Regeln enthalten
                
                
                overallbid = 0;
                cumulativebid = 0;
                chosenrule = 0;
                
                if ~isempty(agent.matchset) % bei leerem matchset muss nichts ausgewählt werden
                    for clsf=1:length(agent.matchset)
                        overallbid = overallbid + agent.classifierset(agent.matchset(clsf)).bid;
                    end
                    
                    for clsf=1:length(agent.matchset)
                        normalbid = agent.classifierset(agent.matchset(clsf)).bid / ...
                            overallbid; % Normalisierung des bid-Wertes
                        % auf Wert zwischen 0 und 1
                        % (Anteil an der Summe aller
                        % bid-Werte aller Regeln)
                        agent.classifierset(agent.matchset(clsf)).minfit = cumulativebid;
                        cumulativebid = cumulativebid + normalbid;
                        agent.classifierset(agent.matchset(clsf)).maxfit = cumulativebid;
                        % Unter- und Obergrenzen werden gesetzt
                    end
                    
                    decision = rand;
                    for clsf=1:length(agent.matchset)
                        if (decision >= agent.classifierset(agent.matchset(clsf)).minfit && ...
                                decision <= agent.classifierset(agent.matchset(clsf)).maxfit)
                            chosenrule = agent.matchset(clsf);
                            break;
                        end
                    end % hier wird die zu verwendende Regel ausgewählt und ihr Index in chosenrule abgespeichert
                end
                
                
                % wenn eine Regel ausgewählt wurde (chosenrule), muss nach
                % dieser Regel auch gebaut werden:
                if (chosenrule)
                    Wabengitter(4,bauPosition) = agent.classifierset(chosenrule).effector;
                    % hier wird gebaut!!
                    
                    agent.bucket = chosenrule;
                    % benutzte Regel wird für BBA
                    % zwischengespeichert
                    
                    agent.BuildCounter = agent.BuildCounter + 1;
                    % Counter erhöhen
                    
                    builtFlag = 1; % zeigt an, dass in diesem Zeitschritt gebaut wurde
                    
                    if (gadurchlauf == numberofgaruns)
                        plotwabe(bauPosition, raumplot, Wabengitter(4,bauPosition));
                        % die gebaute Wabe wird graphisch markiert
                        % Parameter:
                        % bauPosition = Index der zu bebauenden
                        % Zelle
                        % raumplot = Handle des zu benutzenden
                        % Achsenobjekts
                        % Wabengitter(...) = Typ des Bausteins
                        % (definiert die Farbe)
                    end
                    bebautezellen = find(Wabengitter(4,:)); % Update der Liste der Indizes aller bebauten Zellen
                end
                
                if true(builtFlag)
                    builtFlag = 0;
                    break; % jeder Agent soll höchstens einmal pro Zeitschritt bauen
                end
            end
            
            
            agent.Position(1) = agent.Position(1) + cosd(agent.Ausrichtung * agent.Speed); % Bewegung in x-Richtung
            agent.Position(2) = agent.Position(2) + sind(agent.Ausrichtung * agent.Speed); % Bewegung in y-Richtung
            
            zelle = agent.indexPosition;
            if ( (Wabengitter(4,zelle) == 0) || ... % wenn Position unbebaut ist...
                    (agent.Position(1) > Grenzen(1)) || ...
                    (agent.Position(1) < 0) ||...
                    (agent.Position(2) > Grenzen(2)) || ...
                    (agent.Position(2) < 0) ) % .. oder außerhalb des Gitters liegt
                % ...dann mache Positionsänderung rückgängig
                agent.Position(1) = agent.Position(1) + ...
                    cosd( mod((agent.Ausrichtung + 180),360) * agent.Speed );
                agent.Position(2) = agent.Position(2) + ...
                    sind( mod((agent.Ausrichtung + 180),360) * agent.Speed );
            end
            
            if (gadurchlauf == numberofgaruns)
                
                % delete(agent.Markierung); % alte Markierung wird gelöscht
                
%                 agent.Markierung = line(agent.Position(1), agent.Position(2), ...
%                  1.1, 'Marker','.', 'MarkerFaceColor',agent.Farbe, ...
%                  'Color',agent.Farbe, 'Parent',raumplot );
                % und neue Markierung gesetzt
            end
            
            if (animation)
                drawnow;
            end % zeigt die animierte Bausequenz, wenn animation-flag gesetzt
            
            
        end
        
        function liste = nachbarn(obj, mittelpunkt)
            % Gibt für einen gegebenen Knoten im Wabengitter die Liste der
            % direkten Nachbarn zurück - Reihenfolge vom oberen Nachbarn
            % beginnend im Uhrzeigersinn
            global DX;
            
            liste = [ mittelpunkt+2*DX, ...
                mittelpunkt+DX+1+mod(idivide(mittelpunkt,uint32(DX)),2), ...
                mittelpunkt-DX+1+mod(idivide(mittelpunkt,uint32(DX)),2), ...
                mittelpunkt-2*DX, ...
                mittelpunkt-DX-2+mod(idivide(mittelpunkt,uint32(DX)),2), ...
                mittelpunkt+DX-2+mod(idivide(mittelpunkt,uint32(DX)),2) ];
        end
        
        function indexnummer = indexPosition(agent)
            % Gibt für jeden Agenten den Index des Mittelpunkts derjenigen
            % Wabe zurück, auf deren Fläche sich der Agent nach seinen
            % Koordinaten befindet
            global Wabengitter;
            global MittelPunkte;
            % global bebautezellen;
            
            agent.Abstand = [abs(Wabengitter(1,MittelPunkte) - agent.Position(1)); ...
                abs(Wabengitter(2,MittelPunkte) - agent.Position(2))];
            kandidatenX = find(agent.Abstand(1,:) <= 1);
            kandidatenY = find(agent.Abstand(2,:) <= 1);
            
            indexnummer = 0;
            for kandX=1:length(kandidatenX)
                for kandY=1:length(kandidatenY)
                    if (kandidatenX(kandX) == kandidatenY(kandY))
                        indexnummer = MittelPunkte(kandidatenX(kandX));
                        break;
                    end
                end
                if (indexnummer ~= 0)
                    break;
                end
            end
            
            % folgender Code ist zu langsam und wird nur noch verwendet,
            % wenn der andere kein Ergebnis bringt:
            %
            if indexnummer == 0
                agent.Abstand = sqrt((Wabengitter(1,MittelPunkte) - (agent.Position(1,1))).^2 ...
                    + (Wabengitter(2,MittelPunkte) - (agent.Position(1,2))).^2 );
                % erzeugt einen Vektor mit den euklidischen Abständen des Agenten
                % von allen Wabenmittelpunkten:
                % D(X,Y) = sqrt [ (x1-y1)^2 + (x2-y2)^2 ]
                
                [abstand, I] = min(agent.Abstand); % gibt den Abstand zum nächstgelegenen
                % Wabenmittelpunkt (abstand) und den dazugehörigen
                % Index im Vektor Abstand (I) zurück
                indexnummer = MittelPunkte(I); % der nächste Mittelpunkt ist der
                % mit Index I im Vektor Abstand, also ist sein
                % Index im Array Wabengitter der Wert an Stelle i
                % im Vektor Abstand
            end
        end
        
        function updatefitness(agent)
            % Kopiert den Gesamt-Fitnesstring in die Stärkewerte der
            % einzelnen Regeln.
            for regelindex=1:agent.numberofclassifiers
                agent.classifierset(regelindex).fitnessstring = ...
                    agent.classifierfitnessstring( ((regelindex-1)*8)+1:((regelindex-1)*8)+8 );
                graystrength = bin2dec(agent.classifierset(regelindex).fitnessstring);
                % Konvertierung von Binärstring in
                % Dezimalzahl(Graycodiert)
                agent.classifierset(regelindex).strength = ...
                    gray2bin(graystrength,'fsk',256); % konvertiert von gray in standard binär
                % und speichert im strength-Feld
                
            end
        end
        
        function updateclassifierset(agent, ruleset)
            % Erzeugt aus den Regeldefinitionen in ruleset einen neuen
            % Regelsatz für den Agenten und speichert diesen in
            % classifierset ab. Wird beim Erzeugen des Agenten aufgerufen
            % und danach bei jedem Wechsel in eine neue Hierarchieebene
            % zum Wechsel zum neuen Regelsatz.
            
            % classifiers = []; % Löschen eventuell vorhandener alter Daten
            for rule=1:size(ruleset,1)
                classifiers(rule) = classifier(ruleset(rule,:), uint8(rand*255));
            end % Erzeugung des Regelsatzes - für jede Regel eine Zufallszahl zwischen
            % 0 und 255, um genau mit 8-bit Gray code arbeiten zu
            % können
            
            agent.classifierset = classifiers;
            agent.numberofclassifiers = length(agent.classifierset);
            
            agent.classifierfitnessstring = strcat(agent.classifierset.fitnessstring);
        end
        
    end
    
end

