How to avoid the IF ELSE statements - c++

I have the following code, which does not look very nice. What methods could I use to make it look better?
Imagine that there are a lot of IF ELSE statements. As an example, I would like to distinguish whether the left is smaller or larger than the right. And more distances are added.
(It's about a robot that is supposed to detect obstacles in front of it.)
distance1 = left_LOX.distance();
distance2 = middle_LOX.distance();
distance3 = right_LOX.distance();
if (distance1 < 100 || distance2 < 100 || distance3 < 100)
distance_case = 1;
else if (distance1 < 300 || distance2 < 300 || distance3 < 300)
distance_case = 2;
else
distance_case = 3;
// Abfragen für die Automatik
switch (distance_case)
{
case 1:
target = 0;
robot.Stop();
delay(2000);
break;
case 2:
target = 4000;
robot.Forward();
break;
case 3:
target = 6000;
robot.Forward();
break;
}
An idea on how to make it better would be nice.

If you have a lot of distances (hundreds, thousand), it's easier to group all the distances together in a container such as vector first, and then use standard algorithms. E.g. something like this:
This is assuming you have the C++ std library available on the esp32 platform/compiler you are using - it should be possible at least up to C++11 - see e.g. https://medium.com/geekculture/modern-c-with-esp32-dcd3918dd978.
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
// Just an example vector initialisation - replace this with the required x_Lox.distance() calls.
std::vector<int> distances = {300, 500, 500, 200, 100 };
const auto min = *std::min_element(distances.begin(), distances.end());
std::cout << "Min distance: " << min;
return 0;
}
Once you have the minimum distance, you can run through the various cases for handling. This is based on the assumption that you are only interested in the shortest distance to choose your robot's action. If you have more complex logic, the code also gets more complex.

you can use if ... else if ... else block directly if the distance_case does not have long term use. It unnecessarily introduces new variables which has no purpose. Or define an enum if you see, in long term, there will many such cases. Reader can also quickly make sense of the values.

You can add if it looks better for you.
distance_case = (distance1 < 100 || distance2 < 100 || distance3 < 100) +
(distance1 < 300 || distance2 < 300 || distance3 < 300) +
(distance1 >= 300 || distance2 >= 300 || distance3 >= 300);

dmin= min(left_LOX.distance(), middle_LOX.distance(), right_LOX.distance());
if (dmin < 100)
{
target = 0;
robot.Stop();
delay(2000);
}
else if (dmin < 300)
{
target = 4000;
robot.Forward();
}
else
{
target = 6000;
robot.Forward();
}

Related

Tetris function for checking if piece fits doesn't work right

Im practicing C++, and been working on tetris clone. I have tried to figure out my own solutions with help of few tutorials to learn to code with this language. Im having almost everything else working, but this function is giving me some problems:
bool Board::isBoxFree(int pX, int pY, int pPiece[][4], int pBoard[][20])
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (pPiece[i][j] != 0)
{
if (pBoard[(pX / 15) + i][(pY / 15) + j] != 0)
{
return false;
break;
}
if (pY / 15 + j > 18)
{
return false;
break;
}
else
return true;
break;
}
}
}
This works with some of the pieces, but gives wrong values systematically with different tetris pieces. For example when checking if straight line can go right the last block overlaps borders. With n-shape or t-shape there is no problem. Sometimes when going down tetrominos overlap one block before function working and tetromino saving to board with the overlapping position. Some tetrominos don't seem to hit anything but are still collided and saved to the board one block away when they should have been collided.
Is this function written wrong or is there something wrong with my use of two dimensional arrays?
This is how I check if tetromino can go down inside game tick:
if (this->playBoard(xLoc, yLoc, this->arr, rotate, this->boardArr).isBoxFree(xLoc, yLoc + boxSize, this->arr, this->boardArr))
And here is how I check if tetromino can go left or right:
if (this->ev.key.code == Keyboard::Left)
{
if (this->playBoard().isBoxFree(xLoc - boxSize, yLoc , this->arr, this->boardArr)&& move ==false )//(move == false && xLoc>boxSize)
{
xLoc = xLoc - boxSize;
move = true;
}
}
else if (this->ev.key.code == Keyboard::Right)
{
if(this->playBoard().isBoxFree(xLoc+boxSize*3, yLoc , this->arr, this->boardArr)&& move == false) //(move == false && xLoc<boxSize*7)
{
xLoc = xLoc + boxSize;
move = true;
}
}
As you can see I have multiplied boxSize with 3 when checking if going right is possible for it to work right with at least some tetrominos. That alone tells that there is something wrong with my code that I haven't figured out.
Help is much appreciated since this is one of the last problems I have with my tetris. Even the line clearing function works.
Re: Is this function written wrong
You have an error in your Board::isBoxFree() functin, in the inner for loop (I've removed redundant breaks, as Paul commented):
if (pPiece[i][j] != 0)
{
if (pBoard[(pX / 15) + i][(pY / 15) + j] != 0)
return false;
if (pY / 15 + j > 18)
return false;
else
return true;
}
You can remove else too, as if a condition was true - you would return already. So:
if (pPiece[i][j] != 0)
{
if (pBoard[(pX / 15) + i][(pY / 15) + j] != 0)
return false;
if (pY / 15 + j > 18)
return false;
return true;
}
So for the first pPiece[i][j] that is NOT 0, if the board at that location is empty - you would return true, without checking other elements.
This is a common pattern. You check all elements of your pPiece and return false as soon as you find the collision. Then, when you are out of the loop (no collisions were found) - you return true
Re: is there something wrong with my use of two dimensional arrays?
Possibly. My eyes hurt a little looking at all those / 15, > 18... :)

p5.js - get a rectangle to move left and right repeatedly (bounce)

I'm trying out some sample code for a bigger project, and I'm having trouble getting my rectangle to bounce between two lines.
function draw() {
print(frameCount)
background(255)
var x = 150 + frameCount;
rect(x,200,15,15);
line(150,0,150,400);
line(250,0,250,400);
if (x >= 250) {
background(255)
x = 350-frameCount;
rect(x,200,15,15);
line(250,0,250,400);
line(150,0,150,400);
} if (x <= 145) {
background(255)
x = 145 + (frameCount % 100);
rect(x,200,15,15);
line(250,0,250,400);
line(150,0,150,400);
}
}
I'm getting the feeling that after the first instance, it's disregarding the original if statement, which dictates a bounce to the left. I'm really not sure what's going wrong, and any help would be appreciated.
You probably just want to store the current position and speed in a set of variables, and then move the rectangle based on those. Here's an example:
var x = 0;
var speed = 1;
function draw(){
x += speed;
if(x < 0 || x > width){
speed *= -1;
}
background(64);
line(x, 0, x, height);
}
I've written a tutorial on this available here. That's for regular Processing, but the ideas are the same in P5.js.

How do I prevent over-correcting in my autonomous driving solution?

I am working on an autonomous driving solution for Euro Truck Simulator 2 with OpenCV in C++.
Here is where we detect the curve of the road:
int bottom_center = 160;
int sum_centerline = 0;
int count_centerline = 0;
int first_centerline = 0;
int last_centerline = 0;
double avr_center_to_left = 0;
double avr_center_to_right = 0;
//#pragma omp parallel for
for (int i = 240; i > 30; i--){
double center_to_right = -1;
double center_to_left = -1;
for (int j = 0; j < 150; j++) {
if (contours.at<uchar>(i, bottom_center + j) == 112 && center_to_right == -1) {
center_to_right = j;
}
if (contours.at<uchar>(i, bottom_center - j) == 112 && center_to_left == -1) {
center_to_left = j;
}
}
if (center_to_left != -1 && center_to_right != -1){
int centerline = (center_to_right - center_to_left + 2 * bottom_center) / 2;
if (first_centerline == 0) {
first_centerline = centerline;
}
cv::circle(outputImg, Point(centerline, i), 1, Scalar(30, 255, 30), 3);
cv::circle(outputImg, Point(centerline + center_to_right+20, i), 1, Scalar(255, 30, 30) , 3);
cv::circle(outputImg, Point(centerline - center_to_left+10, i), 1, Scalar(255, 30, 30) , 3);
sum_centerline += centerline;
avr_center_to_left = (avr_center_to_left * count_centerline + center_to_left) / count_centerline + 1;
avr_center_to_right = (avr_center_to_right * count_centerline + center_to_right) / count_centerline + 1;
last_centerline = centerline;
count_centerline++;
}
else {}
}
And here is my current solution for steering:
int diff = 0;
if (count_centerline != 0) {
diff = sum_centerline / count_centerline - bottom_center;
int degree = atan2(last_centerline - first_centerline, count_centerline) * 180 / PI;
//diff = (90 - degree);
int move_mouse_pixel = diff;
cout << "Steer: " << move_mouse_pixel << "px ";
if (diff <= 20 || diff >= -20){
SetCursorPos(pt.x + (move_mouse_pixel / 10), height / 2);
}
else{
SetCursorPos(pt.x + (move_mouse_pixel / 25), height / 2);
}
}
Finally, here is a video of what my program currently does: https://www.youtube.com/watch?v=rqyvoFuGKKk&feature=youtu.be
The current problem I have is that the steering does not center fast enough, leading it to continually over-correct until it swerves off the lane. I have tried to increase steering sensitivity in-game, to allow for faster or slower turning, but this either makes the truck spin out of control or not turn enough when driving along a large curve.
My current method just divides slight movements (between -20px and 20px) by 10, and large movements by 20. I've also tried reversing this but did not fix the over-correcting problem.
There are two possible solutions that I have found so far:
I could incrementally increase the divider for which we apply to move_mouse_pixel, therefore reducing the force of steering done between small movements.
Or, I could somehow make the program center the steering wheel more quickly. I am not sure how I would implement this.
What do you guys think?
I believe that PID controller would be suitable for this task.
https://en.wikipedia.org/wiki/PID_controller
In your situation it would look similar to this:
diffOld = diff;
diff = sum_centerline / count_centerline - bottom_center;
SetCursorPos(width/2 + Kp* diff + Kd*(diff - diffOld) , height / 2);
Do not use if statement in this controller. You need to keep steering even if there is no error to corect. I would suggest to skip integral part, because your object integrate (when you do not drive straight you integrate error). You need to experimentally choose values of Kp and Kd parameters, for example with Ziegler–Nichols method https://en.wikipedia.org/wiki/Ziegler%E2%80%93Nichols_method.

Particle Deposition Terrain Generation

I'm using Particle Deposition to try and create some volcano-like mountains procedurally but all I'm getting out of it is pyramid-like structures. Is anyone familiar with the algorithm that might be able to shed some light on what I might be doing wrong. I'm dropping each particle in the same place at the moment. If I don't they spread out in a very thin layer rather than any sort of mountain.
void TerrainClass::ParticalDeposition(int loops){
float height = 0.0;
//for(int k= 0; k <10; k++){
int dropX = mCurrentX = rand()%(m_terrainWidth-80) + 40;
int dropY = mCurrentZ = rand()%(m_terrainHeight-80) + 40;
int radius = 15;
float angle = 0;
int tempthing = 0;
loops = 360;
for(int i = 0; i < loops; i++){
mCurrentX = dropX + radius * cos(angle);
mCurrentZ = dropY + radius * sin(angle);
/*f(i%loops/5 == 0){
dropX -= radius * cos(angle);
dropY += radius * sin(angle);
angle+= 0.005;
mCurrentX = dropX;
mCurrentZ = dropY;
}*/
angle += 360/loops;
//dropX += rand()%5;
//dropY += rand()%5;
//for(int j = 0; j < loops; j++){
float newY = 0;
newY = (1 - (2.0f/loops)*i);
if(newY < 0.0f){
newY = 0.0f;
}
DepositParticle(newY);
//}
}
//}
}
void TerrainClass::DepositParticle(float heightIncrease){
bool posFound = false;
m_lowerList.clear();
while(posFound == false){
int offset = 10;
int jitter;
if(Stable(0.5f)){
m_heightMap[(m_terrainHeight*mCurrentZ)+mCurrentX].y += heightIncrease;
posFound = true;
}else{
if(!m_lowerList.empty()){
int element = rand()%m_lowerList.size();
int lowerIndex = m_lowerList.at(element);
MoveTo(lowerIndex);
}
}
}
}
bool TerrainClass::Stable(float deltaHeight){
int index[9];
float height[9];
index[0] = ((m_terrainHeight*mCurrentZ)+mCurrentX); //the current index
index[1] = ValidIndex((m_terrainHeight*mCurrentZ)+mCurrentX+1) ? (m_terrainHeight*mCurrentZ)+mCurrentX+1 : -1; // if the index to the right is valid index set index[] to index else set index[] to -1
index[2] = ValidIndex((m_terrainHeight*mCurrentZ)+mCurrentX-1) ? (m_terrainHeight*mCurrentZ)+mCurrentX-1 : -1; //to the left
index[3] = ValidIndex((m_terrainHeight*(mCurrentZ+1))+mCurrentX) ? (m_terrainHeight*(mCurrentZ+1))+mCurrentX : -1; // above
index[4] = ValidIndex((m_terrainHeight*(mCurrentZ-1))+mCurrentX) ? (m_terrainHeight*(mCurrentZ-1))+mCurrentX : -1; // bellow
index[5] = ValidIndex((m_terrainHeight*(mCurrentZ+1))+mCurrentX+1) ? (m_terrainHeight*(mCurrentZ+1))+mCurrentX+1: -1; // above to the right
index[6] = ValidIndex((m_terrainHeight*(mCurrentZ-1))+mCurrentX+1) ? (m_terrainHeight*(mCurrentZ-1))+mCurrentX+1: -1; // below to the right
index[7] = ValidIndex((m_terrainHeight*(mCurrentZ+1))+mCurrentX-1) ? (m_terrainHeight*(mCurrentZ+1))+mCurrentX-1: -1; // above to the left
index[8] = ValidIndex((m_terrainHeight*(mCurrentZ-1))+mCurrentX-1) ? (m_terrainHeight*(mCurrentZ-1))+mCurrentX-1: -1; // above to the right
for ( int i = 0; i < 9; i++){
height[i] = (index[i] != -1) ? m_heightMap[index[i]].y : -1;
}
m_lowerList.clear();
for(int i = 1; i < 9; i++){
if(height[i] != -1){
if(height[i] < height[0] - deltaHeight){
m_lowerList.push_back(index[i]);
}
}
}
return m_lowerList.empty();
}
bool TerrainClass::ValidIndex(int index){
return (index > 0 && index < m_terrainWidth*m_terrainHeight) ? true : false;
}
void TerrainClass::MoveTo(int index){
mCurrentX = index%m_terrainWidth;
mCurrentZ = index/m_terrainHeight;
}
Thats all the code thats used.
You should have a look at these two papers:
Fast Hydraulic Erosion Simulation and Visualization on GPU
Fast Hydraulic and Thermal Erosion on the GPU (read the first one first, the second one expands on it)
Don't get scared by the "on GPU", the algorithms work just fine on CPU (albeit slower). The algorithms don't do particle sedimentation per se (but you don't either ;) ) - they instead aggregate the particles into several layers of vector fields.
One important thing about this algorithm is that it erodes already existing heightmaps - for example generated with perlin noise. It fails miserably if the initial height field is completely flat (or even if it has insufficient height variation).
I had implemented this algorithm myself and had mostly success with it (still have more work to do, the algorithms are very hard to balance to give universally great results) - see the image below.
Note that perlin noise with the Thermal weathering component from the second paper may be well enough for you (and might save you a lot of trouble).
You can also find C++ CPU-based implementation of this algorithm in my project (specifically this file, mind the GPL license!) and its simplified description on pages 24-29 of my thesis.
Your particles will need to have some surface friction and/or stickiness (or similar) in their physics model if you want them to not spread out into a single-layer. This is performed in the collision detection and collision response parts of your code when updating your particle simulation.
A simple approach is to make the particles stick (attract each-other). Particles need to have a size too so that they don't simply converge to perfectly overlapping. If you want to make them attract each other, then you need to test the distance between particles.
You might benefit from looking through some of the DirectX SDK examples that use particles, and in particular (pun arf!) there is a great demo (by Simon Green?) in the NVidia GPU Computing SDK that implements sticky particles in CUDA. It includes a ReadMe document describing what they've done. You can see how the particles interact and ignore all the CUDA/GPU stuff if you aren't going for massive particle counts.
Also note that as soon as you use inter-particle forces, then you are checking approximately 0.5*n^2 combinations (pairs) of particles...so you may need to use a simple spatial partitioning scheme or similar to limit forces to nearby groups of particles only.
Good luck!

how to avoid clutch billiard balls?

I'm working on the simple behaviour of billiard balls in a collision with each other. All works normal, but there was a problem when facing a few easy balls is the effect of coupling balls and they're cool with each other. Tell me how to prevent this.
bool MGBilliard::CollisingBall(CCPoint curr_point, CCPoint next_point)
{
float dx = next_point.x - (curr_point.x + dvdt.x);
float dy = next_point.y - (curr_point.y - dvdt.y);
float d = dx*dx+dy*dy;
return d <= BALL_RADIUS * BALL_RADIUS;
}
double MGBilliard::angleCollisionBalls(Ball* current, Ball* next)
{
double na;
double dx = fabs(next->location.x - current->location.x);
double dy = fabs(next->location.y - current->location.y);
na = atan(fabs(dy/dx));
if(atan(fabs(current->location.y/current->location.x)) < atan(fabs(next->location.y/next->location.x)))
na = current->angle - na;
else if(atan(fabs(current->location.y/current->location.x)) > atan(fabs(next->location.y/next->location.x)))
na = current->angle + na;
return na;
}
for(unsigned int i = 0;i<BALL_COUNT;++i)
{
if(vBalls[i]->speed > 0){
vBalls[i]->speed += vBalls[i]->acceleration;
float dsdt = vBalls[i]->speed*dt;
dvdt.x = dsdt*cos(vBalls[i]->angle);
dvdt.y = dsdt*sin(vBalls[i]->angle);
vBalls[i]->location.x += dvdt.x;
vBalls[i]->location.y += dvdt.y;
for(unsigned int j = 1; j < BALL_COUNT; ++j)
{
if(i == j) continue;
if(CollisingBall(vBalls[i]->spriteBall->getPosition(),vBalls[j]->spriteBall->getPosition()))
{
vBalls[j]->speed = 600;
double angle;
angle = angleCollisionBalls(vBalls[i],vBalls[j]);
vBalls[i]->angle = (float)-angle;
vBalls[j]->angle = (float)angle;
}
}
}
}
There are two straightforward bugs that spring to my attention with a quick look at your code.
Firstly, this:
vBalls[i]->angle = (float)-angle;
vBalls[j]->angle = (float)angle;
is not the correct way to calculate opposing angles. For example, it will not do what you want it to do when angle is zero (or 180 degrees, for that matter).
Secondly, you iterate over your whole vBalls array multiple times, once with the index i and an inner loop with the index j. This means collisions will be calculated twice, and the speed of both balls would be set to 600! Changing your inner loop to be this:
for(unsigned int j = i + 1; j < BALL_COUNT; ++j)
should prevent this occurring.
There's also a more subtle bug. Your collision detection does not take into account time. Each ball moves a particular distance each iteration of your game loop. This means that if a collision does not occur in one 'tick', a ball could pass straight through another ball and then trigger the collision code on the far side of the ball. You cannot do a simple radius-based collision test in this situation, as if a ball moves more than (BALL_RADIUS * BALL_RADIUS) in a single step, your system will behave strangely or not work at all.
I personally would use vectors to describe speed and direction for each ball rather than angles and speeds, but refactoring your code to do this is a bit outside the scope of this question.