% Matlab Skript: Mlab_Projekt.m
%
% Ersteller:    RB - 
%               MM - 
%
% Zweck:
% Ziel des Projektes ist es ein kleines Puzzle- Spiel namens 2048 zu entwickeln. 
% Das Ziel des Spiels ist das Bilden einer Kachel mit der Zahl 2048 zu erhalten. 
% Mit den Pfeiltasten (oben, unten, rechts, links) bewegt der Spieler die Kacheln auf dem Spielfeld, 
% wobei sich bei jedem Zug alle Kacheln so weit wie möglich bewegen, 
% als ob sie auf dem in die jeweilige Richtung gekippten Spielfeld rutschen würden. 
% Stoßen dabei zwei Kacheln mit der gleichen Zahl an-einander, 
% verschmelzen sie zu einer Kachel mit der Summe der beiden Kacheln.
%
% Changelog:
% Version 0.1 2017-05-18, RB Erstellen erste version GUI, Spielfeld, Tite und Score
% Version 0.2 2017-05-25, RB Algorithmus für Einfärbung der Kacheln im Spielfeld.
% Version 0.5 2015-05-26, RB Algorithmus für zusammenzählen, Spielende erkennung.
% Version 0.6 2015-05-27, MM Abänderung des GUI's für Speichern, Laden und reset
% Version 0.7 2015-06-01, MM Implementation der Zeitmessung
% Version 0.8 2015-06-01, MM Highscore implementiert 
% Version 1.0 2015-06-06, MM Highscore & spiel speichern/laden implementiert
%
% Verwendete Variablen (nur globale Funktionsvariabeln verwendet)
% data    -- Daten des Spielfelds
% score   -- Aktuelle Punktzahl
% thetime -- Aktuelle Zeit
% addtime -- Wenn geladen wird, muss die "alte" Zeit hinzugefügt werden

%--------------------------------------------------------------------------
% Automatisch generierte Funktion für GUI
%--------------------------------------------------------------------------
function varargout = Mlab_Projekt(varargin)
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @Mlab_Projekt_OpeningFcn, ...
                   'gui_OutputFcn',  @Mlab_Projekt_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end


%--------------------------------------------------------------------------
% Diese Funktion wird aufgerufen bevor das Fenster angezeigt wird.
%--------------------------------------------------------------------------
function Mlab_Projekt_OpeningFcn(hObject, eventdata, handles, varargin)
handles.output = hObject;
guidata(hObject, handles);
% Startet Spieltimer mit einer Periodendauer von einer Sekunde.
handles.t = timer('StartDelay',1,'TimerFcn',{@timer_callback, handles}); 
set(handles.t, 'ExecutionMode', 'fixedRate');
start(handles.t); 


%--------------------------------------------------------------------------
% Wird beim anzeigen des Fensters aufgerufen. Erstellt ein leeres Spielfeld
% und startet den Spieltimer.
%--------------------------------------------------------------------------
function varargout = Mlab_Projekt_OutputFcn(hObject, eventdata, handles) 
varargout{1} = handles.output;
global data;
global score;
global addtime;
data = zeros(4);
zweinull48refresh();
redraw_game(handles);
score = 0;
addtime=0;
tic; %startet den Timer

%--------------------------------------------------------------------------
% Diese Funktion führt den eigentlichen Zug aus. Liegen zwei Kacheln
% nebeneinander bzw. übereinander mit dem gleichen Zahlenwert und ist die
% Zugrichtung korrekt so werden sie zusammenaddiert. Alle anderen Kacheln
% werden soweit wie möglich in die Zugrichtung verschoben. Dazu wird um das
% Feld eine Reihe von Nan Zahlenwerten geschrieben und dann von links nach
% rechts bzw von unten nach oben oder jeweils anders herum abgearbeitet. 
% Der zusätzliche Rand dient dabei einem weiteren Vergleichsbuffer, welcher
% natürlich nie erfüllt wird. Somit können aber alle Kacheln bis zu diesem
% Rand eingerückt werden.
%--------------------------------------------------------------------------
function movenew(k)
    global score;
    global data;

    first = data;
    g = NaN(6,6);
    g(2:5,2:5) = data;
    data = g;
 
    if k == 's'
        % Nach unten
        for r = 4:-1:2
            % Auf Reihe 3 beginnen und nach oben arbeiten
            for c = 2:5
                % Wenn momentane Position nicht 0 ist.
                if data(r,c) ~= 0
                    p = r;
                    % Wenn die Kachel darunter 0 oder denselben wert hat
                    while data(r+1,c) == 0 || data(r,c) == data(r+1,c)
                        if data(r,c) == data(r+1,c)
                            % Wenn die beiden Kacheln den selben Wert haben
                            % werden sie zueinander addiert.
                            data(r+1,c) = data(r,c).*2;
                            score = score + data(r, c);
                            data(r,c) = 0;
                            break
                        elseif data(r+1,c) == 0
                            % Wenn die untere Kachel den Wert 0 enthält
                            % kann die jetzige Kachel verschoben werden.
                            data(r+1,c) = data(r,c);
                            data(r,c) = 0;
                            r = r+1;
                        end
                    end
                    r = p;
                end
            end
        end
    elseif k == 'a'
        % Nach links
        for r = 2:5
            for c = 3:5
                if data(r,c) ~= 0
                    p = c;
                    while data(r,c-1) == 0 || data(r,c) == data(r,c-1)
                        if data(r,c) == data(r,c-1)
                            data(r,c-1) = data(r,c).*2;
                            score = score + data(r, c);
                            data(r,c) = 0;
                            break
                        elseif data(r,c-1) == 0
                            data(r,c-1) = data(r,c);
                            data(r,c) = 0;
                            c = c-1;
                        end
                    end
                    c = p;
                end
            end
        end
    elseif k == 'd'
        % Nach Rechts
        for r = 2:5
            for c = 4:-1:2
                if data(r,c) ~= 0
                    p = c;
                    while data(r,c+1) == 0 || data(r,c) == data(r,c+1)
                        if data(r,c) == data(r,c+1)
                            data(r,c+1) = data(r,c).*2;
                            score = score + data(r, c);
                            data(r,c) = 0;
                            break
                        elseif data(r,c+1) == 0
                            data(r,c+1) = data(r,c);
                            data(r,c) = 0;
                            c = c+1;
                        end
                    end
                    c = p;
                end
            end
        end
    elseif k == 'w'
        % Nach oben
        for r = 3:5
            for c = 2:5
                if data(r,c) ~= 0
                    p = r;
                    while data(r-1,c) == 0 || data(r,c) == data(r-1,c)
                        if data(r,c) == data(r-1,c)
                            data(r-1,c) = data(r,c).*2;
                            score = score +data(r, c);
                            data(r,c) = 0;
                            break
                        elseif data(r-1,c) == 0
                            data(r-1,c) = data(r,c);
                            data(r,c) = 0;
                            r = r-1;
                        end
                    end
                    r = p;
                end
            end
        end
    end
    % Rand wieder wegnehmen.
    data = data(2:5,2:5);
    %Wenn Daten geändert wurden, neue Zahl einfügen. Ansonsten bewirkte der
    %Zug nichts und ist quasi ungültig.
    if ~isequal(data,first)
        zweinull48refresh();
    end
 
%--------------------------------------------------------------------------
% Erweitert das Spielfeld um eine 2 oder 4 an einer zufälligen Position
%--------------------------------------------------------------------------
function zweinull48refresh()
    global data;
    tempx = round(rand*3+1);
    tempy = round(rand*3+1);
    while data(tempx, tempy) ~= 0
        tempx = round(rand*3+1);
        tempy = round(rand*3+1);
    end
    if round(rand)*1000 <= 950
        data(tempx, tempy) = 2;
    else
        data(tempx, tempy) = 4;
    end
    
    
%--------------------------------------------------------------------------
% Überprüft ob noch Spielzüge möglich sind.
%--------------------------------------------------------------------------
function [res]=checkGameEnded()
    global data;
    global score;
    global thetime;
    res = 0;
    if size(find(data==0)) == [0 1]    
        olddata = data;
        oldscore = score;
        movenew('w');
        movenew('a');
        movenew('s');
        movenew('d');
        if olddata == data
            h = msgbox(strcat('Sie haben verloren. Ihr Score ist: ', num2str(score),' mit einer Zeit von: ',num2str(thetime),' Sekunden.'));                
            res = 1;
            highscore();
        else
            data = olddata;
            score = oldscore;
        end
    end
    if any( data(:)==2048 )  
        h = msgbox(strcat('Gratulation! Sie haben gewonnen. Ihr Score ist: ', num2str(score)));

        res = 1;
        highscore();
    end
        
%--------------------------------------------------------------------------
%Fügt einen neuen Highscore in die Liste ein, falls einer gespielt wurde.
%--------------------------------------------------------------------------
function highscore()
        global score;
        global thetime;
        % Folgender Code folgt einem Konzept aus
        % Norbert Hofmann, Unterrichtsfolie B1-2
        %Highscore Liste lesen
        fid=fopen('Highscore.txt','r');
        fgetl(fid);                                  % kopfzeile überspringen
        hscore = fscanf(fid, '%2d %4d %4d', [3 10]); % Platzierung; Zeit; Score;
        fclose(fid);
        hscore = hscore';

        %Matrix vergrössern auf 10x3
        [m,n]=size(hscore);
        zeromatrix=zeros(10-m,3);
        hscore=vertcat(hscore,zeromatrix)                                

        
        %check ob neuer Highscore
        if score>hscore(10,3) %falls in Top10, letzter Platz ersetzen.
           hscore(10,2)=thetime;
           hscore(10,3)=score;
        end

        % Folgender Code folgt einem Konzept aus
        % http://www.gomatlab.de/matrix-nach-einer-bestimmten-zeile-sortieren-t31214.html
        %sortiert die Liste
        hscore=sortrows(hscore,-3); %sortiert nach Scores, absteigend        

        %rewrites placements #:
        for k=1:10
            hscore(k,1)=k;
        end

        %zurückschreiben
        hscore = hscore'; %transponiert da sonst falsch geschrieben.
        fid=fopen('Highscore.txt','w');
        fprintf(fid,'%2s %4s %5s\n','#','time','score');
        fprintf(fid,'%2d %4d %4d\n', hscore); % Platzierung; Zeit; Score;
        fclose(fid);
            
            
            
            
%--------------------------------------------------------------------------
% Gibt für einen erreichten Zahlenwert die entsprechende Kachelfarbe zurück
%--------------------------------------------------------------------------
function [color]=getColorByNumber(number)
    color = [1 1 1];
    if number == 0
        color = [1 1 1];
    end
    if number == 2
        color = [1 1 0];
    end
    if number == 4
        color = [1 0.5 0];
    end
    if number == 8
        color = [1 0.5 0.5];
    end
    if number == 16
        color = [0.5 0.5 0.5];
    end   
    if number == 32
        color = [0.25 0.5 1]; 
    end  
    if number == 64
        color = [1 0.5 0.75]; 
    end  
    if number == 128
        color = [0.5 1 0.5];  
    end 
    if number == 256
        color = [0.75 0.75 0.75]; 
    end  
    if number == 512
        color = [0.75 0.5 0.75];  
    end 
    if number == 1024
        color = [0.75 0.5 1];  
    end
    if number == 2048
        color = [0 0 0];    
    end
            


%--------------------------------------------------------------------------
% Wird ausgeführt wenn der Speichern-Button gedrückt wird und schreibt den
% aktuellen Spielstand in eine Textdatei.
%--------------------------------------------------------------------------
function save_Callback(hObject, eventdata, handles)
% hObject    handle to save (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
    global data;
    global score;
    global thetime;
    % Folgender Code folgt einem Konzept aus
    % Norbert Hofmann, Unterrichtsfolie B1-2
    fid=fopen('Savefile.txt','w');
    fprintf(fid,'%4d %4d %4d %4d\n',data); %
    fprintf(fid,'%4d\n',score); %score
    fprintf(fid,'%4d\n',thetime);   %zeit
    fclose(fid);

%--------------------------------------------------------------------------
% Lädt den gespeicherten Spielstand in das aktuelle Fenster 
%--------------------------------------------------------------------------
function load_Callback(hObject, eventdata, handles)
    global score;
    global data;
    global addtime;
% hObject    handle to load (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
    tic;
    % Folgender Code folgt einem Konzept aus
    % Norbert Hofmann, Unterrichtsfolie B1-2
    fid=fopen('Savefile.txt','r');
    data=fscanf(fid,'%4d %4d %4d %4d',[4 4]);
	score=fscanf(fid,'%4d',1);
	addtime=fscanf(fid,'%4d',1);
    fclose(fid);
    redraw_game(handles);
    
%--------------------------------------------------------------------------
% Setzt das Spielfeld zurück. Neues Spiel
%--------------------------------------------------------------------------
function reset_Callback(hObject, eventdata, handles)
    global data;
    global score;
    global addtime;
    score = 0;
    tic; %Setzt den Timer zurück
    addtime=0;
    data=zeros(4);%Setzt das Board zurück
    zweinull48refresh();
    redraw_game(handles);
    set(handles.highscore,'Visible','off');
%clear data
%


%--------------------------------------------------------------------------
% Wird aufgerufen wann immer eine Tastatureingabe erfolgt
%--------------------------------------------------------------------------
function figure1_KeyPressFcn(hObject, eventdata, handles)  
    global score;
      
    set(handles.txtScore,'String',score); 
    if checkGameEnded() == 1
        % Wird nach Beenden des Spiels ausgeführt
        % Folgender Code folgt einem Konzept aus
        % Norbert Hofmann, Unterrichtsfolie B1-2
        fid=fopen('Highscore.txt','r');
            kopf=fgetl(fid);                                  %kopfzeile überspringen
            hscore = fscanf(fid, '%2d %4d %4d', [3 10]);      % Platzierung; Zeit; Score;
            fclose(fid);
            hscore=hscore';
        %delete Savegame
        fid=fopen('Savefile.txt','w');
        fclose(fid);
        % Folgender Code folgt einem Konzept aus https://ch.mathworks.com/matlabcentral/newsreader/view_thread/63633
        set(handles.highscore,'String',{'High score:';kopf;num2str(hscore)});                                               
        set(handles.highscore,'Visible','on');
        
    else
        k = eventdata.Key;
        if (k == 'w' | k == 's' | k == 'a' | k == 'd')  
            movenew(k);   
        end
    end
    redraw_game(handles);

%--------------------------------------------------------------------------
% Erneuert die angezeigten Zahlen und Farben der Kacheln und den Score
%--------------------------------------------------------------------------
function redraw_game(handles)
    global data;
    global score;
    set(handles.txtScore,'String',score); 
    set(handles.pushbutton1,'String',data(1, 1));
    set(handles.pushbutton1,'BackGroundColor',getColorByNumber(data(1, 1)));
    set(handles.pushbutton2,'String',data(1, 2)); 
    set(handles.pushbutton2,'BackGroundColor',getColorByNumber(data(1, 2)));
    set(handles.pushbutton3,'String',data(1, 3)); 
    set(handles.pushbutton3,'BackGroundColor',getColorByNumber(data(1, 3)));
    set(handles.pushbutton4,'String',data(1, 4)); 
    set(handles.pushbutton4,'BackGroundColor',getColorByNumber(data(1, 4)));
    
    set(handles.pushbutton5,'String',data(2, 1)); 
    set(handles.pushbutton5,'BackGroundColor',getColorByNumber(data(2, 1)));
    set(handles.pushbutton6,'String',data(2, 2)); 
    set(handles.pushbutton6,'BackGroundColor',getColorByNumber(data(2, 2)));
    set(handles.pushbutton7,'String',data(2, 3)); 
    set(handles.pushbutton7,'BackGroundColor',getColorByNumber(data(2, 3)));
    set(handles.pushbutton8,'String',data(2, 4)); 
    set(handles.pushbutton8,'BackGroundColor',getColorByNumber(data(2, 4)));
    
    set(handles.pushbutton9,'String',data(3, 1)); 
    set(handles.pushbutton9,'BackGroundColor',getColorByNumber(data(3, 1)));
    set(handles.pushbutton10,'String',data(3, 2)); 
    set(handles.pushbutton10,'BackGroundColor',getColorByNumber(data(3, 2)));
    set(handles.pushbutton11,'String',data(3, 3)); 
    set(handles.pushbutton11,'BackGroundColor',getColorByNumber(data(3, 3)));
    set(handles.pushbutton12,'String',data(3, 4)); 
    set(handles.pushbutton12,'BackGroundColor',getColorByNumber(data(3, 4)));
    
    set(handles.pushbutton13,'String',data(4, 1)); 
    set(handles.pushbutton13,'BackGroundColor',getColorByNumber(data(4, 1)));
    set(handles.pushbutton14,'String',data(4, 2)); 
    set(handles.pushbutton14,'BackGroundColor',getColorByNumber(data(4, 2)));
    set(handles.pushbutton15,'String',data(4, 3)); 
    set(handles.pushbutton15,'BackGroundColor',getColorByNumber(data(4, 3)));
    set(handles.pushbutton16,'String',data(4, 4)); 
    set(handles.pushbutton16,'BackGroundColor',getColorByNumber(data(4, 4)));

%--------------------------------------------------------------------------
% Wird jede Sekunde aufgerufen. Erneuert die angezeigte Zeit
%--------------------------------------------------------------------------
function timer_callback(obj, event, handles) 
    try
    global thetime;   
    global addtime;
    thetime=floor(toc)+addtime;
    set(handles.txt_time,'String',thetime);
    catch
    end
