I'm working on confusing and error-prone code; the problem arises when using two coordinate systems and continually confusing the coordinates of one of the systems with those of the other.
Problem.
The application consists of a 2d roguelike game with a top view . The coordinate system for the character, projectiles, enemies, and props on the map (world coordinates) is that each number is a dungeon tile; so the tile in the upper left corner starts at position 1 {0, 0}
and extends to position {0.9999…, 0.9999…}
1 .
In other words:
- Values
{[0, 1), [0, 1)}
belong to the tile{0, 0}
. - Values
{[8, 9), [3, 4)}
belong to the tile{8, 3}
. - etc…
On the other hand, the painting coordinate system (screen) is not based on tiles but on sprites , being able to zoom in on the room and placing the room in different positions depending on how the camera points, we find that the coordinates to paint the tile {x, y}
would be in reality:
{x * ancho_sprite * nivel_de_zoom, y * alto_sprite * nivel_de_zoom} - vector_cámara;
This causes me problems when I pass a paint coordinate to a paint function, or when I pass a paint coordinate to an AI, logic, or physics function, which has happened to me more than once, leading to hard-to-find errors. debug.
Solution.
I have thought of creating differentiated types for each family of coordinates, and when the coordinates travel from one context to another, apply transformations automatically; that is to say: transparently to the programmer; or in the worst case cause the use of a coordinate system in the wrong context to fail.
For this purpose I am using the sfml template class Vector2
and to make the coordinates of one system incompatible with those of another I have added one more level to the template:
enum contexto : char
{
mundo,
pantalla,
};
template <typename T, contexto C>
struct coordenada : public sf::Vector2<T>
{
using base = Vector2<T>;
using base::base();
using base::base(T, T);
// Mas directivas using...
};
With this simple trick I avoid unintentionally using coordinates from one system in another (illustrative code):
using Vector2fm = coordenada<float, mundo>;
using Vector2fp = coordenada<float, pantalla>;
struct jugador
{
Vector2fm posicion_logica;
Vector2fp posicion_visual;
sf::Sprite s;
} j1;
void mover_con_teclado(Vector2fm &elemento) { /* ... */ }
void pintar_en_posicion(Vector2fp &elemento, sf::Sprite &s) { /* ... */ }
// ...
/* Error no existe conversion conocida
de coordenada<float, pantalla> a coordenada<float, mundo> */
mover_con_teclado(j1.posicion_visual);
/* Error no existe conversion conocida
de coordenada<float, mundo> a coordenada<float, pantalla> */
pintar_en_posicion(j1.posicion_logica, j1.s);
With this trick, if I correctly define the types of each coordinate, it solves the problem of using screen coordinates in world and vice versa.
Mistake.
All I have to do is supply the conversion operators in both directions:
enum contexto : char
{
mundo,
pantalla,
};
template <typename T, contexto C>
struct coordenada : public sf::Vector2<T>
{
using base = Vector2<T>;
using base::base();
using base::base(T, T);
// Mas directivas using...
template <typename U>
operator coordenada<T, mundo>() const
{ return { x / 2.f, y / 2.f }; } // El 2.f es ilustrativo
template <typename U>
operator coordenada<T, pantalla>() const
{ return { x * 2.f, y * 2.f }; } // El 2.f es ilustrativo
};
But this raises an alarm for every conversion function and still fails when trying to do the conversion:
alarma: la función de conversión convirtiendo 'coordenada<float, contexto::mundo>' a si misma nunca será usada operator coordenada<T, mundo>() const { return { x / 2.f, y / 2.f }; } ^ alarma: la función de conversión convirtiendo 'coordenada<float, contexto::pantalla>' a si misma nunca será usada operator coordenada<T, pantalla>() const { return { x * 2.f, y * 2.f }; } ^
The alarm apparently occurs when a certain instance of the template tries to generate conversion to itself... at the moment it doesn't bother me, what I don't understand is why the conversion still doesn't work despite having created (at least) one function valid conversion code that does not generate an alarm:
error: ninguna función coincide para llamar 'mover_con_teclado' mover_con_teclado(j1.posicion_visual) ^~~~~~~~~~~~~~~~~ nota: la función candidata no es viable: no existe conversión conocida de 'coordenada<[...], contexto::mundo valor 0>' a 'coordenada<[...], contexto::pantalla valor 1>' para el primer argumento error: ninguna función coincide para llamar 'pintar_en_posicion' pintar_en_posicion(j1.posicion_logica, j1.s); ^~~~~~~~~~~~~~~~~~ nota: la función candidata no es viable: no existe conversión conocida de 'coordenada<[...], contexto::pantalla valor 1>' a 'coordenada<[...], contexto::mundo valor 0>' para el primer argumento
Ask.
How should I define the conversion operator of coordenada<float, mundo>
to coordenada<float, pantalla>
and its inverse?
1 The orientation of the coordinates is the one used by sfml in that it {0, 0}
is in the upper left corner and the axis y
grows downwards.
The error occurs because the functions do not accept a constant reference:
When doing the conversion you are creating an rValue that is passed as a reference to the function and the compiler is not liking that too much.
Since an rValue cannot be modified, the compiler discards that conversion and looks for another that is viable. Not finding it shows an error and stays so calm.
The solution is either to use a constant reference or to pass an object by value:
Another solution would be to convert the rValue to an lValue and then call the function: