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++

#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;
}*/