Como posso usar o Editline para um console no jogo?

9

Gostaria de adicionar um console no jogo a um jogo C ++ que estou criando. Embora renderize o console e analise os comandos com os quais estou bem, o aspecto de entrada e edição de texto (por exemplo, manipulação de teclas esquerda / direita, backspaces etc ...) parece muito esforço que eu prefiro gastar em coisas mais interessantes .

Eu consegui encontrar uma biblioteca chamada Editline que lida com os detalhes da entrada de texto no estilo do console, com histórico e preenchimento automático, no entanto, não tenho certeza de como fazer interface com o meu jogo - em particular todos os exemplos que eu descobri usar as alças de entrada e saída padrão e readline()ela mesma é uma chamada de bloqueio. Preciso fornecer minha própria entrada e renderizar o console para minha própria saída, e também preciso garantir que meu loop de jogo continue em execução.

Como devo fazer isso?

Justin
fonte

Respostas:

4

A biblioteca Editline (e GNU readline) funciona apenas com recursos de terminal. Para usar essas bibliotecas com o console do jogo, você primeiro precisa implementar um emulador de terminal (TTY).

Em vez disso (já que fazer isso em um jogo seria uma loucura), eu recomendaria que você implementasse os movimentos do cursor e se editasse. Certa vez, escrevi uma classe C ++ para lidar com isso no meu próprio console no jogo exatamente para esse fim. A classe é chamada apropriadamente EditLine. A classe lida com movimentos básicos do cursor, exclusões de avanço / retrocesso / palavra / linha e histórico. Nada extravagante, mas você pode achar útil. A documentação é escassa ou inexistente, espero que você possa descobrir de qualquer maneira. Infelizmente, nenhuma conclusão automática. Eu implementei isso em uma camada superior usando essa classe.

editline.h

#include <string>
#include <vector>

class EditLine {
public:
    EditLine();
    EditLine(const EditLine&);

    ~EditLine();

    // assignment operator
    EditLine& operator=(const EditLine&);

    // comparison operators
    bool operator==(const EditLine&) const;
    bool operator!=(const EditLine&) const;

    // edit commands
    void accept_line();
    void reject_line();
    void insert_char(int);
    void delete_char();
    void backward_delete_char();
    void delete_word();
    void backward_delete_word();
    void delete_line();

    // motion commands
    void beginning_of_line();
    void end_of_line();
    void forward_char();
    void backward_char();
    void forward_word();
    void backward_word();

    // history commands
    void next_history();
    void previous_history();

    // accessors
    int history_pos() const;
    inline size_t history_size() const;
    inline bool empty() const;
    inline const std::string& line() const;
    inline size_t length() const;
    inline const std::string::size_type& cursor_pos() const;

    // mutators
    void set_line(const std::string& s);
    void set_cursor_pos(std::string::size_type);
    void reset_line();

private:
    std::string line_;
    std::string::size_type cursor_pos_;
    std::vector< std::string > history_;
    std::vector< std::string >::iterator last_iter_;
};

inline size_t EditLine::history_size() const { return history_.size(); }
inline const std::string& EditLine::line() const { return line_; }
inline const std::string::size_type& EditLine::cursor_pos() const { return cursor_pos_; }
inline bool EditLine::empty() const { return line_.empty(); }
inline size_t EditLine::length() const { return line_.length(); }

editline.cpp

#include "editline.h"
#include "string_utils.h"
#include <string>

namespace {
    const std::string word_delims = " !@#$%^&*()+-={}[]:\"|;'\\<>?,./";
} // namespace

EditLine::EditLine()
    : line_(std::string()),
      cursor_pos_(0),
      last_iter_(history_.end())
{

}

EditLine::EditLine(const EditLine& rhs)
{
    *this = rhs;
}

EditLine::~EditLine()
{

}

EditLine& EditLine::operator=(const EditLine& rhs)
{
    line_ = rhs.line_;
    cursor_pos_ = rhs.cursor_pos_;
    history_ = rhs.history_;
    if (rhs.last_iter_ == rhs.history_.end())
        last_iter_ = history_.end();
    else {
        last_iter_ = history_.begin();
        std::advance(last_iter_, rhs.last_iter_ - rhs.history_.begin());
    }
    return *this;
}

void EditLine::set_line(const std::string& s)
{
    line_ = s;
    cursor_pos_ = line_.size();
}

void EditLine::set_cursor_pos(std::string::size_type pos)
{
    if (pos > line_.size())
        pos = line_.size();
    cursor_pos_ = pos;
}

void EditLine::reset_line()
{
    line_.clear();
    cursor_pos_ = 0;
}

bool EditLine::operator==(const EditLine& rhs) const
{
    return (line_         == rhs.line_       &&
            cursor_pos_   == rhs.cursor_pos_ &&
            history_      == rhs.history_    &&
            history_pos() == rhs.history_pos());
}

bool EditLine::operator!=(const EditLine& rhs) const
{
    return !operator==(rhs);
}

void EditLine::accept_line()
{
    if (!line_.empty())
        history_.push_back(line_);

    line_.clear();
    cursor_pos_ = 0;
    last_iter_ = history_.end();
}

void EditLine::reject_line()
{
    line_.clear();
    cursor_pos_ = 0;
    last_iter_ = history_.end();
}

void EditLine::insert_char(int c)
{
    line_.insert(cursor_pos_, 1, c);
    cursor_pos_++;
}

void EditLine::delete_char()
{
    line_.erase(cursor_pos_, 1);
}

void EditLine::backward_delete_char()
{
    if (cursor_pos_ > 0) {
        line_.erase(cursor_pos_ - 1, 1);
        cursor_pos_--;
    }
}

void EditLine::delete_word()
{
    std::string::size_type pos;

    // check if cursor points on a word delim
    if (word_delims.find(line_[cursor_pos_]) != std::string::npos) {
        // cursor points on a word delim - remove everything from here to
        // right up to first delim after this word.
        pos = line_.find_first_not_of(word_delims, cursor_pos_+1);
        if (pos != std::string::npos)
            pos = line_.find_first_of(word_delims, pos+1);
    } else {
        // cursor is in the middle of a word - remove everything up to first
        // delim.
        pos = line_.find_first_of(word_delims, cursor_pos_+1);
    }

    if (pos != std::string::npos)
        // removes everything right of cursor up to 'pos'
        line_.replace(cursor_pos_, pos - cursor_pos_, "");
    else
        // removes everthing right of cursor
        line_.erase(cursor_pos_);
}

void EditLine::backward_delete_word()
{
    std::string::size_type pos;

    if (cursor_pos_ == 0) return;

    // check if char left of cursor is a word delim
    if (word_delims.find(line_[cursor_pos_-1]) != std::string::npos) {
        // left of cursor is a word delim - remove everything from left of
        // cursor up to next word delim before current word.
        pos = rfind_first_not_of(line_, word_delims, cursor_pos_-1);
        if (pos != std::string::npos)
            pos = rfind_first_of(line_, word_delims, pos);
    } else {
        // left of cursor is not a word delim - remove everything left of
        // cursor up to next word delim.
        pos = rfind_first_of(line_, word_delims, cursor_pos_-1);
    }

    if (pos != std::string::npos) {
        // removes from right of pos and up to cursor
        line_.erase(pos + 1, cursor_pos_ - pos - 1);
        cursor_pos_ = pos + 1;
    } else {
        // removes everything from beginning up to cursor
        line_.erase(0, cursor_pos_);
        cursor_pos_ = 0;
    }
}

void EditLine::delete_line()
{
    line_.erase(cursor_pos_);
    cursor_pos_ = line_.size();
}

void EditLine::beginning_of_line()
{
    cursor_pos_ = 0;
}

void EditLine::end_of_line()
{
    cursor_pos_ = line_.size();
}

void EditLine::forward_char()
{
    if (cursor_pos_ < line_.size())
        cursor_pos_++;
}

void EditLine::backward_char()
{
    if (cursor_pos_ > 0)
        cursor_pos_--;
}

void EditLine::forward_word()
{
    std::string::size_type pos;

    pos = line_.find_first_of(word_delims, cursor_pos_);

    if (pos != std::string::npos)
        cursor_pos_ = pos + 1;
    else
        cursor_pos_ = line_.size();
}

void EditLine::backward_word()
{
    if (cursor_pos_ <= 1) {
        cursor_pos_ = 0;
        return;
    }

    std::string::size_type pos, cursor_pos;

    cursor_pos = cursor_pos_;

    if (cursor_pos >= 2)
        cursor_pos -= 2;

    bool found = false;

    for (int i = (int) cursor_pos; i >= 0; --i) {
        if (word_delims.find(line_[i]) != std::string::npos) {
            pos = (std::string::size_type) i;
            found = true;
            break;
        }
    }

    if (found)
        cursor_pos_ = pos + 1;
    else
        cursor_pos_ = 0;
}

void EditLine::next_history()
{
    if (!history_.empty()) {
        if (last_iter_ == history_.end() || ++last_iter_ == history_.end())
            last_iter_ = history_.begin();
        line_ = *last_iter_;
        cursor_pos_ = line_.size();
    }
}

void EditLine::previous_history()
{
    if (!history_.empty()) {
        if (last_iter_ != history_.begin())
            --last_iter_;
        else
            last_iter_ = --history_.end();

        line_ = *last_iter_;
        cursor_pos_ = line_.size();
    }
}

int EditLine::history_pos() const
{
    if (last_iter_ == history_.end())
        return -1;
    return last_iter_ - history_.begin();
}

string_utils.h

std::string::size_type rfind_first_not_of(const std::string& s,
                                          const std::string& delims,
                                          std::string::size_type pos);

std::string::size_type rfind_first_of(const std::string& s,
                                      const std::string& delims,
                                      std::string::size_type pos);

string_utils.cpp

std::string::size_type rfind_first_not_of(const std::string& s,
        const std::string& delims, std::string::size_type pos)
{
    std::string::size_type p = pos;
    while (delims.find(s[p]) != std::string::npos) {
        if (p == 0) // not found
            return std::string::npos;
        --p;
    }
    return p;
}

std::string::size_type rfind_first_of(const std::string& s,
        const std::string& delims, std::string::size_type pos)
{
    std::string::size_type p = pos;
    while (delims.find(s[p]) == std::string::npos) {
        if (p == 0) // not found
            return std::string::npos;
        --p;
    }
    return p;
}
Oskar N.
fonte
0

Não parece ser um problema demais. A primeira coisa que você precisa saber é que o padrão de entrada e saída são apenas fluxos - eles podem ser substituídos pelos seus, se necessário. Havia uma pergunta sobre SO sobre isso, e no AFAIK, as duas únicas maneiras são entrar no código do Editline e alterá-lo para usar seus fluxos ou usar cin.rdbuf()e cout.rdbuf()- estes são fornecidos pela biblioteca padrão e a documentação pode ser encontrada aqui . Pessoalmente, eu recomendaria o uso deste último - não é uma brincadeira e envolve apenas a adição de duas linhas de código ao seu jogo.

Polar
fonte
Só que libedit também depende ncurses e requer uma implementação de baixo do terminal completo (TTY) ...
Oskar N.