FTowerX

FTowerX

A few years ago, I was developing a program for remotely controlling a signal generator. I wanted to create a simple user interface and have maximum portability. The program was intended as an in-house testing tool, so I didn't want to invest much time in it. I ended up using Qt.

Later, I came across a fantastic library for implementing text-based UIs in terminal applications called FTXUI (Functional Terminal (X) User interface). This weekend, I found the opportunity to explore that library and its capabilities through developing my favorite game: Tower of Hanoi.

This is an open-source library, and I suggest visiting their repository. There, you can find a more comprehensive showcase of its functionality. However, in this article, I explain my implementation of the game.

I started with defining the Disk class:

class Disk {
public:
  Disk(size_t size) : m_size{size} {}
  size_t getSize() const { return m_size; }

private:
  size_t m_size{};
};

bool operator<(const Disk &lhs, const Disk &rhs) {
  return lhs.getSize() < rhs.getSize();
}        

After that, I continued creating a Uniform Stack, where you can only push elements smaller than the previous element.

template <typename T, class Compare = std::less<>> class UniformStack {
public:
  T pop() {
    if (m_stack.empty()) {
      throw std::out_of_range("calling pop on an empty UniformStack");
    }
    auto var{std::move(m_stack.back())};
    m_stack.pop_back();
    return var;
  };
  
  std::optional<T> push(T &&var) {
    if (!m_stack.empty() && !Compare{}(var, m_stack.back()))
      return std::move(var);
    m_stack.push_back(std::move(var));
    return {};
  };
  ...
};        

I used three of these stacks to create the GameOfTOH, where you select a disk and if possible, move it to another stack.

void selectOrMoveToTower(TOWERS index) {
  if (index == TOWER_END) {
    m_srcTower = nullptr;
    return;
  }
  if (m_srcTower && moveDisk(m_srcTower, &m_towers[index])) {
    m_srcTower = nullptr;
  } else {
    if (!m_towers[index].empty()) {
      m_srcTower = &m_towers[index];
    }
  }
}

bool moveDisk(Stack *src, Stack *dst) {
  if (!src->empty()) {
    auto disk{src->pop()};
    std::optional<std::unique_ptr<Disk>> rejectedDisk{};
    if (rejectedDisk = dst->push(std::move(disk))) {
      src->push(std::move(*std::move(rejectedDisk)));
      return false;
    }
    return true;
  }
  return false;
}        

Now, I needed a way to represent the stacks using FTXUI. I wrote a function that takes a stack and returns an FTXUI::vbox consisting of text elements where I used background color to present disks.

std::vector<Element> disks{};
disks.push_back(filler());
for (int i = stack.size() - 1; i >= 0; i--) {
  disks.push_back(text(std::string(stack[i]->getSize() * 2, ' ')) | bgcolor(colorMap.at(stack[i]->getSize() % 10)) | center);
  if (isSrc && disks.size() == 2) {
    disks.push_back(filler());
  }
}
return vbox(disks);        

In the main loop, I draw each stack next to another and also capture keyboard strokes to move the disks between them:

return vbox(
        hbox(printTower(got.getTower(GameOfTOH::TOWER_1),
                        got.isSourceTower(GameOfTOH::TOWER_1)) |
                 flex | ftxui::size(WIDTH, EQUAL, Terminal::Size().dimx),
             separator(),
             printTower(got.getTower(GameOfTOH::TOWER_2),
                        got.isSourceTower(GameOfTOH::TOWER_2)) |
                 flex | ftxui::size(WIDTH, EQUAL, Terminal::Size().dimx),
             separator(),
             printTower(got.getTower(GameOfTOH::TOWER_3),
                        got.isSourceTower(GameOfTOH::TOWER_3)) |
                 flex | ftxui::size(WIDTH, EQUAL, Terminal::Size().dimx)) |
            border
});

auto component = CatchEvent(renderer, [&](Event event) {
  if (event == Event::Character('q')) {
    screen.ExitLoopClosure()();
    return true;
  }
  if (event == Event::ArrowLeft) {
    got.selectOrMoveToTower(GameOfTOH::TOWER_1);
  }
  if (event == Event::ArrowDown) {
    got.selectOrMoveToTower(GameOfTOH::TOWER_2);
  }
  if (event == Event::ArrowRight) {
    got.selectOrMoveToTower(GameOfTOH::TOWER_3);
  }
  if (event == Event::Escape) {
    got.selectOrMoveToTower(GameOfTOH::TOWER_END);
  }
  return false;
});        

As simple as that.

Working with this library was a joy, and it offers much more than I have shown here. I hope you find it useful. You can review my code at: https://github.com/MhmRhm/FTowerX


Thank you, Mohammad. It was helpful.

To view or add a comment, sign in

More articles by Mohammad Rahimi

  • Programming Policies

    Picture this situation: you've got a continuous flow of data coming in, and your task is to process it and present the…

  • Exception x Exception

    In my previous post, we demystified std::forward. In this one, we'll explore what happens when you encounter another…

  • Forward Demystified

    In this post I have shed some light on one of the darker corners of the C++ language: std::forward. The code snippets…

  • Maintainer's Dream

    The distributed workflow for Linux source code development is called Benevolent Dictator. Some maintainers collect…

    2 Comments
  • GitCheat

    In my previous post, I introduced a long-existing tool that helps you migrate your workflow from Perforce to Git. Here,…

  • Perforce meets Git

    Perforce is a Centralised Version Control System founded in 1995 and used by many companies. Git came into existence…

  • In Mail IP

    I was planning to run a home server and access it remotely. But there was an issue.

    2 Comments
  • StateBench

    While developing software, from large to small scale, you can use state machines to reduce code complexity and help…

  • Dot Bash History

    I have prepared a GitHub repository where you can find many useful commands and scripts. The following explains how you…

  • C++ and Benchmarking

    It may sound extreme, but I believe benchmarking too should get the special treatment of testing in a project. There…

Explore content categories