Closed suchja closed 4 weeks ago
Adding this to milestone 0.1 as it relates to the core game.
What is the easiest implementation of this that would align with acceptance criteria and NFRs?
MainWindow
- part of widgetapp
Qt6 Widget project
hint
from GameViewModel
.GameBoardView
inside QGraphicsView
as CentralWidget
GameBoardView
to show 20x20 grid with a tile size of 20 pixelsGameModel
- part of snakecore
library project
SnakeModel
getPositionOfFood() : QPointF
SnakeModel
- part of snakecore
library project
getBody() : std::deque<QPoint>
provides all body elements of the snakeGameViewModel
- part of widgetapp
Qt6 Widget project
GameModel
when it is createdGameBoardView : QGraphicsScene?
- part of widgetapp
Qt6 Widget project
setViewModel
-> triggers the first drawing!GameViewModel
GameViewModel::getBoardDimensions() : QRect
. -> Should use info from Game
and multiply with Tile-Size.GameBoardView
, add it to MainWindow
and let it draw Background based on info from GameViewModel
(no tests required here!)GameBoardView
Snake
to model, Game
provide const access to Snake
(requires unit test) -> changed to only provide position of SnakeGameViewModel
provide info about snake`s position (not sure how exactly)GameBoardView
draw snakesnakecore
does depend on Qt CoreThe core library (now called snake-core
) providing the game logic will be based on Qt Core. Although I usually try to fully separate the model (business logic) from technology as good as possible, I prefer to use signals/slots and also the provided "garbage collection".
setViewModel
method instead of constructor injectionGenerally speaking I like dependency injection via the constructor. However, here I'm using a dedicated method on the GameBoardView
to inject the dependency to the GameViewModel
. Here I currently see the following benefits:
setViewModel
), you decouple the instantiation of the GameViewModel
from the GameView
itself. This separation allows you to initialize and set the GameViewModel
independently of creating the GameView
.setViewModel
method supports late binding, meaning you can change the GameViewModel
instance associated with a GameView
dynamically at runtime if required. - Although I'm not sure whether this is required here at all.GameView
should ideally focus on initializing its own internal components and setting up basic functionality. Handling the GameViewModel
assignment through a setter method keeps the constructor cleaner.As I'm not that familiar with this way of injection, it is more like a try. I'll need to come back later on and see whether this really holds true and improves the overall design.
QList
vs std::vector
(or similar)For this design it is necessary to pass around lists of points for the snake's tail. Generally speaking this involves usually a maximum of 10-15 objects. Therefore performance consideration isn't that big of a problem. However, as my goal is also to learn about Qt and current versions of C++, I'm deciding to use QList
.
Although I usually avoid relying on a framework or technology in core libraries, I already decided to use Qt::Core
for snakecore
. Therefore this isn't as much an issue.
Then it seems that especially in Qt6
the QList
has been optimized for better memory management. The core feature I see for this scenario is implicit sharing (copy on write). This means an QList
instance is only copied, if an element is added. For the snake game an element is only added to the body, if the snake eats a food. Therefore this happens not that often.
GameBoardView
FoodGenerator
to generate random QPoint within the game board dimensions (requires tests!)Game
provide provide access to QPoint
as foodGameViewModel
and GameBoardView
to draw food from Game
.FoodGenerator
(random positions)As the FoodGenerator
uses QRandomGenerator
it is difficult to apply automated tests. Problem is that although two consecutive calls shouldn't result in the same position there is a slight probability that it will. If two consecutive calls result in the position, this is not an error per se. It only is an error, if it happens all the time or if the sequence of the created random positions is always the same.
Adding tests on the output of QRandomGenerator
seems to be a bad idea. As there is always a slight chance that due to the randomness the tests will fail. In this case I will go without tests, because to me a test that wrongly fails is a real anti pattern.
Thus I provide the tests, let them execute and verify the results manually. Then I mark them to be skipped (QSKIP
) in the future. Once I have an idea how to properly test this, they should be updated accordingly.
GameViewModel::getFoodPosition()
Generally speaking testing the GameViewModel::getFoodPosition()
involves similar problems (regarding randomness) as described in the previous section. Additionally this should test that the coordinates are in the correct coordinates system. Unfortunately some coordinates overlap between the row/column based system applied by the snakecore
and the GraphicsScene related system applied by the widgetapp
.
Therefore I skip these tests as well. Somehow I need to provide a better way of testing this.
MainWindow
GameViewModel
to "trigger" UserMessages
(requires tests!)MainWindow
Although I'm not sure about the user experience of this, I'll show the hint in the StatusBar
of the MainWindow
.
Notes
GameBoard
(20x20). In #6 we will change this, but until then it is fixed.Snake
appears at the same position with each start.Food
should be randomly placed at different positions with each start.Acceptance criteria
GameBoard
withSnake
(head and one tail item) and firstFood
is shown