Почему в C++ нужно строить всю программу на ООП (длинный вопрос)?
Задача ООП: 1) Локализовать изменения состояния объекта (инкапсуляция); 2) связывать разные кирпичики данных через стандартные интерфейсы (полиморфизм).
Простейший тетрис не слишком велик, чтобы его писать на чистом ООП.
Но представьте себе, мы начинаем налаживать настраиваемое управление джойстиком или клавиатурой. И тогда у нас появляется такой код.enum {
  BT_LEFT = 1,
  BT_RIGHT = 2,
  BT_ROTATE = 4,
  BT_SOFTDROP = 8,
  BT_HARDDROP = 16,
  BT_PAUSE = 32,
  BT_CONNECTED = 32768,   // бит, указывающий, что контроллер подключён
};
class Controller {  // интерфейс
public:
  virtual unsigned poll() const = 0;   // сочетание битов BT_XXX
  virtual ~Controller = default;
};
Классы Keyboard и Joystick поддерживают интерфейс Controller, и подмена клавиатуры на джойстик и наоборот ничего не изменит.
Вот вам полиморфизм.
Текстовый редактор превращаем в многооконный — берём класс Editor и пристраиваем его не к программе в целом, а к MDI-окошку. Вот вам инкапсуляция — локализованное изменение состояния.
Я как-то мучил движок Doom. Он написан в самом настоящем объектном стиле на чистом Си! Хотя и там были проблемы: сетевой код был куда хуже по качеству, чем сам движок. Писали разделённый экран, глобальную переменную netgame разделили на две, multiplayer и netgame и долго-долго правили баги, где multiplayer, где netgame (было дело, участник десматча ввёл IDKFA, это сработало и вызвало рассинхронизацию). А код пользовательского интерфейса — вообще медвежуть!
Что касается наследования и полиморфизма, то в них возникает потребность очень редко
Вот только прочитав это можно понять, что всей сути ООП вы и не поняли. У меня есть один проект (60Кб в собранной прошивке) для железа под ARM процессор на C. Проект так вырос, что подумываю в будещем реализовать это же самое на C++ с использованием ООП.