Home

Download

Funktionen Prozeduren

Delphi

Erstellen eines Schiebepuzzles


Download - Source als ZIP

Download - Executable (.exe)

Variablen


            
            
Form1: TForm1;
board: Array[1..4, 1..4] of TPanel;     //Spielfeld
randomString: Array[1..16] of Integer;  //Zufallszahlen -> benötigt in shuffle()
ranList: TStringList;                   //verfügbaren Zahlen -> benötigt in shuffle()
redPanel: TPanel;                       //Panel, für die TimerRed-Animation        
tri: Integer = 1;                       //tri = TimerRed-Index
tmi: Integer = 1;                       //tmi = TimerMove-Index
b1, b2: TPanel;                         //Block 1 und 2 (bzw. Feld) für die Verschiebung, 1=Vorher, 2=Nachher
dir: Integer;                           //Richtung des Zuges             
playtime: Integer;                      //SPielzeit in Sekunden
        

Funktionen


isSolved()

            
procedure isSolved;
var n, x, y: Integer;
begin
    n := 0;
    for y:=1 to 4 do
    begin
    for x:=1 to 4 do
    begin
        if y*x = 16 then
        begin
            if board[x, y].Tag = 0 then n:=n+1;
        end
        else
        begin
            if board[x, y].Tag = (y-1)*4+x then n:=n+1;
        end;
    end;
    end;

    if n >= 16 then
    begin
        Form1.TimerGame.Enabled := False;
        ShowMessage('Glückwunsch, du hast es in ' + Form1.LabelTimePlayed.Caption + ' geschafft!');
    end;
end;
        

Diese Funktion überprüft, ob das Puzzle gelöst ist. Dazu werden alle Felder des Spielfeldes durchlaufen und die Tag-Werte mit der Position verglichen, die sie haben sollten. Wenn ein Felder an der richtigen Position ist, wird die Variable n um eins erhöht. Wenn n 16 erreicht, also alle Felder die richtige Position haben, ist das Puzzle gelöst.


moveBlock()

            
procedure moveBlock(x, y, z: Integer);
begin
    b1 := board[x, y];

    case z of
        1: b2 := board[x-1, y];
        2: b2 := board[x, y-1];
        3: b2 := board[x+1, y];
        4: b2 := board[x, y+1];
    end;

    case z of
        1: board[x-1, y] := b1;
        2: board[x, y-1] := b1;
        3: board[x+1, y] := b1;
        4: board[x, y+1] := b1;
    end;

    board[x, y] := b2;
    dir := z;
    Form1.TimerMove.Enabled := True;
    isSolved;
end;
        

Diese Funktion bewegt ein Feld. Dazu werden vorerst beide Felder, das freie sowie das zu bewegende, in den Variablen b1 und b2 gespeichert. Anschließend wird das zu bewegende Feld an die Position des freien Feldes gesetzt und das freie Feld an die Position des zu bewegenden Feldes. Die Richtung wird in der Variable dir gespeichert, damit die Animation weiß, in welche Richtung das Feld bewegt werden soll. Gestartet wird die Animation mit Form1.TimerMove.Enabled := True;. Zum Schluss wird die Funktion isSolved() aufgerufen, um zu überprüfen, ob das Puzzle gelöst ist.


checkSides()

            
function checkSides(x, y: Integer): Integer;
begin
    //1 - left, 2 - top, 3 - right, 4- bottom
    //return 0 if no side is free
    Result:=0;

    //check left
    if x>1 then
    begin
        if board[x-1, y].Tag = 0 then Result := 1;
    end;

    //check top
    if y>1 then
    begin
        if board[x, y-1].Tag = 0 then Result := 2;
    end;

    //check right
    if x<4 then
    begin
        if board[x+1, y].Tag = 0 then Result := 3;
    end;

    //check bottom
    if y<4 then
    begin
        if board[x, y+1].Tag = 0 then Result := 4;
    end;

    if Result=0 then //safe-check to make sure animation is done
    begin
        if Form1.TimerRed.Enabled=False then
        begin
            redPanel := board[x, y];
            Form1.TimerRed.Enabled := True;
        end;
    end
    else moveBlock(x, y, Result);
end;
        

Diese Funktion überprüft, ob das Feld, welches die Kooridinaten x, y hat, ein freies Feld um sich hat. Die Kooridinaten werden hier als Parameter übergeben: checkSides(x, y: Integer): Integer. Das Ergebnis (Richtung des freien Feldes) wird in der Variable Result (Standardvariable zur Ausgabe eines Ergebnis in einer Funktion) gespeichert und zurückgegeben. Die Werte der Variable Result sind:

Wenn ein freies Feld gefunden wurde, wird die Funktion moveBlock(x, y, z: Integer) aufgerufen, um das Feld zu bewegen.


getInv()

            
function getInv(arr: array of Integer): Integer;
var i, j, inv: Integer;
begin
    inv := 0;
    for i:= 0 to 14 do
    begin
        for j := i+1 to 15 do
        begin
            if arr[i] < 16 then
            begin
                if arr[i] > arr[j] then inv := inv+1;
            end;
        end;
    end;
    Result := inv;
end;
        

Diese Funktion zählt die Inversionen. Dazu werden alle Zahlen miteinander verglichen und die Variable inv um eins erhöht, wenn die Zahl i größer als die Zahl j ist. Das Ergebnis wird in der Variable Result gespeichert und zurückgegeben.

Die For-Schleifen sind kompakt geschrieben, bspw. zählt die i-Schleife nur bis 14, da die letzte Zahl im Array ja nicht mit sich selbst verglichen werden muss. Die j-Schleife nutzt die Variable i als Startwert, da j immer um mind. 1 größer sein muss als i.

Simplicity is prerequisite for reliability.
- Edsger W. Dijkstra


findFree()

            
function findFree(arr: array of Integer): Integer;
var x, y, a, b: Integer;
    prevBoard: Array[1..4, 1..4] of Integer;
begin
    for y:=1 to 4 do
    begin
    for x:=1 to 4 do
    begin
        prevBoard[x, y] := arr[(y-1)*4+x-1];
    end;
    end;

    for b:=1 to 4 do
    begin
    for a:=1 to 4 do
    begin
        if prevBoard[a, b] = 16 then begin Result := (4-b)+1; Exit; end;
    end;
    end;
end;
        

Diese Funktion sucht das freie Feld im Array und gibt die Position des freien Feldes zurück.

Zu beachten:
In der Funktion shuffle() wird das Array vom Index 1-16 definiert, jedoch wurde das Array übergeben und durch Delphi um eine Stelle nach links gerückt. Das bedeutet, falls man beim Definieren eines array bei 1 anfängt und diese einer Funktion übergibt, rückt Delpi automatisch alle Elemente nach links, um die 0. Stelle aufzufüllen.

Vermutung und gefähliches Halbwissen:
Durch das automatisch Auffüllen hat Delpi auch anscheinend die Position 16, die nun eigentlich leer sein sollte mit einer anderen Zahl aufgefüllt, d.h. beim Index 16 ist nun eine weiter Zahl (wahrscheinlich die gleiche wie bei Index 1).


shuffle()

            
procedure shuffle;
var i, n: byte;
    x, y, ran, pos: Integer;
begin
    x:=1;
    y:=1;

    Form1.PanelGameBoard.DestroyComponents;

    ...

end;
        

Diese Funktion ist etwas größer, weshalb ich sie hier in einzelne Stücke aufteile.
Im ersten Teil werden alle Komponenten (Panles) des Spielfeldes entfernt, damit sie später neu erstellt werden können. Außerdem werden die Startkoordinaten (x, y) für das Spielfeld auf 1 gesetzt.

            
...
    repeat
        randomize();
        ranList := TStringList.Create;

        for n:=1 to 16 do randomArray[n] := 0;

        for n:=1 to 16 do
        begin
            ranList.Add(IntToStr(n));
        end;

        for n:=1 to 16 do
        begin
            if n=16 then pos:=1
            else pos := RandomRange(1, 18-n);

            ran := StrToInt(ranList[pos-1]);
            randomArray[n] := randomArray[n] + ran;
            ranList.Delete(pos-1);
        end;
    until isSolvable(randomArray);
...
        

In der repeat-Schleife wird eine Liste mit 16 Zufallszahlen erstellt. Dazu wird ein StringList mit den Zahlen 1-16 erstellt. Die Zahlen 1-16 werden dann in die Liste ranList geschrieben. Anschließend wird eine zufällige Zahl aus der Liste gezogen, aus der Liste gelöscht (dies Liste mache ich nur, um den Prozess der Zufallsgeneration zu entlasten und zu optimieren) und in das Array randomArray geschrieben. Die Schleife wird solange wiederholt, bis die Funktion isSolvable() true zurückgibt.

            
...
    for i:=1 to 16 do
    begin
        board[x, y] := TPanel.Create(Form1.PanelGameBoard);

        //create empty block
        if randomArray[i]=16 then
        begin
            with board[x, y] do
            begin
                Parent := Form1.PanelGameBoard;
                Width := 0;
                Height := 0;
                Left := 0;
                Top := 0;
                Tag := 0;
                Visible := False;
            end;
        end //end of If
...
        

Hier beginnt eine For-Schleife, die bis in den folgenden Code-Block geht.
Im diesem ersten Teil der Schleife wird geschaut, ob der aktuelle Index von randomArray[] gleich 16 ist. Wenn ja, wird ein leeres Panel erstellt, welches später das freie Feld darstellen soll.

            
...
        else //else of If
        begin
            //create 15 blocks
            with board[x, y] do
            begin
                Parent := Form1.PanelGameBoard;
                Width := 100;
                Height := 100;
                Left := x * 100 - 100;
                Top := y * 100 - 100;
                Tag := randomArray[i];
                Show;
                OnClick := Form1.blockClick;
                Caption := inttostr(randomArray[i]);
                BorderStyle := bsNone;
                Color := rgb(224, 224, 224);
            end;
        end; //end of If else

        if x > 3 then y:=y+1;
        if x > 3 then x:=1 else x:=x+1;

    end; //end of For
...
        

Wenn der aktuelle Index nicht 16 ist, wird ein Panel erstellt, welches später ein Feld darstellt.
mit with ... do kann man einem Objekt mehrere Eigenschaften gleichzeitig zuweisen.
Am Ende wird noch x und y erhöht, damit das nächste Panel an der richtigen Stelle erstellt wird.

Prozeduren


Form.Create

            
procedure TForm1.FormCreate(Sender: TObject);
begin
    shuffle;
end;
        

Beim Start des Programms wird lediglich die shuffle Funktion aufgerufen, um ein neues Spielfeld zu erstellen.


TimerRed

            
procedure TForm1.TimerRedTimer(Sender: TObject);
begin
    redPanel.Color := rgb(255, 0, 0);

    if tri < 8 then
    begin
        redPanel.Color :=  rgb(255, round((tri/8)*255), round((tri/8)*255));
        tri := tri+1;
    end

    else
    begin
        tri:=1;
        redPanel.Color := clBtnface;
        TimerRed.Enabled := false;
    end;
end;
        

Wenn jemand auf ein Feld klickt, welches keine freien Felder neben sich hat, wird dieser Timer aus der Funktion checkSides() aktiviert.
In diesem Timer wird lediglich jedes mal die Farbe des Panels auf Rot, bzw auf ein immer helleres Rot gesetzt, bis das Panel wieder seine Ursprungsfarbe hat. Sozusagen ist diser Timer nur für eine Animation zuständig. Am Ende der Schleife deaktiviert sich der Timer selbst und die Animation ist beendet.


TimerMove

            
procedure TForm1.TimerMoveTimer(Sender: TObject);
begin
    if tmi < 6 then
        begin
            case dir of
            1: b1.Left:=b1.Left-20;
            2: b1.Top:=b1.Top-20;
            3: b1.Left:=b1.Left+20;
            4: b1.Top:=b1.Top+20;
        end;
        tmi := tmi + 1;
    end

    else
    begin
        tmi := 1;
        TimerMove.Enabled := False;
    end;
end;
        

In diesem Timer wird in jedem Intervall (insgesamt 5 Intervalle) die Position des angeklickten Feldes Stück für Stück geändert, so dass eine Animation entsteht.


BitBtn1Click (Shuffle Button)

            
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
    shuffle;
    playtime := 0;
    TimerGame.Enabled := True;
end;
        

Wenn der Shuffle Knopf gedrückt wird, wird Spielfeld zurückgesetzt und neu generiert, außerdem wird der Timer neugestartet.


TimerGame

            
procedure TForm1.TimerGameTimer(Sender: TObject);
var m, s: String;
begin
    m := IntToStr(playtime div 60);
    s := IntToStr(playtime mod 60);

    if StrToInt(m) < 10 then m:='0'+m;
    if StrToInt(s) < 10 then s:='0'+s;

    playtime := playtime + 1;
    LabelTimePlayed.Caption := m + ':' + s;
end;
        

In diesem Timer wird die Zeit hochgezählt und im LabelTimePlayed ausgegeben. Um eine gut leserliche Form zu erstellen werden die gezählten Sekunden in Minuten und Sekunden umgewandelt. Um es noch schönder zu machen werden vor jeder Zahl unter 10 eine 0 davor geschrieben um eine gleichbleibende Form beizubehalten.