Springerproblem

Hier mal wieder eine Aufgabe von Indi:
Es geht darum ein Programm zu schreiben, das den/einen kürzesten Weg errechnet den ein Springer braucht um jedes Feld eines Schachbrettes zu betreten.
Der Springer ist natürlich allein auf dem Schachbrett und kann sich nur nach den beim Schach für den Springer üblichen Regeln bewegen (zwei Felder in eine Richtung und ein weiteres Feld senkrecht dazu, also zwei Felder vor/zurück und ein Feld links/rechts oder umgekehrt).
Das Programm soll den kompletten Weg ausgeben, den der Springer zurücklegt.

PS: afaik gibt es immer mindestens eine Mögichkeit die Aufgabe so zu lösen, dass kein Feld doppelt betreten wird.

viel Spaß damit
mfg
Nornagest
 
Ich habe mich mal dran gemacht und eine erste Version des Programmes in Pascal geschrieben.

Das Programm ist jedoch noch nicht voll funktionsfähig und nicht optimiert: Erstens findet er nur einen Weg zum Ziel, nicht den kürzesten und zweitens kann es passieren, dass er in einer Endlosschleife von Zügen verbleibt.

Code:
Code:
program Springer;
uses crt;

type pos=array[1..2] of byte;
var  start,tmp,ziel:pos;
     eingabe_ok:boolean;
     i:longint;


function move(position:pos):pos;   { Bewegen des Springers }
var new_pos:pos;
begin

  { Zug 01 : 2 hoch, 1 links}
  if (position[1]>1) and (position[2]<7) then begin new_pos[1]:=position[1]-1; new_pos[2]:=position[2]+2; end;

  { Zug 02 : 2 hoch, 1 rechts}
  if (position[1]<8) and (position[2]<7) then begin new_pos[1]:=position[1]+1; new_pos[2]:=position[2]+2; end;

  { Zug 03 : 1 hoch, 2 rechts }
  if (position[1]<7) and (position[2]<8) then begin new_pos[1]:=position[1]+2; new_pos[2]:=position[2]+1; end;

  { Zug 04 : 1 runter, 2 rechts }
  if (position[1]<7) and (position[2]>1) then begin new_pos[1]:=position[1]+2; new_pos[2]:=position[2]-1; end;

  { Zug 05 : 2 runter, 1 rechts }
  if (position[1]<8) and (position[2]>2) then begin new_pos[1]:=position[1]+1; new_pos[2]:=position[2]-2; end;

  { Zug 06 : 2 runter, 1 links }
  if (position[1]>1) and (position[2]>2) then begin new_pos[1]:=position[1]-1; new_pos[2]:=position[2]-2; end;

  { Zug 07 : 1 runter, 2 links }
  if (position[1]>2) and (position[2]>1) then begin new_pos[1]:=position[1]-2; new_pos[2]:=position[2]-1; end;

  { Zug 08 : 1 hoch, 2 links }
  if (position[1]>2) and (position[2]<8) then begin new_pos[1]:=position[1]-2; new_pos[2]:=position[2]+1; end;

  move:=new_pos;
end;


begin { Hauptprogramm }
  clrscr;

  start[1]:=0;
  start[2]:=0;
  ziel[1]:=0;
  ziel[2]:=0;


  repeat

    { Annehmen, dass die folgenden Eingaben g?ltig sind }
    eingabe_ok:=TRUE;

    { Eingabe der Startposition des Springers }
    write('Startposition (horizontal): ');   readln(start[1]);
    write('Startposition (vertikal):   ');   readln(start[2]);

    { Eingabe der Zielposition des Springers }
    write('Zielposition (horizontal):  ');   readln(ziel[1]);
    write('Zielposition (vertikal):    ');   readln(ziel[2]);

    { P?fen, ob Eingabe m?glich und ggf. Fehlermeldung}
    if (start[1]<1) or (start[1]>8) then begin writeln('Passen Sie die horiz. Startpos. an!'); eingabe_ok:=FALSE; end;
    if (start[2]<1) or (start[2]>8) then begin writeln('Passen Sie die vert. Startpos. an!'); eingabe_ok:=FALSE; end;
    if (ziel[1]<1) or (ziel[1]>8) then begin writeln('Passen Sie die horiz. Zielpos. an!'); eingabe_ok:=FALSE; end;
    if (ziel[2]<1) or (ziel[1]>8) then begin writeln('Passen Sie die vert. Zielpos. an!'); eingabe_ok:=FALSE; end;
    if eingabe_ok=FALSE then writeln('Die Positionen m?ssen Werte zwischen 1 und 8 annehmen.');


  until eingabe_ok=TRUE;


  { Bewegen }
  i:=0;
  tmp:=start;
  repeat
    tmp:=move(tmp);
    i:=i+1;
    writeln('Zug nach:  H: ',tmp[1],' V: ',tmp[2],'  Zug Nr.: ',i);
  until (tmp[1]=ziel[1]) and (tmp[2]=ziel[2]);

  writeln('Ziel in ',i,' Z?gen erreicht!');

end.
http://cxmedia.ath.cx/div/springer.pas

Binary:
http://cxmedia.ath.cx/div/springer.exe

Erklärung:

0. Prinzip
Das Schachbrett wird durch ein Array der Dimension 2 dargestellt, also quasi zwei Werte pro Variable. Die erste steht für die horizontale Position des Springers, die zweite für die vertikale.
Zum Bewegen wird eine Funktion move aufgerufen, die unter Beachtung der Grenzen des Brettes und den für einen Springer zulässigen Zugmöglichkeiten (genau acht bei "freier Bahn") die neue Position der Figur ausgibt.
Das Problem ist im Moment noch das, dass die Funktion move einfach der Reihe nach die verschiedenen Zugrichtungen durchprobiert, und immer die erst mögliche nimmt. Somit ist das ganze eher ein irres Auf-Dem-Brett-Rumlaufen mit der Hoffnung, irgendwann das Ziel zu treffen, wobei dabei eine Endlosschleife entstehen kann. :(
Wenn man Start 2,3 und Ziel 4,2 wählt gibt es einen sinnvollen Programverlauf. :)

1. Eingabe
Das Programm fragt nach der Start- und Zielposition und überprüft die Gültigkeit (Die Werte dürfen nur einen Wert zwischen 1 und 8 annehmen, da die Figur sonst ausserhalb des Brettes stehen würde :p ).

2. Bewegen
Mittels der Funktion move wird der Springer verschoben. Die neue Position wird immer in der Variablen tmp gespeicht, die zu Beginn mit den Werten von start (der Startposition) gefüttert wird.
Das passiert in einer Schleife, sodass immer weiter gezogen wird.
Die Schleife wird dann verlassen, wenn tmp=ziel ist, also die Figur sich am Ziel befindet.
 
@Nornagest:

afaik gibt es immer mindestens eine Mögichkeit die Aufgabe so zu lösen, dass kein Feld doppelt betreten wird

Ich hab meinen Code auf diese Vorgabe angepasst. Theoretisch sollte das Programm funktionieren, ob es wirklich läuft bzw. es eine Lösung gibt, kann ich derzeit noch nicht sagen, da das Programm gerade rechnet.


Code:
// Header
#include <stdio.h>

// Funktionen
int way_possible(int x, int y);
int check_brett();

// Konstanten
const int anzahl = 64; // Anzahl der max. Züge

// Variabeln
int brett[8][8]; // Schachbrett
int a[anzahl]; // aktueller Zug
int x_new, y_new, x, y;
int g;

main()
{
	for (g=0; g<anzahl; g++)
		a[g] = 1;

	while (1)
	{
		// SchachBrett initialisieren
		for (x=0; x < 8; x++)
		{
			for (y=0; y < 8; y++)
				brett[x][y] = 0;
		}

		// Zug-Generator
		for (g=anzahl-1; g>=0; g--)
		{
			if (a[g] == 8)
			{
				a[g] = 1;
				if (a[g-1] == anzahl-1)
					a[g-1] = 1;
			}
			else
			{
				a[g]++;
				break;
			}
		}
			
		// Ausgangsposition des Springers festlegen
		brett[1][0] = 1;
		x=1;
		y=0;

		// den aktuellen Zug durchführen
		for (g=0; g < anzahl; g++)
		{
			// die richtungen der einzelnen zug-variationen
			if (a[g] == 1)
			{
				x_new = x + 1;
				y_new = y + 2;
			}
			else if (a[g] == 2)
			{
				x_new = x + 2;
				y_new = y + 1;
			}
			else if (a[g] == 3)
			{
				x_new = x + 2;
				y_new = y - 1;
			}
			else if (a[g] == 4)
			{
				x_new = x + 1;
				y_new = y - 2;
			}
			else if (a[g] == 5)
			{
				x_new = x - 1;
				y_new = y - 2;
			}
			else if (a[g] == 6)
			{
				x_new = x - 2;
				y_new = y - 1;
			}
			else if (a[g] == 7)
			{
				x_new = x - 2;
				y_new = y + 1;
			}
			else if (a[g] == 8)
			{
				x_new = x - 1;
				y_new = y + 2;
			}

			// prüfen ob Zug auf dieses Feld bereits durchgeführt wurde
			// bzw. ob Zug auf reales Feld trifft
			// != Zug abbrechen	
			if (way_possible(x_new, y_new) == 1 && brett[x_new][y_new] < 1) // Zug war möglich
			{
				x = x_new;
				y = y_new;
				brett[x][y] = 1; // Feld markieren
				// Brett auf Vollständigkeit prüfen
				if (check_brett() == 1)
				{
					printf("Eine Lösung wurde gefunden:\n");
					for (int z=0; z <= g; z++)
						printf("%d", a[z]);
					printf("\n\n");
				}
			}
			else // Zug war nicht möglich
			{
				// alle restlichen Züge die mit diesem beginnen,
				// können daher gelöscht werden
				if (a[g] < 8)
					a[g]++;
				else
				{
					a[g] = 1;
					int f=g;
					while(1)
					{
						f--;
						if (a[f] < 8)
						{
							a[f]++;
							break;
						}
						else
							a[f]=1;
					}
				}
				for (int j=g+1;j<anzahl;j++)
					a[j] = 1;
				break;
			}
		}

		/*
		// aktuelle Zeile ausgeben
		for (g=0; g < anzahl; g++)
			printf("%d", a[g]);
		printf("\n");
		*/
		
		// Ende der while-schleife prüfen
		if (a[0] == 8)
			break;
	}
	return 0;
}

// prüft ob Zug möglich ist
way_possible(int x, int y)
{
	if (x >= 0 && x < 8 && y >= 0 && y < 8)
		return 1;
	else
		return 0;
}

// prüft ob alle Felder des SchachBrettes bereits belegt wurden
check_brett()
{
	for (int a=0; a < 8; a++)
	{
		for(int b=0; b < 8; b++)
		{
			if (brett[a][b] == 0)
				return 0; // Nicht alle Felder belegt
		}
	}
	return 1; // Alle Felder belegt
}

Erläuterungen:
Also für alle Neueinsteiger hier, die verwendete Programmiersprache ist C. Der Code entstand unter MS VC++ und läuft dort recht gut.

Arbeitsweise:
Der ganze Code ist eigentlich relativ simple zu verstehen. Der Hauptbestandteil ist die while-schleife in main(). Ein Durchlauf der while-schleife repräsentiert eine Anzahl von Zugmöglichkeiten (festgelegt durch die Konstante anzahl). Hier sind es 64 (wie ich eben bemerkte, hätten 63 gerreicht) Züge. Damit es nicht zu einer Endlos-Schleife kommt, müssen diese Zugmöglichkeiten bei jedem Durchlauf abgeändert werden, die aktuellen Werte findet man im Array a[anzahl]. Dabei werden an jeder Position Werte zwischen 1 und 8 gespeichert (je nach Richtung in die der Springer "hüpfen" soll). Die for-Schleife ab Zeile Nr. 53 prüft nun bei jedem Durchlauf der while-Schleife die Möglichkeit dieser 64 Züge. Die if-Abfrage in Zeile 100 dürfte verständlich sein, zu erläutern wäre aber der else-Zweig in Zeile 114. Bei 64 Zügen in jeweils 8 Richtungen ergäbe es eine sehr hohe Anzahl wenn jeder Zug tatsächlich möglich werde. Ingesamt 8^64. Wir wissen, dass 2^8 256 ergibt, daher ist ersteres Ergebnis weit über unserem Vorstellungsvermögen. Würde man all diese Möglichkeiten in einem Diagramm aufzeigen würde sich eine nette Baumstruktur ergeben mit zig Ästen. Sollte nun ein Zug zb. a[20] mit dem Wert 1 belegt sein und sich der Springer in y=7 befinden, so wäre dieser Zug nicht möglich. In diesem Fall kommt das Programm in diese else-Schleife. Da nach diesem Zug aber noch 8^44 Züge vorhanden wären, wir aber nicht wollen, dass diese alle durchsucht werden, da wir ohnehin wissen, dass a[20] ins Nichts führt, brechen wir die for-Schleife an dieser Stelle ab und manipulieren die aktuelle Variante der 64 Züge in a[anzahl] so, dass im restlichen Programmablauf diese nicht durchlaufen werden. Dies spart im Endeffekt eine Menge Rechenzeit (auch wenn es mit dieser Funktion trotzdem noch lang dauert). :))

Bei Fragen einfach melden.
 
Ich muss zugeben, ich bin leider noch nicht dazu gekommen mich mal dran zu setzen, aber ich denke es sollte sich rekursiv ganz gut lösen lassen (falls nicht der gesamte Speicher aufgebraucht wird ;) )

Man müsste bei einem belibigen Feld anfangen und dann mit einer Funktion rekursiv alle Möglichkkeiten durchprobieren, wobei man jedes Feld auf das der Springer hüpft als betreten markiert und so sicherstellt, dass kein Feld doppelt betreten wird, wenn er in eine Sackgasse kommt (also nicht weiter ziehen kann) sollte er so lange Züge zurücknehmen (und natürlich die Felder wieder freigeben, also als nicht betreten markieren), bis er wieder eine Möglichkeit zum fortfahren findet.
 
ich befinde mich zwar in den anfängen, aber hätte einen vorschlag an euch zu machen...lässt es sich nciht graphisch machen? so, das jedes betrettene feld verschwinden würde??damit das auge auch was von hätte... ;)
 
@anakadai:
Das Problem daran ist nur, dass pro Sekunde soviele Zugmöglichkeiten berechnet werden, dass du wohl kaum mehr mit dem Schaun mitkommen wirst. :))
 
Die bessere Möglichkeit für die grafische Lösung wäre wohl zuerst die Lösung zu errechnen und dann das Ergebnis auszugeben (also nicht alle Versuche, sondern nur die Züge zu endgültigen Lösung)
 
ähm..genau :)
meinte schon das fertige ergebniss...sosnt würd, glaube ich zumindestens, der rechner arge probs kriegen, oder??
 
Der Rechner sollte keine Probleme kriegen, immerhin macht er dann das gleiche wie vorher. Allerdings muss er zwischendurch noch ne Ausgabe machen, was das ganze natürlich verlangsam, allerdings hat der Rechner Zeit nur der User will sich das vielleicht nicht alles ansehen. ;)
 
naja, dann könnte ja der coder eine auswhl ermöglichen ;)
wer weiss, wenn mir langweilig wäre, würde ich mir sowas vllt auch reinziehen :)

ist so 'n problem schwer zu realisieren?
habe gerade vba und langweile mich da oft, könnte ja damit mal anfangen :)
wenn es mir mal einer erklären würde..so bischen theorie und so :)
 
hi,
hab noch dazu eine idee...
man könnte dies dann auch als spiel realisieren, oder?
mit highscore-liste, etc...man lässt einfach den zocker die bewegung machen... ;)
 
Ähm ... da es genau 64 Felder gibt und jedes Feld genau einmal betreten wird ist die Weglänge immer 64 ... was soll da bitte der "kürzeste Weg"?

Lösung: Feste Reihenfolge aller von einem Startfeld erreichbaren Zielfelder festlegen (z.B. im Uhrzeigersinn), dann rekursives Backtracking.

Interessant wären allerdings Aufwandsoptimierungen, so dass man z.B. die Ansichten des Bretts aus 4 Richtungen als äquivalent betrachtet und nicht neu berechnet, außerdem kann man das Brett in viertel zerlegen und blockweise arbeiten ....

Greets, Ziri
 
Ähm ... da es genau 64 Felder gibt und jedes Feld genau einmal betreten wird ist die Weglänge immer 64 ... was soll da bitte der "kürzeste Weg"?

Einfach mal Aufgabe lesen:

Da steht nicht: Finde den Weg, der jedes Feld nur einmal benutzt. Sondern den kürzesten Weg. Soll heissen: Finde den Weg, der alle Felder UND die geringste Zugzahl hat.

Daß es einen Weg gibt, der jedes Feld nur einmal benutzt, ist keine Gegebenheit, die aus der Aufgabe hervorgeht, sondern eine, die als Tipp angegeben wurde, und das sogar noch mit einem AFAIK versehen.
 
Original von Damien
Da steht nicht: Finde den Weg, der jedes Feld nur einmal benutzt. Sondern den kürzesten Weg.
Oh stimmt ja. Allerdings ist das bei diesem Problem synonym, auf dem kürzesten Weg wird in der Tat jedes Feld nur ein mal besucht. Musste ich vor längerer Zeit mal experimentell beweisen ;)

Greets, Ziri

PS: Das "Springerproblem" ist normalerweise auch so forumliert, dass jedes Feld _genau_ einmal besucht werden soll ...
 
auf dem kürzesten Weg wird in der Tat jedes Feld nur ein mal besucht

Aber nur, wenn eben dieser Weg auch möglich ist, was sich nicht aus der Aufgabenstellung ergibt. Deswegen bleibt die allgemeine Formulierung. Mal abgesehen davon würde die Aufgabe ja durch die Voraussetzung, daß jedes Feld nur einmal besucht werden darf, unmoralisch einfach.
 
Zurück
Oben