You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
560 lines
13 KiB
C++
560 lines
13 KiB
C++
#include "utils.hpp"
|
|
#include <string>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include "RLGame.hpp"
|
|
|
|
#ifdef _DEBUG
|
|
#include <iostream>
|
|
#endif
|
|
|
|
// From http://lazyfoo.net/tutorials/SDL/27_collision_detection/index.php
|
|
bool utils::IsOverlapping(int x1, int y1, int width1, int height1, int x2, int y2, int width2, int height2)
|
|
{
|
|
//The sides of the rectangles
|
|
int leftA, leftB;
|
|
int rightA, rightB;
|
|
int topA, topB;
|
|
int bottomA, bottomB;
|
|
|
|
//Calculate the sides of rect A
|
|
leftA = x1;
|
|
rightA = x1 + width1;
|
|
topA = y1;
|
|
bottomA = y1 + height1;
|
|
|
|
//Calculate the sides of rect B
|
|
leftB = x2;
|
|
rightB = x2 + width2;
|
|
topB = y2;
|
|
bottomB = y2 + height2;
|
|
|
|
//If any of the sides from A are outside of B
|
|
if(bottomA <= topB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(topA >= bottomB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(rightA <= leftB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(leftA >= rightB)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//If none of the sides from A are outside B
|
|
return true;
|
|
}
|
|
|
|
|
|
// From http://www.roguebasin.com/index.php?title=Bresenham%27s_Line_Algorithm
|
|
// and https://www.cs.helsinki.fi/group/goa/mallinnus/lines/bresenh.html
|
|
void utils::Bresenham(int x1, int y1, const int x2, const int y2)
|
|
{
|
|
int delta_x(x2 - x1);
|
|
// if x1 == x2, then it does not matter what we set here
|
|
const signed char ix((delta_x > 0) - (delta_x < 0));
|
|
delta_x = std::abs(delta_x) << 1;
|
|
|
|
int delta_y(y2 - y1);
|
|
// if y1 == y2, then it does not matter what we set here
|
|
const signed char iy((delta_y > 0) - (delta_y < 0));
|
|
delta_y = std::abs(delta_y) << 1;
|
|
|
|
//plot(x1, y1);
|
|
#ifdef _DEBUG
|
|
std::cout << "\nx1: " << x1 << " y1: " << y1 << '\n';
|
|
#endif
|
|
//terminal_put(x1, y1, L'B');
|
|
//terminal_refresh();
|
|
|
|
if(delta_x >= delta_y)
|
|
{
|
|
// error may go below zero
|
|
int error(delta_y - (delta_x >> 1));
|
|
|
|
while(x1 != x2)
|
|
{
|
|
// reduce error, while taking into account the corner case of error == 0
|
|
if((error > 0) || (!error && (ix > 0)))
|
|
{
|
|
error -= delta_x;
|
|
y1 += iy;
|
|
}
|
|
// else do nothing
|
|
|
|
error += delta_y;
|
|
x1 += ix;
|
|
|
|
//plot(x1, y1);
|
|
#ifdef _DEBUG
|
|
std::cout << "\nx1: " << x1 << " y1: " << y1 << '\n';
|
|
#endif
|
|
if(G_CURRENT_MAP->CheckTile(x1, y2) == G_MAPTILE_WALL)
|
|
return;
|
|
terminal_put(x1 + G_OFFSET_X, y1 + G_OFFSET_Y, L'x');
|
|
terminal_refresh();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// error may go below zero
|
|
int error(delta_x - (delta_y >> 1));
|
|
|
|
while(y1 != y2)
|
|
{
|
|
// reduce error, while taking into account the corner case of error == 0
|
|
if((error > 0) || (!error && (iy > 0)))
|
|
{
|
|
error -= delta_y;
|
|
x1 += ix;
|
|
}
|
|
// else do nothing
|
|
|
|
error += delta_x;
|
|
y1 += iy;
|
|
|
|
//plot(x1, y1);
|
|
#ifdef _DEBUG
|
|
std::cout << "\nx1: " << x1 << " y1: " << y1 << '\n';
|
|
#endif
|
|
if(G_CURRENT_MAP->CheckTile(x1, y2) == G_MAPTILE_WALL)
|
|
return;
|
|
terminal_put(x1 + G_OFFSET_X, y1 + G_OFFSET_Y, L'x');
|
|
terminal_refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// This function implements the method recommended by the knowing
|
|
// folks at comp.lang.c: http://c-faq.com/lib/randrange.html
|
|
// Returns an integer in [min, max].
|
|
int utils::RandomBetween(const int min, const int max)
|
|
{
|
|
unsigned int N = (max - min <= RAND_MAX) /* Make sure the algorithm */
|
|
? (max - min + 1U) /* terminates by keeping N */
|
|
: (RAND_MAX + 1U); /* in rand()'s maximum range. */
|
|
unsigned int x = (RAND_MAX + 1U) / N;
|
|
unsigned int y = x * N;
|
|
unsigned int r;
|
|
do
|
|
{
|
|
r = rand();
|
|
} while(r >= y);
|
|
return r / x + min;
|
|
}
|
|
|
|
// simpler random but also less random
|
|
int utils::RandomBetweenSimple(const int min, const int max)
|
|
{
|
|
return rand() % (max - min + 1) + min;
|
|
}
|
|
|
|
color_t utils::RandomColor()
|
|
{
|
|
return color_from_argb(UINT8_MAX, uint8_t(utils::RandomBetweenSimple(0, UINT8_MAX)), uint8_t(utils::RandomBetweenSimple(0, UINT8_MAX)), uint8_t(utils::RandomBetweenSimple(0, UINT8_MAX)));
|
|
}
|
|
|
|
// Uses utils::RandomBetween to return a random position
|
|
Position utils::RandomPositionBetween(const int minX, const int maxX, const int minY, const int maxY)
|
|
{
|
|
return Position{ utils::RandomBetween(minX, maxX), utils::RandomBetween(minY, maxY) };
|
|
}
|
|
|
|
// color_t uses a uint32_t to represent colors
|
|
void utils::DrawBox(int x, int y, int width, int height, int layer, bool background, const std::string& title, color_t foregroundColor, color_t backgroundColor)
|
|
{
|
|
int prev{ terminal_state(TK_LAYER) };
|
|
|
|
// background
|
|
if(background)
|
|
{
|
|
terminal_layer(layer);
|
|
terminal_crop(x, y, width, height);
|
|
terminal_color(backgroundColor);
|
|
|
|
for(int backY{ y }; backY < y + height; ++backY)
|
|
{
|
|
for(int backX{ x }; backX < x + width; ++backX)
|
|
{
|
|
terminal_put(backX, backY, L'\x2588');
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// box
|
|
terminal_layer(layer + int(background)); // on top of the squares (higher layer) because the box characters will otherwise have a transparent background, small explanation below
|
|
///terminal_composition(TK_ON); // forgot that you can stack chars with bearlibterminal, problem with title though
|
|
|
|
/*
|
|
* BearLibTerminal only allows the 0th layer to have a background color, the others will be transparent, so this is a workaround
|
|
* For example, all entities are drawn on the 0th layer and the player has a green background
|
|
* I could instead clear the 0th layer and draw the box there instead (kind of like ClearBox() below)
|
|
* but I did it like this instead (also there'd be one turn where there'd be empty space and I'd have to redraw that part of the level)
|
|
*/
|
|
|
|
terminal_crop(x, y, width, height);
|
|
terminal_color(foregroundColor);
|
|
|
|
for(int dx{ x }; dx < x + width; ++dx)
|
|
{
|
|
if(dx == x) // commented lines here are the non-bold box characters
|
|
{
|
|
//terminal_put(dx, y, L'┌');
|
|
//terminal_put(dx, y + height - 1, L'└');
|
|
//terminal_put(dx, y, L'┏');
|
|
terminal_put(dx, y, L'\x250f');
|
|
//terminal_put(dx, y + height - 1, L'┗');
|
|
terminal_put(dx, y + height - 1, L'\x2517');
|
|
}
|
|
else if(dx == x + width - 1)
|
|
{
|
|
//terminal_put(dx, y, L'┐');
|
|
//terminal_put(dx, y + height - 1, L'┘');
|
|
//terminal_put(dx, y, L'┓');
|
|
terminal_put(dx, y, L'\x2513');
|
|
//terminal_put(dx, y + height - 1, L'┛');
|
|
terminal_put(dx, y + height - 1, L'\x251b');
|
|
}
|
|
else
|
|
{
|
|
//terminal_put(dx, y, L'─');
|
|
//terminal_put(dx, y + height - 1, L'─');
|
|
terminal_put(dx, y, L'\x2501');
|
|
//terminal_put(dx, y, L'━');
|
|
terminal_put(dx, y + height - 1, L'\x2501');
|
|
//terminal_put(dx, y + height - 1, L'━');
|
|
}
|
|
}
|
|
|
|
for(int dy{ y + 1 }; dy < y + height - 1; ++dy)
|
|
{
|
|
//terminal_put(x, dy, L'│');
|
|
//terminal_put(x + width - 1, dy, L'│');
|
|
terminal_put(x, dy, L'\x2503');
|
|
//terminal_put(x + width - 1, dy, L'┃');
|
|
terminal_put(x + width - 1, dy, L'\x2503');
|
|
}
|
|
|
|
// title, just overwrite the box characters
|
|
if(!title.empty())
|
|
{
|
|
//title = " " + title + " "; // looked weird
|
|
//terminal_color("dark yellow");
|
|
|
|
bool rainbow{ false }; // rainbow text
|
|
|
|
if(rainbow)
|
|
{
|
|
for(size_t i{}; i < title.size(); ++i)
|
|
{
|
|
terminal_color(RandomColor());
|
|
terminal_put(x + 1 + i, y, title[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
terminal_print(x + 1, y, title.c_str());
|
|
}
|
|
}
|
|
|
|
terminal_refresh();
|
|
terminal_color(G_COL_WHITE);
|
|
///terminal_composition(TK_OFF);
|
|
terminal_layer(prev);
|
|
}
|
|
|
|
void utils::ClearBox(int x, int y, int width, int height, int layer)
|
|
{
|
|
int prev{ terminal_state(TK_LAYER) }; // gets current layer
|
|
terminal_layer(layer);
|
|
terminal_clear_area(x, y, width, height);
|
|
terminal_layer(layer + 1);
|
|
terminal_clear_area(x, y, width, height);
|
|
terminal_layer(prev);
|
|
}
|
|
|
|
wchar_t utils::GetWChar()
|
|
{
|
|
int key{};
|
|
|
|
/*if(key == TK_RETURN || key == TK_ESCAPE || key == TK_CLOSE)
|
|
{
|
|
// No distinction between confirmation and interruption
|
|
return L'\x0';
|
|
}
|
|
else if(terminal_check(TK_WCHAR))
|
|
{
|
|
return wchar_t(terminal_state(TK_WCHAR));
|
|
}
|
|
else
|
|
{
|
|
return L'\x0';
|
|
}*/
|
|
|
|
while(true)
|
|
{
|
|
key = terminal_read();
|
|
|
|
switch(key)
|
|
{
|
|
case TK_RETURN:
|
|
case TK_ESCAPE:
|
|
case TK_CLOSE:
|
|
return L'\x0'; // cancel
|
|
break;
|
|
case TK_SHIFT:
|
|
case TK_CONTROL:
|
|
case TK_ALT:
|
|
case TK_TAB:
|
|
continue; // ignore
|
|
break;
|
|
default:
|
|
return terminal_state(TK_WCHAR); // TK_(key) != terminal_state(TK_CHAR)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int utils::GetKey()
|
|
{
|
|
int key{};
|
|
|
|
while(true)
|
|
{
|
|
key = terminal_read();
|
|
|
|
switch(key)
|
|
{
|
|
case TK_RETURN:
|
|
case TK_ESCAPE:
|
|
case TK_CLOSE:
|
|
return L'\x0'; // cancel
|
|
break;
|
|
case TK_SHIFT:
|
|
case TK_CONTROL:
|
|
case TK_ALT:
|
|
case TK_TAB:
|
|
continue; // ignore
|
|
break;
|
|
default:
|
|
return key; // TK_(key) != terminal_state(TK_CHAR)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool utils::WaitForEnter()
|
|
{
|
|
int key{};
|
|
|
|
while(true)
|
|
{
|
|
key = terminal_read();
|
|
|
|
if(key == TK_ENTER || key == TK_KP_ENTER)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
size_t utils::SplitString(const std::string& toSplit, std::vector<std::string>& splitString, char delimiter)
|
|
{
|
|
*if(splitString.size() <= 1)
|
|
{
|
|
splitString.push_back(toSplit);
|
|
return size_t(1);
|
|
}*
|
|
|
|
size_t pos{ toSplit.find(delimiter) };
|
|
size_t initialPos{};
|
|
splitString.clear();
|
|
|
|
while(pos != std::string::npos)
|
|
{
|
|
splitString.push_back(toSplit.substr(initialPos, pos - initialPos));
|
|
initialPos = pos + 1;
|
|
|
|
pos = toSplit.find(delimiter, initialPos);
|
|
}
|
|
|
|
splitString.push_back(toSplit.substr(initialPos, std::min(pos, splitString.size()) - initialPos + 1));
|
|
return splitString.size();
|
|
}*/
|
|
|
|
/// removing all these slashes is going to be shit, thanks visual studio
|
|
|
|
/*
|
|
/// Breadth first algorithm
|
|
std::vector<Position> utils::BFS(const Position& start, const Position& end)
|
|
{
|
|
std::queue<Position> frontier;
|
|
std::vector<Position> neighbors, explored;
|
|
std::map<Position, Position> prev;
|
|
Position current;
|
|
frontier.push(start);
|
|
prev[start] = start;
|
|
|
|
while(!frontier.empty())
|
|
{
|
|
current = frontier.front();
|
|
frontier.pop();
|
|
//explored.push_back(current);
|
|
|
|
if(current == end)
|
|
break;
|
|
|
|
neighbors.clear();
|
|
neighbors = GetNeighbors(current);
|
|
|
|
for(auto&& pos : neighbors)
|
|
{
|
|
if(std::find(prev.begin(), prev.end(), pos) == prev.end())
|
|
{
|
|
frontier.push(pos);
|
|
prev[pos] = current;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<Position> path;
|
|
|
|
for(auto at = end; !(at == Position{ 0,0 }); at = prev[at])
|
|
{
|
|
path.push_back(at);
|
|
}
|
|
|
|
std::reverse(path.begin(), path.end());
|
|
|
|
if(path[0] == start)
|
|
return path;
|
|
else
|
|
return std::vector<Position>{};
|
|
|
|
|
|
}
|
|
|
|
std::map<Position, Position> utils::Pathfind(const Position& start, const Position& end)
|
|
{
|
|
std::queue<Position> frontier;
|
|
std::vector<Position> neighbors, explored;
|
|
std::map<Position, Position> explored;
|
|
Position current;
|
|
frontier.push(start);
|
|
explored[start] = start;
|
|
|
|
while(!frontier.empty())
|
|
{
|
|
current = frontier.front();
|
|
frontier.pop();
|
|
//explored.push_back(current);
|
|
|
|
if(current == end)
|
|
break;
|
|
|
|
neighbors.clear();
|
|
neighbors = GetNeighbors(current);
|
|
|
|
for(auto&& pos : neighbors)
|
|
{
|
|
if(std::find(explored.begin(), explored.end(), pos) == explored.end())
|
|
{
|
|
frontier.push(pos);
|
|
explored[pos] = current;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
for(size_t i{}; i < explored.size(); ++i)
|
|
{
|
|
mvprintw(explored[i].y, explored[i].x, "E");
|
|
}
|
|
refresh();
|
|
*
|
|
|
|
return explored;
|
|
|
|
Position* cameFrom = new Position[(SCREEN_HEIGHT - SCREEN_BOTTOM) * SCREEN_WIDTH];
|
|
std::array<Position, (SCREEN_HEIGHT - SCREEN_BOTTOM) * SCREEN_WIDTH> cameFrom;
|
|
}
|
|
|
|
// all m_Tiles have a maximum of 8 neighboring m_Tiles
|
|
// exceptions are m_Tiles near walls and the edges of the screen
|
|
std::vector<Position> utils::GetNeighbors(const Position& from)
|
|
{
|
|
std::vector<Position> neighbors;
|
|
Position temp{ from };
|
|
|
|
// north
|
|
temp.y -= 1;
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// south
|
|
temp = from;
|
|
temp.y += 1;
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// east
|
|
temp = from;
|
|
temp.x += 1;
|
|
//neighbors.push_back(Position{ from.x + 1, from.y });
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// west
|
|
temp = from;
|
|
temp.x -= 1;
|
|
//neighbors.push_back(Position{ from.x - 1, from.y });
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// north east
|
|
temp = from;
|
|
temp.x += 1;
|
|
temp.y -= 1;
|
|
//neighbors.push_back(Position{ from.x + 1, from.y - 1 });
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// south east
|
|
temp = from;
|
|
temp.x += 1;
|
|
temp.y += 1;
|
|
//neighbors.push_back(Position{ from.x + 1, from.y + 1 });
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// north west
|
|
temp = from;
|
|
temp.x -= 1;
|
|
temp.y -= 1;
|
|
//neighbors.push_back(Position{ from.x - 1, from.y - 1 });
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
// south west
|
|
temp = from;
|
|
temp.x -= 1;
|
|
temp.y += 1;
|
|
//neighbors.push_back(Position{ from.x - 1, from.y + 1 });
|
|
if(std::find(WALLS_ARRAY.begin(), WALLS_ARRAY.end(), GET_AT_POS(temp)) == WALLS_ARRAY.end())
|
|
neighbors.push_back(temp);
|
|
|
|
return neighbors;
|
|
}*/ |