diff --git a/luprex/cpp/luaconsole.cpp b/luprex/cpp/luaconsole.cpp index 1af07375..c3733f22 100644 --- a/luprex/cpp/luaconsole.cpp +++ b/luprex/cpp/luaconsole.cpp @@ -12,12 +12,32 @@ LuaConsole::~LuaConsole() { } void LuaConsole::clear() { - expression_ = ""; + raw_input_ = ""; lines_ = 0; + lua_expression_ = ""; + words_.clear(); syntax_ = ""; action_ = DO_NOTHING; } - + +void LuaConsole::split_words() { + words_.clear(); + std::string acc; + for (char c : raw_input_) { + if ((c == ' ')||(c == '\n')) { + if (!acc.empty()) { + words_.push_back(acc); + acc = ""; + } + } else { + acc += c; + } + } + if (!acc.empty()) { + words_.push_back(acc); + } +} + void LuaConsole::add(std::string line) { if (action_ != DO_NOTHING) { clear(); @@ -25,30 +45,42 @@ void LuaConsole::add(std::string line) { for (int i = 0; i < int(line.size()); i++) { if (line[i] == '\n') line[i] = ' '; } - expression_ += line; - expression_ += '\n'; + raw_input_ += line; + raw_input_ += '\n'; lines_ += 1; + char cmd = raw_input_[0]; - // Convert leading = to 'return' - if (expression_[0] == '=') { - expression_ = std::string("return ") + expression_.substr(1); + // All commands are a single character. + if ((raw_input_[1] != ' ')&&(raw_input_[1] != '\n')) { + action_ = DO_SYNTAX; + syntax_ = "Commands are a single character followed by arguments"; + return; } - // Analyze slash-commands. - if (expression_[0] == '/') { + // Analyze non-lua commands. + if ((cmd != 'r') && (cmd != 'l')) { + lua_expression_ = ""; if (lines_ == 1) { - action_ = DO_SLASH; - syntax_ = ""; + action_ = DO_COMMAND; + split_words(); } else { action_ = DO_SYNTAX; syntax_ = "slash command has more than one line"; } return; } + + // Strip the leading punctuation from lua commands. + std::string partial; + if (cmd == 'r') { + partial = std::string("return ") + raw_input_.substr(2); + } else { + partial = raw_input_.substr(2); + } // Analyze lua expressions. int top = lua_gettop(lua_state_); - int status = luaL_loadbuffer(lua_state_, expression_.c_str(), expression_.size(), "=stdin"); + int status = luaL_loadbuffer(lua_state_, partial.c_str(), partial.size(), "=stdin"); if (status == LUA_ERRSYNTAX) { const char *eof = "''"; @@ -56,15 +88,14 @@ void LuaConsole::add(std::string line) { const char *msg = lua_tolstring(lua_state_, -1, &lmsg); const char *tp = msg + lmsg - (sizeof(eof) - 1); if (strstr(msg, eof) == tp) { - syntax_ = ""; action_ = DO_NOTHING; } else { - syntax_ = msg; action_ = DO_SYNTAX; + syntax_ = msg; } } else { - syntax_ = ""; - action_ = DO_COMMAND; + action_ = DO_LUA; + lua_expression_ = partial; } lua_settop(lua_state_, top); } @@ -75,7 +106,7 @@ void LuaConsole::add_stdin() { } const int MAXINPUT = 1000; char buf[MAXINPUT]; - fputs(expression_.empty() ? "> " : ">> ", stdout); + fputs(raw_input_.empty() ? "> " : ">> ", stdout); fflush(stdout); if (fgets(buf, MAXINPUT, stdin)) { size_t len = strlen(buf); diff --git a/luprex/cpp/luaconsole.hpp b/luprex/cpp/luaconsole.hpp index 1e5fdcaa..4c2bdeeb 100644 --- a/luprex/cpp/luaconsole.hpp +++ b/luprex/cpp/luaconsole.hpp @@ -2,20 +2,23 @@ // // LuaConsole: // -// Used to parse lines of text that are being interactively typed +// Used to parse commands that are being interactively typed // in by a user. // -// Whenever the user types in a line, you should add the line to -// the LuaConsole object. After adding a line, the console will -// recommend one of several actions: +// The command syntax is always a single character command, +// followed by arguments. Only two commands are hardwired: // -// 1. DO_NOTHING: The input is not complete, so you need to wait. -// 2. DO_COMMAND: Execute a lua command. -// 3. DO_SYNTAX: Print a syntax error message. -// 4. DO_SLASH: Execute a slash-command that bypasses lua. +// l - evaluate lua expression +// r - evaluate lua expression with 'return' prepended // -// Also: if the first character of the lua form is '=', then -// replace the '=' with 'return '. +// The console expects you to add lines of text one at a time. +// After adding a line, the console will recommend one of these +// actions: +// +// 1. DO_NOTHING: Add more lines of text. +// 2. DO_LUA: Evaluate a lua expression. +// 3. DO_OTHER: Execute a non-lua command. +// 4. DO_SYNTAX: Print a lua syntax error. // ////////////////////////////////////////////////////// @@ -29,28 +32,46 @@ class LuaConsole { public: enum { DO_NOTHING, // We need more input text. - DO_COMMAND, // We have a valid lua expression. + DO_COMMAND, // We have a valid non-lua command. + DO_LUA, // We have a valid lua expression. DO_SYNTAX, // We have a syntax error. - DO_SLASH, // We have a slash-command. }; + using StringVec = std::vector; + private: lua_State *lua_state_; int action_; + std::string raw_input_; int lines_; - std::string expression_; + std::string lua_expression_; + StringVec words_; std::string syntax_; + void split_words(); + public: LuaConsole(); ~LuaConsole(); + // Get the recommended action. int action() const { return action_; } - int lines() const { return lines_; } - const std::string &expression() const { return expression_; } + + // When action is DO_COMMAND, get the command words. + const StringVec &words() const { return words_; } + + // When action is DO_LUA, get the valid lua expression. + std::string lua_expression() const { return lua_expression_; } + + // When action is DO_SYNTAX, get the syntax error. const std::string &syntax() const { return syntax_; } + // Add a line of text that was just read from the console. void add(std::string line); + + // Read a line of text from stdin and add it. void add_stdin(); + + // Clear the state. void clear(); }; diff --git a/luprex/cpp/source.cpp b/luprex/cpp/source.cpp index 032be5fb..b143a777 100644 --- a/luprex/cpp/source.cpp +++ b/luprex/cpp/source.cpp @@ -14,6 +14,23 @@ #include "table.hpp" #include "source.hpp" +// Read control.lst +// +// - trim all lines +// - remove blank lines +// - remove comment lines +// +util::stringvec read_control_lst(const std::string &path) { + util::stringvec lines = util::get_file_lines(path); + util::stringvec result; + for (int i = 0; i < int(lines.size()); i++) { + std::string trimmed = util::trim(lines[i]); + if ((trimmed.size() > 0) && (trimmed[0] != '#')) { + result.push_back(trimmed); + } + } + return result; +} LuaDefine(source_makeclass, "f") { LuaArg classname; @@ -158,8 +175,7 @@ void SourceDB::update() { } // Read the list of filenames. - std::string ctrl = "lua/control.lst"; - util::stringvec filenames = util::trim_and_uncomment(util::read_lines(ctrl)); + util::stringvec filenames = read_control_lst("lua/control.lst"); if (filenames.empty()) { luaL_error(L, "cannot read source database control.lst"); } diff --git a/luprex/cpp/textgame.cpp b/luprex/cpp/textgame.cpp index 3471a219..714e69d1 100644 --- a/luprex/cpp/textgame.cpp +++ b/luprex/cpp/textgame.cpp @@ -43,8 +43,8 @@ static void l_message(const char *msg) } -static void doexp(lua_State *L, const std::string &exp) -{ +void TextGame::do_lua(const std::string &exp) { + lua_State *L = viewer_.get_lua_state(); int status = luaL_loadbuffer(L, exp.c_str(), exp.size(), "=stdin"); assert(status == LUA_OK); globalL = L; @@ -76,18 +76,66 @@ static void doexp(lua_State *L, const std::string &exp) } } +void TextGame::do_view_command(const StringVec &cmd) { + if (cmd.size() != 1) { + std::cerr << "v command (view) takes no arguments" << std::endl; + return; + } + viewer_.update_view_map(); + for (auto viewpair : viewer_.get_view_map()) { + int64_t id = viewpair.first; + AnimView &view = viewpair.second; + std::cerr << id << ": " << view.get_graphic() << " " << view.get_xyz() << std::endl; + } +} + +void TextGame::do_menu_command(const StringVec &cmd) { + int64_t id; + if (cmd.size() == 1) { + id = 1; + } else if (cmd.size() == 2) { + id = util::strtoint(cmd[1], -1); + } else { + std::cerr << "m command (menu) expects a tangible ID or defaults to 1" << std::endl; + return; + } + + std::cerr << "Menu command (id " << id << ") not implemented yet." << std::endl; +} + +void TextGame::do_choose_command(const StringVec &cmd) { + int64_t index; + if (cmd.size() == 2) { + index = util::strtoint(cmd[1], -1); + } else { + std::cerr << "c command (choose) expects a menu line number" << std::endl; + return; + } + std::cerr << "Choose command (index " << index << ") not implemented yet." << std::endl; +} + +void TextGame::do_command(const StringVec &words) { + switch (words[0][0]) { + case 'v': do_view_command(words); break; + case 'm': do_menu_command(words); break; + case 'c': do_choose_command(words); break; + default: + std::cerr << "Unknown command: " << words[0] << std::endl; + } +} + void TextGame::run() { - LuaConsole console; + console_.clear(); while (true) { - console.add_stdin(); - int action = console.action(); - if (action == LuaConsole::DO_COMMAND) { - doexp(viewer_.get_lua_state(), console.expression()); - } else if (action == LuaConsole::DO_SLASH) { - std::cerr << "Slash commands not supported yet." << std::endl; + console_.add_stdin(); + int action = console_.action(); + if (action == LuaConsole::DO_LUA) { + do_lua(console_.lua_expression()); + } else if (action == LuaConsole::DO_COMMAND) { + do_command(console_.words()); } else if (action == LuaConsole::DO_SYNTAX) { - std::cerr << console.syntax() << std::endl; + std::cerr << console_.syntax() << std::endl; } } } diff --git a/luprex/cpp/textgame.hpp b/luprex/cpp/textgame.hpp index 86421902..9e7b9fed 100644 --- a/luprex/cpp/textgame.hpp +++ b/luprex/cpp/textgame.hpp @@ -3,10 +3,23 @@ #define TEXTGAME_HPP #include "viewer.hpp" +#include "luaconsole.hpp" class TextGame { private: + using StringVec = LuaConsole::StringVec; Viewer viewer_; + LuaConsole console_; + int64_t menu_id_; + std::vector menu_; + + + void do_view_command(const StringVec &cmd); + void do_menu_command(const StringVec &cmd); + void do_choose_command(const StringVec &cmd); + + void do_lua(const std::string &exp); + void do_command(const StringVec &exp); public: void run(); }; diff --git a/luprex/cpp/util.cpp b/luprex/cpp/util.cpp index 478127ef..f0c0ae0d 100644 --- a/luprex/cpp/util.cpp +++ b/luprex/cpp/util.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "util.hpp" #include #include @@ -11,8 +12,41 @@ namespace util { -// Read a file as lines. -const stringvec read_lines(const std::string &path) { +int64_t strtoint(const std::string &value, int64_t errval) { + char *endptr; + int64_t result = strtoll(value.c_str(), &endptr, 10); + if (endptr == value.c_str() + value.size()) { + return result; + } else { + return errval; + } +} + +std::string ltrim(std::string s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), + std::not1(std::ptr_fun(std::isspace)))); + return s; +} + +std::string rtrim(std::string s) { + s.erase(std::find_if(s.rbegin(), s.rend(), + std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; +} + +std::string trim(std::string s) { + return ltrim(rtrim(s)); +} + + +std::string get_file_contents(const std::string &fn) { + std::ifstream fs(fn); + std::stringstream buffer; + buffer << fs.rdbuf(); + return buffer.str(); +} + +stringvec get_file_lines(const std::string &path) { stringvec result; std::ifstream f; f.open(path); @@ -26,7 +60,6 @@ const stringvec read_lines(const std::string &path) { return result; } -// Get a string encoding the modification time and size of the file. std::string get_file_fingerprint(const std::string &fn) { struct stat result; if(stat(fn.c_str(), &result)==0) @@ -38,34 +71,13 @@ std::string get_file_fingerprint(const std::string &fn) { return ""; } -std::string get_file_contents(const std::string &fn) { - std::ifstream fs(fn); - std::stringstream buffer; - buffer << fs.rdbuf(); - return buffer.str(); -} - - -// Strip leading and trailing whitespace and comments. -const stringvec trim_and_uncomment(const stringvec &lines) { - stringvec result; - for (int i = 0; i < int(lines.size()); i++) { - std::string trimmed = trim(lines[i]); - if ((trimmed.size() > 0) && (trimmed[0] != '#')) { - result.push_back(trimmed); - } - } - return result; -} - double distance_squared(double x1, double y1, double x2, double y2) { double dx = x1 - x2; double dy = y1 - y2; return dx*dx + dy*dy; } -// These are Bob Jenkins' Lookup3. - +// These hash functions are part of Bob Jenkins' Lookup3. #define hash_rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) #define hash_mix(a,b,c) { \ @@ -94,9 +106,15 @@ uint32_t hash3(uint32_t a, uint32_t b, uint32_t c) { double hash_to_float(double lo, double hi, uint32_t a, uint32_t b, uint32_t c) { double result = hash3(a, b, c); // Lossless. - result *= ((hi-lo) * (1.0 / 0xFFFFFFFF)); + result *= (1.0 / 0xFFFFFFFF); + result *= (hi-lo); result += lo; return result; } +std::ostream & operator << (std::ostream &out, const XYZ &xyz) { + out << "(" << xyz.x << "," << xyz.y << "," << xyz.z << ")"; + return out; +} + } // namespace util diff --git a/luprex/cpp/util.hpp b/luprex/cpp/util.hpp index 3218c544..2db747d6 100644 --- a/luprex/cpp/util.hpp +++ b/luprex/cpp/util.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -13,44 +14,40 @@ namespace util { using stringvec = std::vector; using stringset = std::set; -// trim from start -static inline std::string ltrim(std::string s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), - std::not1(std::ptr_fun(std::isspace)))); - return s; -} +// String to integer. Returns errval if the number is not parseable. +int64_t strtoint(const std::string &value, int64_t errval); -// trim from end -static inline std::string rtrim(std::string s) { - s.erase(std::find_if(s.rbegin(), s.rend(), - std::not1(std::ptr_fun(std::isspace))).base(), s.end()); - return s; -} +// Trim strings: left end, right end, both ends. +std::string ltrim(std::string s); +std::string rtrim(std::string s); +std::string trim(std::string s); -// trim from both ends -static inline std::string trim(std::string s) { - return ltrim(rtrim(s)); -} +// Read a file as one big string. +std::string get_file_contents(const std::string &path); -const stringvec read_lines(const std::string &path); -const stringvec trim_and_uncomment(const stringvec &lines); +// Read a file as a vector of lines. +stringvec get_file_lines(const std::string &path); + +// Get a file's fingerprint - ie, size and modification time. std::string get_file_fingerprint(const std::string &path); -std::string get_file_contents(const std::string &fn); +// Calculate distance between two points double distance_squared(double x1, double y1, double x2, double y2); // Return a pseudorandom number which is a hash function of A,B,C. uint32_t hash3(uint32_t a, uint32_t b, uint32_t c); -// Returns a floating point value between lo and hi inclusive. +// Return a pseudorandom float between lo and hi inclusive. double hash_to_float(double lo, double hi, uint32_t a, uint32_t b, uint32_t c); +// An XYZ coordinate, general purpose. struct XYZ { float x, y, z; XYZ() { x=0; y=0; z=0; } XYZ(float ix, float iy, float iz) { x=ix; y=iy; z=iz; } bool operator ==(const XYZ &o) { return x==o.x && y == o.y && z==o.z; } }; - +std::ostream & operator << (std::ostream &out, const XYZ &xyz); + } // namespace util #endif // UTIL_HPP diff --git a/luprex/cpp/viewer.cpp b/luprex/cpp/viewer.cpp index 267c81b1..6b706ceb 100644 --- a/luprex/cpp/viewer.cpp +++ b/luprex/cpp/viewer.cpp @@ -25,9 +25,3 @@ std::vector Viewer::get_menu(int64_t id) { result.push_back("destroy"); return result; } - -void Viewer::choose_menu_item(int64_t id, const std::string &item) { -} - -void Viewer::advance_clock() { -}; diff --git a/luprex/cpp/viewer.hpp b/luprex/cpp/viewer.hpp index 111857b0..3f6d27dd 100644 --- a/luprex/cpp/viewer.hpp +++ b/luprex/cpp/viewer.hpp @@ -30,19 +30,11 @@ public: // Get the view map. // - const AnimViewMap &get_view_map() { return view_map_; } + AnimViewMap &get_view_map() { return view_map_; } // Get the menu of the specified tangible. // std::vector get_menu(int64_t id); - - // Select the specified menu item. - // - void choose_menu_item(int64_t id, const std::string &item); - - // Advance the simulation clock. Called 1/sec. - // - void advance_clock(); }; #endif // VIEWER_HPP diff --git a/luprex/cpp/world.cpp b/luprex/cpp/world.cpp index ac060655..795c8ec9 100644 --- a/luprex/cpp/world.cpp +++ b/luprex/cpp/world.cpp @@ -48,6 +48,9 @@ World::World() { void Tangible::be_a_player() { if (id_player_pool_ == nullptr) { id_player_pool_.reset(new IdPlayerPool(&world_->id_global_pool_)); + + anim_queue_.add(world_->id_global_pool_.get_one(), ""); + anim_queue_.set_graphic("player"); } }