patterncppMinor
Simple 8 Puzzle - ASCII - follow up
Viewed 0 times
simplepuzzlefollowascii
Problem
Based on the previous question, I have implemented all suggestions with slight modifications. The game seems fine, are there any further improvements needed in this game?
```
#include
#include
#include
#include
#include
#include
#include
#include
template
struct Vector
{
Vector(T x, T y): x(x), y(y){}
T x, y;
};
using Vector2i = Vector;
using Vector2u = Vector;
namespace Key
{
enum Type
{
Up = 72,
Right = 77,
Left = 75,
Down = 80
};
}
namespace Direction
{
enum Type
{
Up,
Down,
Left,
Right,
DirectionCount
};
}
namespace utility
{
std::mt19937 randomEngine()
{
std::array seed_data;
thread_local std::random_device source;
std::generate(seed_data.begin(), seed_data.end(), std::ref(source));
std::seed_seq seeds(seed_data.begin(), seed_data.end());
thread_local std::mt19937 seeded_engine(seeds);
return seeded_engine;
}
Vector2i directionOffset(Direction::Type direction)
{
using OffsetHolder = std::array;
constexpr static OffsetHolder xOffset{ 0, 0, +1, -1 };
constexpr static OffsetHolder yOffset{ +1, -1, 0, 0 };
return{ xOffset[direction], yOffset[direction] };
}
}
namespace ut = utility;
class Puzzle
{
private:
constexpr static auto MaxTiles = 9u;
constexpr static auto Columns = 3u;
using Pair = std::pair;
using TileHolder = std::array;
public:
Puzzle()
{
std::iota(mTiles.begin(), mTiles.end(), '1');
mTiles.back() = ' ';
thread_local auto random = ut::randomEngine();
std::shuffle(mTiles.begin(), mTiles.end(), random);
}
void update(Direction::Type direction)
{
auto coord = getEmptyTileCoordinates();
auto x = coord.first.x + ut::directionOffset(direction).x;
auto y = coord.first.y + ut::directionOffset(direction).y;
if (x >= 3u || y >= 3u) re
```
#include
#include
#include
#include
#include
#include
#include
#include
template
struct Vector
{
Vector(T x, T y): x(x), y(y){}
T x, y;
};
using Vector2i = Vector;
using Vector2u = Vector;
namespace Key
{
enum Type
{
Up = 72,
Right = 77,
Left = 75,
Down = 80
};
}
namespace Direction
{
enum Type
{
Up,
Down,
Left,
Right,
DirectionCount
};
}
namespace utility
{
std::mt19937 randomEngine()
{
std::array seed_data;
thread_local std::random_device source;
std::generate(seed_data.begin(), seed_data.end(), std::ref(source));
std::seed_seq seeds(seed_data.begin(), seed_data.end());
thread_local std::mt19937 seeded_engine(seeds);
return seeded_engine;
}
Vector2i directionOffset(Direction::Type direction)
{
using OffsetHolder = std::array;
constexpr static OffsetHolder xOffset{ 0, 0, +1, -1 };
constexpr static OffsetHolder yOffset{ +1, -1, 0, 0 };
return{ xOffset[direction], yOffset[direction] };
}
}
namespace ut = utility;
class Puzzle
{
private:
constexpr static auto MaxTiles = 9u;
constexpr static auto Columns = 3u;
using Pair = std::pair;
using TileHolder = std::array;
public:
Puzzle()
{
std::iota(mTiles.begin(), mTiles.end(), '1');
mTiles.back() = ' ';
thread_local auto random = ut::randomEngine();
std::shuffle(mTiles.begin(), mTiles.end(), random);
}
void update(Direction::Type direction)
{
auto coord = getEmptyTileCoordinates();
auto x = coord.first.x + ut::directionOffset(direction).x;
auto y = coord.first.y + ut::directionOffset(direction).y;
if (x >= 3u || y >= 3u) re
Solution
I see a number of things that may help you improve your code.
Don't use
There are two reasons not to use
Isolate platform-specific code
In this code, there are several things that are DOS/Windows only including
Make sure you have all required
The code uses
Fix the initialization
The initialization of the puzzle does not always result in a solvable puzzle. In fact, exactly half of all possible states are impossible to solve. This is a bit frustrating for anyone actually trying to solve the puzzle, so I'd recommend a different initialization that only results in solvable puzzles.
Don't abuse namespaces
The namespacess in this program seem excessive to me. In particular, I was annoyed by this line:
If you find the name
Don't abuse
I'm a fan of
it makes me question why it's being used. It seems to be written almost as though the point were obfuscation.
Write code for clarity
Let's imagine that you've just been asked to add a feature to this code and it's the first time you've seen it. You look at this line in
So what is a
Great. So what's a
By now you are cursing the name of the original programmer. What is a
So now you're four levels of indirection away and have probably forgotten what started you on this chain of lookups in the first place. Even more frustrating is that each of these is only used exactly once in the program, so there's little point to most of it. Don't do this! Write for clarity instead.
Keep
Both the
Make static data
The
Then outside the class, initialize it like this:
Note that I've also dropped yet another
Place
A
Don't use
system("cls")There are two reasons not to use
system("cls") or system("pause"). The first is that it is not portable to other operating systems which you may or may not care about now. The second is that it's a security hole, which you absolutely must care about. Specifically, if some program is defined and named cls or pause, your program will execute that program instead of what you intend, and that other program could be anything. First, isolate these into a seperate functions cls() and pause() and then modify your code to call those functions instead of system. Then rewrite the contents of those functions to do what you want using C++. For example, if your terminal supports ANSI Escape sequences, you could use this:void cls()
{
std::cout << "\x1b[2J";
}Isolate platform-specific code
In this code, there are several things that are DOS/Windows only including
#include and the getch() and kbhit() functions within that, and also system("cls"); that I've already mentioned. Your code runs successfully on Linux if I supply those missing functions, but it would be nice if there were an #ifdef WINDOWS already in the code so that one could recompile without having to alter the source code.Make sure you have all required
#includesThe code uses
std::ref but doesn't #include . Fix the initialization
The initialization of the puzzle does not always result in a solvable puzzle. In fact, exactly half of all possible states are impossible to solve. This is a bit frustrating for anyone actually trying to solve the puzzle, so I'd recommend a different initialization that only results in solvable puzzles.
Don't abuse namespaces
The namespacess in this program seem excessive to me. In particular, I was annoyed by this line:
namespace ut = utility;If you find the name
utility too long, change it. If you don't, then use it as is. It's only used three places anyway.Don't abuse
autoI'm a fan of
auto generally, but when I see lines like this:auto column = 0u;it makes me question why it's being used. It seems to be written almost as though the point were obfuscation.
Write code for clarity
Let's imagine that you've just been asked to add a feature to this code and it's the first time you've seen it. You look at this line in
Puzzle:Pair getEmptyTileCoordinates() constSo what is a
Pair? You scan the code and find this line:using Pair = std::pair;Great. So what's a
Vector2u? More hunting until you find this:using Vector2u = Vector;By now you are cursing the name of the original programmer. What is a
Vector? It's this:template
struct Vector
{
Vector(T x, T y): x(x), y(y){}
T x, y;
};So now you're four levels of indirection away and have probably forgotten what started you on this chain of lookups in the first place. Even more frustrating is that each of these is only used exactly once in the program, so there's little point to most of it. Don't do this! Write for clarity instead.
Keep
private data togetherBoth the
Game and the Puzzle classes have three different places where private: is used. Again, this makes things more confusing than they need to be. Instead, put all of the private parts together. I recommend putting functions first and then data.Make static data
staticThe
mKeyBinding member of the doesn't change (so it should be const) and is also invariant across Puzzle instantiations. This strongly suggests that it should instead be static. Change the declaration to this:static const std::map mKeyBinding;Then outside the class, initialize it like this:
const std::map Game::mKeyBinding{
{Key::Up, Direction::Up},
{Key::Down, Direction::Down},
{Key::Left, Direction::Left},
{Key::Right, Direction::Right}
};Note that I've also dropped yet another
typedef to clarify the code.Place
friend declarations in a public sectionA
friend declaration is always public no matter if it's declared within the private or public sections. For clarity, put it in the public section unless there is a very good reason not to (and there is almost never a good reason).Code Snippets
void cls()
{
std::cout << "\x1b[2J";
}namespace ut = utility;auto column = 0u;Pair getEmptyTileCoordinates() constusing Pair = std::pair<Vector2u, unsigned int>;Context
StackExchange Code Review Q#117766, answer score: 4
Revisions (0)
No revisions yet.