379 lines
13 KiB
C
379 lines
13 KiB
C
#include "map.h"
|
|
|
|
#define maxPlatformLength 5 /**< A legszélesebb platformok szélessége [m]. */
|
|
#define minPlatformLength 2 /**< A legkeskenyebb platformok szélessége [m], ez nem teljesen igaz, mindig lesznek ksiebbek is, csak nem olyan sok. */
|
|
#define baseHeightPercent 0.3 /**< A pálya hányad része legyen tömör "föld". */
|
|
#define fuzziness 2 /**< Mennyire legyen recegős a pálya? (legnagyobb magasság különbségek a földszinten)*/
|
|
#define treasureColor (colors){ (color){69, 82, 160, 255}, (color){58, 69, 135, 255}, (color){44, 53, 102, 255} } /**< A jutalmak színe. */
|
|
#define treasureSize (vector3D){0.5, 0.5, 0.25} /**< A jutalom mérete. */
|
|
|
|
colors* colorMap = NULL; /**< A színeket tartalmazó 1 dimenziós tömb. (minden oszlop egyszínű) */
|
|
block** map = NULL; /**< A pályát tartalmazó 2 dimenziós tömb. */
|
|
intVector2D mapSize; /**< A pálya mérete [m]. */
|
|
|
|
|
|
///A pálya méretét lehet vele beállítani.
|
|
void setMapSize(int x, int y) {
|
|
mapSize.x = x;
|
|
mapSize.y = y;
|
|
}
|
|
|
|
///A pálya méretét adja vissza.
|
|
intVector2D getMapSize() {
|
|
return mapSize;
|
|
}
|
|
|
|
///A pályát adja vissza.
|
|
block** getMap() {
|
|
return map;
|
|
}
|
|
|
|
///Megmondja, hogy egy pont benne van-e egy négyzetben.
|
|
bool isPointWithinBox(vector2D point, vector2D boxPosition, vector2D boxSize) {
|
|
return
|
|
boxPosition.x < point.x && point.x < boxPosition.x + boxSize.x &&
|
|
boxPosition.y < point.y && point.y < boxPosition.y + boxSize.y;
|
|
}
|
|
|
|
///Megmondja, hogy két négyzet fedésben van-e.
|
|
bool isBoxOverlappingBox(vector2D box1Position, vector2D box1Size, vector2D box2Position, vector2D box2Size) {
|
|
int i;
|
|
for(i = 0; i < 4; i++) {
|
|
switch(i) {
|
|
case 1:
|
|
box1Position.x += box1Size.x;
|
|
break;
|
|
case 2:
|
|
box1Position.y += box1Size.y;
|
|
break;
|
|
case 3:
|
|
box1Position.x -= box1Size.x;
|
|
}
|
|
if(isPointWithinBox(box1Position, box2Position, box2Size)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
///Megmondja, hogy egy pont a pályán belül van-e.
|
|
bool isWithinTheMap(vector2D point, vector2D size) {
|
|
return
|
|
0 <= point.x && point.x + size.x < mapSize.x &&
|
|
0 <= point.y && point.y + size.y < mapSize.y;
|
|
}
|
|
|
|
///Megmondja, hogy történt-e ütközés egy négyzet és a pálya között. Visszaadja a szóban forgó kocka pointerét is.
|
|
bool detectCollision(vector2D position, vector2D size, block collidableBlock, block** collidedWith) {
|
|
int x, y;
|
|
if (isWithinTheMap(position, size)) {
|
|
for(x = (int)position.x; x < position.x + size.x; x++) {
|
|
for(y = (int)position.y; y < position.y + size.y; y++) {
|
|
*collidedWith = &map[x][y];
|
|
if(**collidedWith == collidableBlock) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
else {
|
|
collidedWith = NULL;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
///Felszabadítja a színek tömbjét.
|
|
void freeColorMap() {
|
|
free(colorMap);
|
|
}
|
|
|
|
///Véletlenszerűen létrehozza a kockák palettáját, egy oszlopon belül minden kocka egyszínű.
|
|
void generateColorMap() {
|
|
freeColorMap();
|
|
|
|
colorMap = (colors*) malloc(mapSize.x * sizeof(colors));
|
|
int x;
|
|
for(x = 0; x < mapSize.x; x++)
|
|
colorMap[x] = createBlockColor();
|
|
}
|
|
|
|
///Felszabadítja a térkép tömbjét.
|
|
void freeMap() {
|
|
if(map != NULL) {
|
|
free(map[0]);
|
|
free(map);
|
|
}
|
|
}
|
|
|
|
///Létrehoz egy tömböt a térképnek.
|
|
void createMap() {
|
|
freeMap();
|
|
|
|
int x;
|
|
map = (block**) malloc(mapSize.x * sizeof(block*));
|
|
map[0] = (block*) malloc(mapSize.x * mapSize.y * sizeof(block));
|
|
for(x = 1; x < mapSize.x; x++)
|
|
map[x] = map[0] + x * mapSize.y;
|
|
}
|
|
|
|
///Kiszámolja, hogy ellehet-e érni egy adott kockát a játékosnak.
|
|
static bool canBeReached(int deltaX, int deltaY, vector2D maxVelocity, float hMax) {
|
|
float t1 = maxVelocity.y / g;
|
|
float t2 = sqrt(2 * (hMax - (float)deltaY) / g);
|
|
float possibleDeltaX = maxVelocity.x * (t1 + t2);
|
|
|
|
return possibleDeltaX > deltaX;
|
|
}
|
|
|
|
///Egy kiszámolt mátrix alapján megmondja, hogy a játékos elérhet-e egy adott kockát.
|
|
static bool canJumpThere(intVector2D from, intVector2D to, int width, int height, bool possiblePlacesToJumpTo[][height]) {
|
|
int xIndex;
|
|
int yIndex = to.y - from.y - 1;
|
|
if(yIndex >= height || yIndex < 0) return false;
|
|
|
|
xIndex = to.x > from.x ? to.x - from.x - 1 : from.x - to.x - 1;
|
|
if(xIndex >= width || xIndex < 0) return false;
|
|
|
|
return possiblePlacesToJumpTo[xIndex][yIndex];
|
|
}
|
|
|
|
///A pálya földszintjét hozza létre.
|
|
static void createMapBase() {
|
|
int x, y, currentHeight;
|
|
|
|
int baseHeight = (int) (mapSize.y * baseHeightPercent);
|
|
|
|
for(x = 0; x < mapSize.x; x++)
|
|
for(y = 0; y < mapSize.y; y++)
|
|
map[x][y] = air;
|
|
|
|
currentHeight = baseHeight / 2;
|
|
for(x = 0; x < mapSize.x; x++) {
|
|
|
|
if(fuzziness > 0) {
|
|
currentHeight = randomIntWithinBorders(currentHeight, fuzziness);
|
|
putInsideInterval(1, ¤tHeight, baseHeight);
|
|
}
|
|
|
|
for(y = 0; y <= baseHeight + currentHeight; y++)
|
|
map[x][y] = solid;
|
|
}
|
|
}
|
|
|
|
///Létrehoz egy platformot, ami nem lóg túl a pályarészek határán, ezekről később bővebben.
|
|
static void buildPlatform(int direction, intVector2D* previous, int x, int xMaxBound) {
|
|
int platformLength = rand() % (maxPlatformLength - minPlatformLength) + minPlatformLength + 1;
|
|
|
|
if(direction == 1 && previous -> x + platformLength - 1 >= xMaxBound)
|
|
platformLength = xMaxBound - previous -> x;
|
|
|
|
if(direction == -1 && previous -> x - platformLength < x)
|
|
platformLength = previous -> x - x + 1;
|
|
|
|
int m;
|
|
for(m = 0; m < platformLength; m++) {
|
|
map[previous -> x + direction * m][previous -> y] = solid;
|
|
if(rand() % 3 == 0) map[previous -> x][previous -> y + 1] = treasure;
|
|
}
|
|
|
|
previous -> x += direction * rand() % platformLength;
|
|
}
|
|
|
|
///Primitív megoldás, de lthatatlan falakat csinál, amik megakadályozzák, hogy olyan helyre menjen a játékos, ahova nem kéne.
|
|
static void createInvisibleBounds() {
|
|
int x, y;
|
|
for(x = 0; x < mapSize.x; x++) {
|
|
map[x][0]= invisible;
|
|
map[x][mapSize.y - 1]= invisible;
|
|
}
|
|
|
|
for(y = 0; y < mapSize.y; y++) {
|
|
map[0][y]= invisible;
|
|
map[mapSize.x - 1][y]= invisible;
|
|
}
|
|
}
|
|
|
|
/** \brief Térkép platformokkal betöltése, úgy hogy játszható legyen
|
|
* Kisebb egységekre, részekre bontja a pályát. Minden egységben egy darab platform oszlop van. A platformok magasság különbsége olyan,
|
|
* hogy a jelenlegi fizikával a játékos ugrálva elérhesse őket. A pályagenerálás előtt ezért ki kell számolni, milyen messzire ugorhat.
|
|
* \param sebességvektor player max
|
|
* A platformokra random tesz jutalmakat (kék kockákat) is.
|
|
* Nem túl hosszú a függvény, de nem is a legrövidebb, ennél több részre sajnos nem lehet szétszedni szépen.
|
|
*/
|
|
void generateMap(vector2D maxVelocity) {
|
|
|
|
createMapBase();
|
|
|
|
int x, y;
|
|
|
|
//A játékos képességeinek maximuma
|
|
float realHMax = (maxVelocity.y * maxVelocity.y / 2 / g);
|
|
int lowerHMax = (int)realHMax;
|
|
int upperHMax = lowerHMax + 1;
|
|
int lowerXMax = (int)(2 * maxVelocity.x * maxVelocity.y / g);
|
|
|
|
|
|
//Hova ugorhat a (-1, -1) -ről?
|
|
int possibleWidth = lowerXMax;
|
|
int possibleHeight = lowerHMax;
|
|
bool possiblePlacesToJumpTo[possibleWidth][possibleHeight];
|
|
|
|
|
|
for(y = 0; y < possibleHeight; y++)
|
|
for(x = 0; x < possibleWidth; x++)
|
|
possiblePlacesToJumpTo[x][y] = canBeReached(x + 1, y + 1, maxVelocity, realHMax);
|
|
|
|
|
|
intVector2D min, max, previous, test;
|
|
|
|
int previousPreviousY;
|
|
bool isOkey;
|
|
|
|
int xInterval = 10;
|
|
int minHeightDiff = 2;
|
|
int l, direction, optionsCount, xMaxBound;
|
|
bool alreadyTried;
|
|
|
|
intVector2D options[possibleWidth * possibleHeight];
|
|
|
|
for(x = 0; x < mapSize.x; x += xInterval) {
|
|
|
|
//Kezdőpont keresés
|
|
previous.x = smaller(x + rand() % xInterval, mapSize.x - 1);
|
|
xMaxBound = smaller(x + xInterval, mapSize.x);
|
|
min.y = getHeightAtX(previous.x);
|
|
|
|
previous.y = min.y;
|
|
previousPreviousY = -100;
|
|
max.y = min.y;
|
|
|
|
//Addig csináljuk, amíg nem érünk a tetejére
|
|
while(previous.y + 2 * upperHMax < mapSize.y) {
|
|
|
|
//Határok meghatározása véletlenszerű irány alapján
|
|
direction = randomPlusOrMinus();
|
|
alreadyTried = false;
|
|
|
|
newDirection:
|
|
|
|
min.y = larger(previousPreviousY + upperHMax + 1, previous.y + minHeightDiff) + 1;
|
|
max.y = previous.y + possibleHeight;
|
|
//[határok)
|
|
if(direction == 1) {
|
|
//jobbra
|
|
min.x = smaller(previous.x + 1, mapSize.x - 1);
|
|
max.x = smaller(previous.x + possibleWidth + 1, xMaxBound);
|
|
}
|
|
else {
|
|
//balra
|
|
min.x = larger(larger(previous.x - possibleWidth, x), 0);
|
|
max.x = larger(previous.x, 1);
|
|
}
|
|
|
|
//A kiválasztott határok közé lehet-e ugrani egyáltalán?
|
|
isOkey = false;
|
|
optionsCount = 0;
|
|
for(test.x = min.x; test.x < max.x; test.x++)
|
|
for(test.y = min.y; test.y < max.y; test.y++)
|
|
if(canJumpThere(previous, test, possibleWidth, possibleHeight, possiblePlacesToJumpTo)) {
|
|
isOkey = true;
|
|
options[optionsCount++] = test;
|
|
}
|
|
//Ha igen, válasszunk ki egyet véletlenszerűen
|
|
if(isOkey) {
|
|
previousPreviousY = previous.y;
|
|
l = rand() % optionsCount;
|
|
previous = options[l];
|
|
|
|
buildPlatform(direction, &previous, x, xMaxBound);
|
|
}
|
|
else {
|
|
if(alreadyTried) break;
|
|
alreadyTried = true;
|
|
direction *= -1;
|
|
goto newDirection; // a goto nem a legjobb, de mindenképpen szebb egy for ciklusnál
|
|
}
|
|
}
|
|
}
|
|
createInvisibleBounds();
|
|
}
|
|
|
|
///Megmondja egy adott x-hez milyen magas földszint tartozik
|
|
int getHeightAtX(int x) {
|
|
int y;
|
|
for(y = 1; map[(int)x][y] == solid; y++)
|
|
;
|
|
return y;
|
|
}
|
|
|
|
///Ez a player modul szempontjából lényeges, megmondja, hogy belelóg-e egy kockába
|
|
bool isInBlockSideways(int x, int lowestY, int uppermostY) {
|
|
return map[x][lowestY]== solid || map[x][lowestY]== invisible ||
|
|
map[x][uppermostY]== solid || map[x][uppermostY]== invisible;
|
|
}
|
|
|
|
///Ez a player modul szempontjából lényeges, megmondja, hogy belelóg-e egy kockába
|
|
bool isInBlockPerpendicular(int leftmostX, int rightmostX, int y) {
|
|
return map[leftmostX][y]== solid || map[leftmostX][y]== invisible ||
|
|
map[rightmostX][y]== solid || map[rightmostX][y ]== invisible;
|
|
}
|
|
|
|
///Melyik oldalai látszanak egy kockának? Azok biztos nem, amiket más kockák eltakarnak.
|
|
static char calculateSides(bool sides[], int x, int y) {
|
|
sides[0] = map[x][y - 1] != solid; //alja
|
|
sides[1] = map[x - 1][y] != solid; //bal oldal
|
|
sides[2] = map[x + 1][y] != solid; //jobb oldal
|
|
sides[3] = map[x][y + 1] != solid; //teteje
|
|
sides[4] = true; //eleje
|
|
|
|
char sum = 0;
|
|
int i;
|
|
for(i = 0; i < 5; i++)
|
|
if(sides[i]) sum += 1 << i;
|
|
return sum;
|
|
}
|
|
|
|
/** \brief A térkép rajzolást teszi lehetővé.
|
|
* A sebesség növelés végett az egymás alatt lévő kockákat (mivel egyszínűek) egyszerre is rajzolhatjuk.
|
|
* Ilyenkor több dologra is kell figyelnünk. Először is, nem elég az azonos szín, arra is figyelni kell, hogy
|
|
* azonos oldalaik is látszódjanak. Ha tud oszlopot rajzolni, akkor az y koordját módosítja hívójának.
|
|
* \param koord x
|
|
* \param koord* y
|
|
* \return test oszlop
|
|
* Pályakockákat és jutalmakat is rajzol.
|
|
*/
|
|
renderObject getBlockRenderObject(int x, int* y, int direction, int bound, bool sides[]) {
|
|
renderObject blockRenderObject;
|
|
if(isWithinTheMap((vector2D){x, *y}, (vector2D){1, 1}) && map[x][*y] == treasure) {
|
|
calculateSides(sides, x, *y);
|
|
sides[1] = true;
|
|
sides[2] = true;
|
|
sides[3] = true;
|
|
|
|
//jutalmat rajzol
|
|
blockRenderObject.position = (vector3D){x + 0.25, *y, 0.375};
|
|
blockRenderObject.size = treasureSize;
|
|
blockRenderObject.colors = treasureColor;
|
|
blockRenderObject.doesExist = true;
|
|
|
|
}
|
|
else if(isWithinTheMap((vector2D){x, *y}, (vector2D){1, 1}) && map[x][*y] == solid) {
|
|
char sidesSum = calculateSides(sides, x, *y);
|
|
int i = 1;
|
|
while(((direction == -1 && *y - i > bound) || (direction == 1 && *y + i < bound)) &&
|
|
map[x][*y + i * direction] == solid &&
|
|
sidesSum == calculateSides(sides, x, *y + i * direction))
|
|
i++;
|
|
|
|
sidesSum = calculateSides(sides, x, *y);
|
|
|
|
blockRenderObject.size = (vector3D){1, i, 1};
|
|
int newY = direction == 1 ? *y : *y - (i-1);
|
|
blockRenderObject.position = (vector3D){x, newY, 0};
|
|
blockRenderObject.colors = colorMap[x];
|
|
blockRenderObject.doesExist = true;
|
|
|
|
*y += direction * (i - 1);
|
|
}
|
|
else {
|
|
blockRenderObject.doesExist = false;
|
|
}
|
|
return blockRenderObject;
|
|
}
|