platform-game/Code blocks projekt/modules/map.c.save
2026-08-09 13:37:33 +01:00

379 lines
13 KiB
Text

#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, &currentHeight, 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;
}