3
\$\begingroup\$

I'm trying to build a "simple" Tetris game, but I want to have enough flexibility to add a few things:

  • The game can be played by a human, or (if the human presses a certain key) an AI is swapped in.

  • The rendering code should be "configurable". That is, the user may enable or disable animations, or speed them up.

MVC initially looks like a good fit: the game itself is encapsulated in a Game object, providing some methods to interact with it (moveLeft, rotate). The Controller would handle user input or AI. The View just renders the game, and gets notified of updates by the Game itself whenever something interesting happens (PieceMoved, LineDestroyed and so on).

However, my problem mostly lies in "What if I want a blocking animation?"

When lines are destroyed, I want to do a "falling" animation where the top lines crush the bottom lines (or whatever), but since this animation may take some time, I want to avoid updating the game (that is, piece gravity) until the animation is completed.

Does it make sense to have the Controller do something like...

void update() {
    input.process()
    render.update()
    if (!render.blocked()) {
        game.update();
    }
}

Is MVC a bad fit for this? What's a good way to separate concerns in games?

(I am using libGDX, but my question isn't limited to it.)

\$\endgroup\$
4
\$\begingroup\$

I wouldn't bother that much with trying to fit the game in the mold that comes with MVC or any other pattern until I know for sure that it's the simplest way to develop it. Especially since most of the time you'll implement multiple known patterns in the same program, or alter patterns after you notice they don't quite fit in your program the way they were formally described.

Here's how I'd architect Tetris. It contains traces of Strategy.

Put your board in the middle, and let it keep track of the state of things (position of pieces and so on).

class Board
{
    private bool isBusy(); //Don't accept player input, or update non-graphics
                           //make it return true if you're playing an animation.        
    public void Update()
    {
         //Make pieces move and so on, if not isBusy()
    }

    public void ApplyMove(Move move);
}

Define an interface that will be common among board interactors.

interface BoardInteractor
{         
      void Update(Board board);
}

Your player will implement BoardInteractor.

class Player : BoardInteractor
{
       public void Update(Board board)
       {
            if (UserPressedLeft)
            {
                board.ApplyMove(LeftMove);
            }
            //and so on
       }
}

Your AI will also implement BoardInteractor, and will simulate a player.

class AIPlayer : BoardInteractor
{
         public void Update(Board board)
         {
              //Make your AI seem smart here.
              Move move = calculateBestMove(board);            
              board.ApplyMove(move);
         }
}

Cover both things in a Game class that will keep things together.

class Game
{
      private Board _board;
      private BoardInteractor _interactor;

      private void changeInteractor(BoardInteractor newInteractor)
      {
            _interactor = newInteractor;
      }

      public void Start()
      {
            _board = new Board();
            _interactor = new Player();
      }

      public void Update()
      {
            if (UserPressedSpecialKey)
            {
                 changeInteractor(new AIPlayer());
            }
            _board.Update();
            _interactor.Update(_board);
      }
}

I find that I have reached a proper separation of concerns in my games when I can take out as many components of the game as possible, and keep the others running independently (e.g. if you take out the interactors completely, the board can still exist, and on the screen you'll see bricks constantly falling in a particular direction; interactors just give hints to the board).

A goal close to that is to also make components interchangeable. This helps with easy prototyping, testing and extensibility (I can easily define multiple types (with different levels of intelligence?) of Tetris AIs and try them out as I wish, by changing between AIs at runtime, for example).

\$\endgroup\$
  • 1
    \$\begingroup\$ "A goal close to that is to also make components interchangeable." This is precisely why I'm asking; I'm trying to fin an architecture that allows me to do this. There are things I still have questions on. For example: You give "Board" the "isBusy" flag, but that feels awkward: Why make the board know about its renderer (since that seems to be the only reason to pause). How would you design your Renderer? Would the Game know about its Renderer and notify it about changes? "PieceDropped" for example, to trigger the animation/sound for that. \$\endgroup\$ – Tordek Sep 10 '14 at 4:02
  • \$\begingroup\$ I personally find no problem in passing the object that actually does the rendering to each thing that has something to render. Game.Draw(renderingObject) would call Board.Draw(renderingObject) which will call methods like renderingObject.DrawSprite(spriteInfo). I find that keeping the thing that does the rendering completely separate and having it traverse a hierarchy by itself or something like that is too cumbersome to justify the effort. How the rendering object actually does the rendering is separate anyway, so it won't affect clients. \$\endgroup\$ – user15805 Sep 10 '14 at 8:46
  • \$\begingroup\$ I see, so you're sending a Renderer as a Visitor and asking stuff to request its rendering. I'm still confused on how you handle the animations. I'm assuming you'd have the piece hold an "isFalling" such tht, when gravity triggers, it's toggled on, and then when it's time to render, it'll call "renderer.drawFalling(this)"? \$\endgroup\$ – Tordek Sep 10 '14 at 19:45
  • \$\begingroup\$ No, the renderer is dumb and can only display sprites at the position it's being told to draw them at. Each brick does renderer.Draw(texture, position); If you animate a brick while it falls, you just change the texture object whenever you want to advance to the next frame within the brick's logic. \$\endgroup\$ – user15805 Sep 11 '14 at 9:50
  • \$\begingroup\$ Ah, so your entities know what shape they have on-screen (they would also know not to draw themselves should they be off-screen, I assume? (As in, permanent entities, not ones that die offscreen).). Is this typical for games, since the visuals are so coupled to the logic itself? \$\endgroup\$ – Tordek Sep 11 '14 at 10:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.