Closed suchja closed 3 months ago
Adding this to milestone 0.1 as it relates to the core game.
ChangeLog: Moved #5 from here into a separate user story as it seems to me that it all together gets to large for one user story.
ChangeLog: Added acceptance criteria for letting snake "cross" the game board and added that the hint how to start the game should disappear.
This morning I made up my mind about TDD (see here. So now it is time to try TDD "the right way").
That means I try to use the following list of tests to guide the development.
Based on the wonderful book Test Driven Development by example (written by Kent Beck), I'll also track todos and open issues as they arise in order to keep moving forward and not get distracted.
Snake::executeMove
moves body independently of head. Probably this will get us in some trouble.Game::isStarted
signal is emitted before Snake::setMoveDirection
is executed?GameViewModel
instead of the MainWindow
. Check whether there is a possibility to do this while keeping up a good testability.QSignalSpy
has some wired behavior. While I can use QSignalSpy another_signal(&game_dependency, &Game::isStarted)
on the Game
class, only QSignalSpy is_started_signal(&sut, SIGNAL(gameStarted()));
works for the GameViewModel
. No idea why that is.GameViewModel
might require a signal userMessageUpdated
. This would improve single responsibility and maybe allow separate interfaces for View (one MainWindow
is using and one GameBoardView
is using).Starting with test Pressing any other key (except arrow) does nothing to the snake or the game
GameViewModel::processKeyboardAction(int key_code) : bool
MainWindow
and follows the EventFilter
mechanismTrue
when key_code
was handledFalse
when key_code
was not handled and needs to be passed along for further processingThen I do have some logic here that I'll not test. Not sure how to integrate things like that into TDD. Anyhow, the MainWindow
will get an EventFilter
, which calls the newly implemented method in GameViewModel
.
Somehow I have difficulties with the next test Pressing any arrow key should set the moving direction of the snake accordingly. This is somehow difficult to test AND it seems to me that it already has some kind of implementation decision in it. It talks about a moving direction. Although the snake obviously moves into a certain direction on the game board, the term moving direction to implies a certain state of a Snake
object.
Wouldn't it make more sense to say Pressing any arrow key should move the snake's head into the according direction with its next move? This brings us to the following questions:
So I would argue that the real test required here is Pressing any arrow key will move the snake towards the implied direction with its next move. This would give me the following direction:
Game::setMovingDirection(...)
Game::executeMove()
Game::getSnakePosition()
Game
will forward the calls towards Snake
. Thus Snake
needs to have a similar interface!globals.h
will provide the Definition of enum class Direction
. Thus Snake
does not depend on Game
and in the next step GameViewModel
can use the Direction
from there as well.GameViewModel
needs to call the new interface of Game
.Game
as this seems to me as the core classSnake
. Those would add (currently) no benefit.GameViewModel
should have similar tests. Even if they are (partially?) duplicate with those in Game
. As the snakecore
library should be independently useable, it should be okay to have test on both?GameViewModel
needs to make the transition from the Key to the Direction
. This should be tested as well!?!GameViewModel
it became also apparent that the Snake
was missing movement of its body.So next stop is:
Snake (head and body) move one tile every 250 ms
GameViewModel::executeMove
. Although I'm not sure whether I really like this way (should a timer be part of snakecore
lib or maybe at least of the view model itself?), I'll stay with it to move ahead with full speed.Game::isStarted() - signal
- While going through this I recognized that we need a trigger to start the game. So probably removing the initial test around this wasn't a good idea!Game
needs a test that its signal isStarted
is emitted once movement changes from NoMove
to something else.Game
starts in NoMove
"state"? If yes, how do I test that?MainWindow
has no tests.MainWindow
(for the moment) although it might be better placed in GameViewModel
.Retrospective:
QSingalSpy
as well as the question where to place the timer confused me. There was no flow at all and I'm still pretty uncertain whether everything works as expected.SnakeView
and integrating it into GameBoardView
was a large chunk. So I should really learn how to use TDD in this UI-centric classes or at least learn how to better make small steps.SnakeView
as a QGraphicsItem
could be used from there.Next step is:
Snake head appears on the contrary side of the game board, if it leaves the game board on one side
GameViewModel
requires similar tests.Snake
requires a setToHeadPosition(QPoint new_position)
for testing.SetToHeadPosition
should also take the current movement direction into account, but for the current test scenarios this is not required. Therefore I skip it so far.Snake
no other changes are required. This is because the GameViewModel
as well as the GameBoardView
simply follow the Model. This is now really nice!Snake::setToHeadPosition()
already felt strange when I decided to do so. If I hadn't done that, the error could not have passed the tests.for
loop, which I usually see as a "test smell").Final step is to update the user message!
GameViewModel::getCurrentUserMessage()
, GameViewModel::processKeyboardAction
as well as the GameViewModel::gameStarted
signal. This allows to transport all required information.GameViewModel::userMessageUpdated()
signal might be a good idea. However, as the required behavior can be realized without it, we add to the todo list, but ignore it for the moment.GameViewModel
as well as MainWindow
both have a startGame
slot. This can be used to transport and update the message accordingly.userMessageUpdated
. However, writing down parts of this comment here reminded me that it is not necessary (at the moment).Copied open todos/issues from this comment to #9
As all acceptance criteria are met and todos are stored, this user story will be closed now.
Notes
Snake
gets a direction when it starts.Open Issues
Snake
has at least one tail element, when it is initially placed on the screen. What happens when the user tries to start the game into the direction of the tail? Should the game immediately end?Snake
even in the direction of its tail. Assuming the tail is only one item at start.Acceptance criteria
Snake
starts moving into the direction of the first pressed arrow keySnake
continuously moves into the direction initially chosen by userSnake
will reappear "on the other side" when hits the end of game board