Hallo nochmal,
Aha! Das war wohl ein Missverständnis ;-) Du hast ja doch nur Einträge in deiner "world"-Tabelle, wo sich entweder ein Planet oder ein Schiff befindet.
Denn davon ist meine erste Lösung _nicht_ ausgegangen - klar dass sie dann für Dich nicht funktioniert.
Du hast ja nun keine Rows in der Tabelle "world" für die freien Spots, daher wird es aber mit den von MySQL standardmässig bereitgestellten Mitteln schwierig bis unmöglich eine freie Position nur mit SQL zu finden. Window-Funktionen gibt es nicht, Geo-Funktionen gibt es zwar, hab ich jetzt aber mal aussen vorgelassen. Sicher gibt es auch Möglichkeiten durch diverse Self-Joins zu einer Lösung zu kommen, aber ich hab mal hier mal einen anderen Ansatz gewählt:
Erst der Source, später die Erklärung.
SQL:
DELIMITER $$
CREATE FUNCTION getEmptySpot(Abstand INTEGER(3)) RETURNS INTEGER(10)
BEGIN
-- Zufallswerte für X und Y
DECLARE randX INTEGER(4) DEFAULT 0;
DECLARE randY INTEGER(4) DEFAULT 0;
-- maximale Ausdehnung der Welt
DECLARE maxX INTEGER(4);
DECLARE maxY INTEGER(4);
-- zaehlt wieviele Objekte in Umgebung vorhanden
DECLARE anzUmgebung INTEGER(2) DEFAULT 0;
-- Sicherheits-Zaehler, falls gar nichts gefunden wird
DECLARE anzDurchlaeufe INTEGER(3) DEFAULT 0;
-- Flag, ob freier Spot gefunden wurde
DECLARE gefunden BOOLEAN DEFAULT false;
DECLARE fertig BOOLEAN DEFAULT false;
-- Ausdehnung der Welt ermitteln
SELECT MAX(x), MAX(y)
INTO maxX, maxY
FROM world;
-- solange suchen, bis ein freier Spot gefunden
REPEAT
-- hole eine neue zufaellige Position
SET randX = FLOOR(RAND() * maxX) + 1;
SET randY = FLOOR(RAND() * maxY) + 1;
-- Anzahl Objekte in Umgebung zaehlen
SELECT count(*)
INTO anzUmgebung
FROM world
WHERE (typ ='s' OR typ='p')
AND x BETWEEN randX-Abstand AND randX+Abstand
AND y BETWEEN randY-Abstand AND randY+Abstand;
-- pruefen ob Umgebung frei ist
IF anzUmgebung = 0 THEN
SET gefunden = true;
SET fertig = true;
END IF;
SET anzDurchlaeufe = anzDurchlaeufe + 1;
UNTIL (gefunden = true) OR (anzDurchlaeufe = 500)
END REPEAT;
-- Gib Position zurueck
IF fertig THEN
RETURN randX*10000 + randY;
ELSE
RETURN 0;
END IF;
END $$
DELIMITER ;
Hierbei versuche ich mittels Zufallszahlen eine neue Position zu ermitteln. Dann prüfe ich, ob es in der Umgebung schon Objekte gibt. Wenn ja, generiere ich neue Zufallszahlen und versuche es an einer der neuen Position noch einmal usw. Wenn ich eine Position gefunden habe, bei der keinerlei Objekte in der Umgebung vorhanden sind, breche ich ab und liefere die x- und y-Positonen zurück.
Anmerkung 1) Die Position wird zurückgegeben als INTEGER, der sowohl die X- als auch Y-Koordinate enthält. Dieser setzt sich zusammen aus der X-Koordinate * 10000 addiert zu der Y-Koordinate.
Falls keine freie Position gefunden wurde, wird 0 zurück gegeben.
Um daraus wieder an die X- und Y-Koordinate zu kommen, rechnest du z.B. so:
Code:
X = POS div 10000 (Ganzzahlige Integerdivision)
Y = POS mod 10000 (Modulo, d.h. Rest der Division)
Du kannst das ganze natürlich umschreiben, dass die Positonen als OUT-Parameter zurück kommen, oder als String, den du dann parst. Wie auch immer.
Anmerkung 2) Du hast die Möglichkeit, einen Parameter "Abstand" mitzugeben, der den Bereich angibt, der um die Position frei sein soll. Je grösser der Bereich, desto länger läuft die Funktion, da mehr Iterationen durchgeführt werden müssen um eine passende Position zu finden. Daher...
Anmerkung 3) ... muss die Anzahl Durchläufe begrenzt werden, falls der "Abstand" zu gross gewählt wurde oder tatsächlich nichts mehr frei sein sollte. Ich hab jetzt mal 500 gewählt, das sollte für den Normalfall reichen.
Anmerkung 4) Es kann natürlich aufgrund des Zufallszahlengenerators bei grösser gewählten "Abständen" oder weil man einfach Pech hat, dazu kommen, dass man keine freie Position erhält, trotz dass noch eine vorhanden wäre. Da solltest du schauen, dass du entweder die Funktion nochmal ausführst oder aber abbrichst.
Testen kannst du die Funktion z.B. mittels
Wie du weiter verfährst, bleibt Dir überlassen. Du kannst noch eine Stored Procedure schreiben, die die eigentliche Arbeit macht und den Planet bzw. das Schiff anlegt und den Eintrag in der "world"-Tabelle vornimmt.
Du kannst das ganze dann natürlich auch in deiner Host-Umgebung machen (PHP etc.)
Hoffe diesmal hilft es, ;-)
Markus
Edit: Was noch nicht optimal ist, ist die Ermittlung der Ausdehnung der Welt. Entweder legst du sie fest, gibst sie als Parameter mit, oder hast eine zusätzliche Tabelle in der DB, in der diese Information gespeichert ist.