#Game Play
Game Play
- most Tetris games use the up-arrow to rotate the piece. Unless you have a very specific reason you need to do otherwise, I'd use up-arrow like everybody else.
- I'd consider making each individual block of a tetromino two character cells wide (and still only one high). At least in most western European fonts, characters are about twice as tall as they are wide, so your "squares" aren't very square. This is particularly misleading with a block that's 2x3 squares, but 3 squares wide is actually portrayed narrower than 2 squares tall.
- the cursor keys are extremely sensitive--to the point that it's often difficult to get a block to the desired column--you get back and forth and can't quite get it to stop in the right place.
- You haven't called
srand
anywhere, so every game has the exact same sequence of game pieces.
#Early Exit
Early Exit
Right now, if the user decides to quite the game early (e.g., with ctrl+C) they're left with a console that does't really function normally. In my opinion, it would be better to handle this so the user gets a normally functioning console.
One way to do that would be to add a call to SetConsoleCtrlHandler
, to set up a handler that will close the handle to the console when/if the user kills the application.
Structure
Right now, most of the game's top-level logic is actually hidden in Utils.cpp. Normally, I'd expect something named "utils.cpp" to contain things that are quite generic, with no relationship to the specific program at hand, beyond some extremely general thing it does (e.g., it does some sort of string processing, so our utilities include some string stuff).
Code vs. Comments
I'm not overly fond of code like this:
// 0 characters are spaces and 9 are borders
pField[y * fieldWidth + x] = (x == 0 || x == fieldWidth - 1 || y == fieldHeight - 1) ? 9 : 0;
I'd prefer something like this instead:
static const char space = '\x0';
static const char border = '\x9';
pField[y+fieldWidth+x] = (x==0 || x == fieldWidth-1 || y == fieldHeight-1) ? border : space;
Separation of Concerns
Right now, your PlayField
manually allocates storage for the playing field. And it simulates 2D addressing in linear memory. And it knows about where the borders go in Tetris. And it doesn't do those very well--for example, it has a ctor that allocates memory with new
, but there's no code to delete that memory anywhere, so the memory is leaked.
In my opinion, it would be better to use std::vector
to manage the raw memory. Then write a simple wrapper to manage 2D addressing on top of that. Finally, add a layer to manage the Tetris border.
#Game Play
- most Tetris games use the up-arrow to rotate the piece. Unless you have a very specific reason you need to do otherwise, I'd use up-arrow like everybody else.
- I'd consider making each individual block of a tetromino two character cells wide (and still only one high). At least in most western European fonts, characters are about twice as tall as they are wide, so your "squares" aren't very square. This is particularly misleading with a block that's 2x3 squares, but 3 squares wide is actually portrayed narrower than 2 squares tall.
- the cursor keys are extremely sensitive--to the point that it's often difficult to get a block to the desired column--you get back and forth and can't quite get it to stop in the right place.
- You haven't called
srand
anywhere, so every game has the exact same sequence of game pieces.
#Early Exit
Right now, if the user decides to quite the game early (e.g., with ctrl+C) they're left with a console that does't really function normally. In my opinion, it would be better to handle this so the user gets a normally functioning console.
One way to do that would be to add a call to SetConsoleCtrlHandler
, to set up a handler that will close the handle to the console when/if the user kills the application.
Structure
Right now, most of the game's top-level logic is actually hidden in Utils.cpp. Normally, I'd expect something named "utils.cpp" to contain things that are quite generic, with no relationship to the specific program at hand, beyond some extremely general thing it does (e.g., it does some sort of string processing, so our utilities include some string stuff).
Code vs. Comments
I'm not overly fond of code like this:
// 0 characters are spaces and 9 are borders
pField[y * fieldWidth + x] = (x == 0 || x == fieldWidth - 1 || y == fieldHeight - 1) ? 9 : 0;
I'd prefer something like this instead:
static const char space = '\x0';
static const char border = '\x9';
pField[y+fieldWidth+x] = (x==0 || x == fieldWidth-1 || y == fieldHeight-1) ? border : space;
Separation of Concerns
Right now, your PlayField
manually allocates storage for the playing field. And it simulates 2D addressing in linear memory. And it knows about where the borders go in Tetris. And it doesn't do those very well--for example, it has a ctor that allocates memory with new
, but there's no code to delete that memory anywhere, so the memory is leaked.
In my opinion, it would be better to use std::vector
to manage the raw memory. Then write a simple wrapper to manage 2D addressing on top of that. Finally, add a layer to manage the Tetris border.
Game Play
- most Tetris games use the up-arrow to rotate the piece. Unless you have a very specific reason you need to do otherwise, I'd use up-arrow like everybody else.
- I'd consider making each individual block of a tetromino two character cells wide (and still only one high). At least in most western European fonts, characters are about twice as tall as they are wide, so your "squares" aren't very square. This is particularly misleading with a block that's 2x3 squares, but 3 squares wide is actually portrayed narrower than 2 squares tall.
- the cursor keys are extremely sensitive--to the point that it's often difficult to get a block to the desired column--you get back and forth and can't quite get it to stop in the right place.
- You haven't called
srand
anywhere, so every game has the exact same sequence of game pieces.
Early Exit
Right now, if the user decides to quite the game early (e.g., with ctrl+C) they're left with a console that does't really function normally. In my opinion, it would be better to handle this so the user gets a normally functioning console.
One way to do that would be to add a call to SetConsoleCtrlHandler
, to set up a handler that will close the handle to the console when/if the user kills the application.
Structure
Right now, most of the game's top-level logic is actually hidden in Utils.cpp. Normally, I'd expect something named "utils.cpp" to contain things that are quite generic, with no relationship to the specific program at hand, beyond some extremely general thing it does (e.g., it does some sort of string processing, so our utilities include some string stuff).
Code vs. Comments
I'm not overly fond of code like this:
// 0 characters are spaces and 9 are borders
pField[y * fieldWidth + x] = (x == 0 || x == fieldWidth - 1 || y == fieldHeight - 1) ? 9 : 0;
I'd prefer something like this instead:
static const char space = '\x0';
static const char border = '\x9';
pField[y+fieldWidth+x] = (x==0 || x == fieldWidth-1 || y == fieldHeight-1) ? border : space;
Separation of Concerns
Right now, your PlayField
manually allocates storage for the playing field. And it simulates 2D addressing in linear memory. And it knows about where the borders go in Tetris. And it doesn't do those very well--for example, it has a ctor that allocates memory with new
, but there's no code to delete that memory anywhere, so the memory is leaked.
In my opinion, it would be better to use std::vector
to manage the raw memory. Then write a simple wrapper to manage 2D addressing on top of that. Finally, add a layer to manage the Tetris border.
#Game Play
- most Tetris games use the up-arrow to rotate the piece. Unless you have a very specific reason you need to do otherwise, I'd use up-arrow like everybody else.
- I'd consider making each individual block of a tetromino two character cells wide (and still only one high). At least in most western European fonts, characters are about twice as tall as they are wide, so your "squares" aren't very square. This is particularly misleading with a block that's 2x3 squares, but 3 squares wide is actually portrayed narrower than 2 squares tall.
- the cursor keys are extremely sensitive--to the point that it's often difficult to get a block to the desired column--you get back and forth and can't quite get it to stop in the right place.
- You haven't called
srand
anywhere, so every game has the exact same sequence of game pieces.
#Early Exit
Right now, if the user decides to quite the game early (e.g., with ctrl+C) they're left with a console that does't really function normally. In my opinion, it would be better to handle this so the user gets a normally functioning console.
One way to do that would be to add a call to SetConsoleCtrlHandler
, to set up a handler that will close the handle to the console when/if the user kills the application.
Structure
Right now, most of the game's top-level logic is actually hidden in Utils.cpp. Normally, I'd expect something named "utils.cpp" to contain things that are quite generic, with no relationship to the specific program at hand, beyond some extremely general thing it does (e.g., it does some sort of string processing, so our utilities include some string stuff).
Code vs. Comments
I'm not overly fond of code like this:
// 0 characters are spaces and 9 are borders
pField[y * fieldWidth + x] = (x == 0 || x == fieldWidth - 1 || y == fieldHeight - 1) ? 9 : 0;
I'd prefer something like this instead:
static const char space = '\x0';
static const char border = '\x9';
pField[y+fieldWidth+x] = (x==0 || x == fieldWidth-1 || y == fieldHeight-1) ? border : space;
Separation of Concerns
Right now, your PlayField
manually allocates storage for the playing field. And it simulates 2D addressing in linear memory. And it knows about where the borders go in Tetris. And it doesn't do those very well--for example, it has a ctor that allocates memory with new
, but there's no code to delete that memory anywhere, so the memory is leaked.
In my opinion, it would be better to use std::vector
to manage the raw memory. Then write a simple wrapper to manage 2D addressing on top of that. Finally, add a layer to manage the Tetris border.