bot with mentions; recover gamestate from message
This commit is contained in:
parent
abcfa64409
commit
36f27d8235
2
TAPI
2
TAPI
|
@ -1 +1 @@
|
|||
Subproject commit 7a7a3840da75f2287a2e7e7b858f71194c8a7b1f
|
||||
Subproject commit 1d99dc3e99afb192299e85804a2e6b194c480bf6
|
|
@ -26,8 +26,7 @@ private:
|
|||
|
||||
void updateGame(int64_t chatid, uint64_t msgid, const Game* g);
|
||||
TelegramAPI::InlineKeyboard gameToKeyboard(const Game*) const;
|
||||
std::string gameToMessage(const Game*) const;
|
||||
std::string gameToMessage(const Game*, std::vector<TelegramAPI::MessageEntity>& entites) const;
|
||||
|
||||
// maps <chatid, messageid> -> gameptr
|
||||
std::map<std::pair<int64_t, uint64_t>, Game*> games;
|
||||
Game* messageToGame(const TelegramAPI::Message& msg) const;
|
||||
};
|
|
@ -11,21 +11,40 @@ public:
|
|||
B = 2
|
||||
};
|
||||
|
||||
struct UserInfo {
|
||||
uint64_t id = 0;
|
||||
std::string name;
|
||||
std::string username;
|
||||
|
||||
std::string getUsernameOrName() const;
|
||||
|
||||
constexpr operator bool() const {
|
||||
return id != 0 || !username.empty();
|
||||
}
|
||||
|
||||
bool operator==(const UserInfo& other) const;
|
||||
|
||||
constexpr bool operator!=(const UserInfo& other) const {
|
||||
return other.id != id && other.username != username;
|
||||
}
|
||||
};
|
||||
|
||||
Game();
|
||||
|
||||
void setMessageID(uint32_t messageid);
|
||||
void setPlayerA(const UserInfo& player);
|
||||
void setPlayerB(const UserInfo& player);
|
||||
bool addPlayer(const UserInfo& player);
|
||||
|
||||
void setPlayerA(uint64_t player);
|
||||
void setPlayerB(uint64_t player);
|
||||
bool addPlayer(uint64_t player);
|
||||
|
||||
uint64_t getPlayerA() const;
|
||||
uint64_t getPlayerB() const;
|
||||
const UserInfo& getPlayerA() const;
|
||||
const UserInfo& getPlayerB() const;
|
||||
|
||||
// true = next turn is playerA, false = next turn is playerB
|
||||
constexpr bool getNextTurn() const {
|
||||
return nextturna;
|
||||
}
|
||||
constexpr void setNextTurn(bool isA) {
|
||||
nextturna = isA;
|
||||
}
|
||||
|
||||
// are both player set and the game is not over?
|
||||
bool ready() const;
|
||||
|
@ -33,14 +52,18 @@ public:
|
|||
// is the game over? (won / full)
|
||||
bool done() const;
|
||||
|
||||
// is atleast one space free?
|
||||
bool isFull() const;
|
||||
|
||||
// do one turn
|
||||
// returns false if the player doing this turn is not allowed or the game is done
|
||||
bool turn(uint_fast8_t x, uint_fast8_t y, uint64_t player);
|
||||
bool turn(uint_fast8_t x, uint_fast8_t y, const UserInfo& player);
|
||||
|
||||
// get the winner or empty string if game is not over yet
|
||||
const std::string& getWinner() const;
|
||||
|
||||
SYM getPos(uint_fast8_t x, uint_fast8_t y) const;
|
||||
void setPos(uint_fast8_t x, uint_fast8_t y, Game::SYM);
|
||||
constexpr uint_fast8_t getSize() const { return SIZE; }
|
||||
|
||||
private:
|
||||
|
@ -61,9 +84,8 @@ private:
|
|||
|
||||
SYM checkWinner() const;
|
||||
|
||||
uint32_t messageid = 0;
|
||||
uint64_t playerA = 0;
|
||||
uint64_t playerB = 0;
|
||||
UserInfo playerA;
|
||||
UserInfo playerB;
|
||||
|
||||
// true = next turn is playerA, false = next turn is playerB
|
||||
bool nextturna = true;
|
||||
|
|
173
src/bot.cpp
173
src/bot.cpp
|
@ -4,11 +4,7 @@
|
|||
|
||||
Bot::Bot(TelegramAPI::Manager* tapi, Conf& conf) : tapi(tapi), conf(conf) {}
|
||||
|
||||
Bot::~Bot() {
|
||||
for(auto it : games) {
|
||||
delete it.second;
|
||||
}
|
||||
}
|
||||
Bot::~Bot() {}
|
||||
|
||||
bool Bot::handleStart(TelegramAPI::Manager* api, const TelegramAPI::Message& msg) {
|
||||
if(msg.chat.id > 0) {
|
||||
|
@ -16,49 +12,50 @@ bool Bot::handleStart(TelegramAPI::Manager* api, const TelegramAPI::Message& msg
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
// create a game
|
||||
// create a empty game
|
||||
Game* g = new Game();
|
||||
// g->addPlayer(msg.from.id); // does not work
|
||||
|
||||
auto sendmsg = api->sendMessage(msg.chat.id, gameToMessage(g), gameToKeyboard(g));
|
||||
if(sendmsg) {
|
||||
int64_t chatid = sendmsg.chat.id;
|
||||
uint64_t msgid = sendmsg.messageId;
|
||||
g->setMessageID(msgid);
|
||||
std::vector<TelegramAPI::MessageEntity> entities;
|
||||
const std::string text = gameToMessage(g, entities);
|
||||
auto sendmsg = api->sendMessage(msg.chat.id, text, gameToKeyboard(g), entities);
|
||||
delete g;
|
||||
|
||||
games.insert({{chatid, msgid}, g});
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bot::handleMessage(TelegramAPI::Manager* api, TelegramAPI::Message& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static Game::UserInfo userToUserInfo(const TelegramAPI::User& u) {
|
||||
// + (u.last_name.empty() ? "" : " " + u.last_name) // problems with spaces and unicode in last name
|
||||
return {u.id, u.first_name, u.username};
|
||||
}
|
||||
|
||||
#define delret delete g; \
|
||||
return true
|
||||
|
||||
bool Bot::handleCallback(TelegramAPI::Manager* api, TelegramAPI::CallbackQuery& clbq) {
|
||||
int64_t chatid = clbq.message.chat.id;
|
||||
uint64_t msgid = clbq.message.messageId;
|
||||
const std::string& data = clbq.data;
|
||||
|
||||
auto it = games.find({chatid, msgid});
|
||||
if(it == games.end()) {
|
||||
Game* g = messageToGame(clbq.message);
|
||||
if(!g) {
|
||||
api->answerCallbackQuery(clbq.id, "Invalid game");
|
||||
return true;
|
||||
}
|
||||
|
||||
Game* g = it->second;
|
||||
if(data == "join") {
|
||||
if(g->addPlayer(clbq.from.id)) {
|
||||
Game::UserInfo newuser = userToUserInfo(clbq.from);
|
||||
if(g->addPlayer(newuser)) {
|
||||
api->answerCallbackQuery(clbq.id, "You joined the game");
|
||||
updateGame(chatid, msgid, g);
|
||||
return true;
|
||||
delret;
|
||||
}
|
||||
|
||||
api->answerCallbackQuery(clbq.id, "You can not join");
|
||||
return true;
|
||||
delret;
|
||||
}
|
||||
|
||||
// try to parse "x y"
|
||||
|
@ -66,17 +63,22 @@ bool Bot::handleCallback(TelegramAPI::Manager* api, TelegramAPI::CallbackQuery&
|
|||
int x = -1, y = -1;
|
||||
datastream >> x >> y;
|
||||
if(x != -1 && y != -1) {
|
||||
Log::info << "turn: " << (int) x << " " << (int) y << " " << clbq.from.id;
|
||||
if(g->turn(x, y, clbq.from.id)) {
|
||||
Game::UserInfo player = userToUserInfo(clbq.from);
|
||||
if(g->done()) {
|
||||
api->answerCallbackQuery(clbq.id, "Error - game is over");
|
||||
delret;
|
||||
}
|
||||
|
||||
if(g->turn(x, y, player)) {
|
||||
api->answerCallbackQuery(clbq.id, "ok");
|
||||
updateGame(chatid, msgid, g);
|
||||
return true;
|
||||
delret;
|
||||
}
|
||||
api->answerCallbackQuery(clbq.id, "Error - this is not your turn");
|
||||
api->answerCallbackQuery(clbq.id, "Error - this is not your turn");
|
||||
delret;
|
||||
}
|
||||
api->answerCallbackQuery(clbq.id, "Error - invalid button");
|
||||
|
||||
return true;
|
||||
delret;
|
||||
}
|
||||
|
||||
void Bot::stop() {
|
||||
|
@ -84,7 +86,9 @@ void Bot::stop() {
|
|||
}
|
||||
|
||||
void Bot::updateGame(int64_t chatid, uint64_t msgid, const Game* g) {
|
||||
tapi->editMessageText(chatid, msgid, gameToMessage(g), gameToKeyboard(g));
|
||||
std::vector<TelegramAPI::MessageEntity> entities;
|
||||
const std::string text = gameToMessage(g, entities);
|
||||
tapi->editMessageText(chatid, msgid, text, gameToKeyboard(g), entities);
|
||||
}
|
||||
|
||||
TelegramAPI::InlineKeyboard Bot::gameToKeyboard(const Game* g) const {
|
||||
|
@ -108,14 +112,113 @@ TelegramAPI::InlineKeyboard Bot::gameToKeyboard(const Game* g) const {
|
|||
return kb;
|
||||
}
|
||||
|
||||
std::string Bot::gameToMessage(const Game* g) const {
|
||||
static TelegramAPI::MessageEntity userInfoToEntity(const Game::UserInfo& userinfo) {
|
||||
TelegramAPI::MessageEntity ent;
|
||||
|
||||
if(userinfo.username.empty()) {
|
||||
// id bassed mention only works with users with no username
|
||||
ent.type = TelegramAPI::MessageEntity::Type::TEXT_MENTION;
|
||||
ent.user.id = userinfo.id;
|
||||
ent.length = userinfo.name.size();
|
||||
} else {
|
||||
ent.type = TelegramAPI::MessageEntity::Type::MENTION;
|
||||
ent.length = userinfo.username.size()+1;
|
||||
}
|
||||
|
||||
return ent;
|
||||
}
|
||||
|
||||
std::string Bot::gameToMessage(const Game* g, std::vector<TelegramAPI::MessageEntity>& entites) const {
|
||||
std::ostringstream out;
|
||||
out << "X: " << g->getPlayerA() << std::endl;
|
||||
out << "O: " << g->getPlayerB() << std::endl;
|
||||
out << "X: " << g->getPlayerA().getUsernameOrName() << std::endl;
|
||||
out << "O: " << g->getPlayerB().getUsernameOrName() << std::endl;
|
||||
if(g->done()) {
|
||||
out << "Winner: " << g->getWinner() << std::endl;
|
||||
if(g->getWinner().empty()) {
|
||||
out << "Draw" << std::endl;
|
||||
} else {
|
||||
out << "Winner: " << g->getWinner() << std::endl;
|
||||
}
|
||||
} else {
|
||||
out << "nextturn: " << (g->getNextTurn() ? "X" : "O") << std::endl;
|
||||
}
|
||||
|
||||
//create entities
|
||||
if(g->getPlayerA()) {
|
||||
TelegramAPI::MessageEntity a = userInfoToEntity(g->getPlayerA());
|
||||
a.offset = 3;
|
||||
entites.push_back(a);
|
||||
|
||||
if(g->getPlayerB()) {
|
||||
TelegramAPI::MessageEntity b = userInfoToEntity(g->getPlayerB());
|
||||
b.offset = 7 + a.length; // ("x: " + "\n" + "O: ").size() = 7
|
||||
entites.push_back(b);
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static Game::UserInfo userinfoFromEntity(const TelegramAPI::MessageEntity& ent, const std::string& messageText) {
|
||||
if(ent.type == TelegramAPI::MessageEntity::Type::TEXT_MENTION) {
|
||||
uint64_t userid = ent.user.id;
|
||||
std::string name = ent.user.first_name;
|
||||
std::string username = ent.user.username;
|
||||
return {userid, name, username};
|
||||
}
|
||||
if(ent.type == TelegramAPI::MessageEntity::Type::MENTION) {
|
||||
return {0, "", messageText.substr(ent.offset+1, ent.length-1)}; // without the "@"
|
||||
}
|
||||
// invlaid user entity
|
||||
return {0, "", ""};
|
||||
}
|
||||
|
||||
|
||||
Game* Bot::messageToGame(const TelegramAPI::Message& msg) const {
|
||||
if(msg.entities.size() > 2) {
|
||||
Log::warn << "To many entities: " << msg.entities.size();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(msg.entities.size() == 0) {
|
||||
// no user (empty game)
|
||||
return new Game();
|
||||
}
|
||||
|
||||
Game* g = new Game();
|
||||
// read the userinfo from the entities
|
||||
for(uint_fast8_t i = 0; i < msg.entities.size(); ++i) {
|
||||
const TelegramAPI::MessageEntity& ent = msg.entities.at(i);
|
||||
Game::UserInfo info = userinfoFromEntity(ent, msg.text);
|
||||
if(!info) {
|
||||
// invalid
|
||||
Log::warn << "invalid entity " << (int) i;
|
||||
delete g;
|
||||
return nullptr;
|
||||
}
|
||||
g->addPlayer(info);
|
||||
}
|
||||
|
||||
// read the next turn value
|
||||
char nextturn = msg.text.at(msg.text.size()-1);
|
||||
g->setNextTurn(nextturn == 'X');
|
||||
|
||||
// get the game
|
||||
for(uint_fast8_t i = 0; i < msg.replyMarkup.size(); ++i) {
|
||||
const std::vector<TelegramAPI::MarkupButton>& row = msg.replyMarkup.at(i);
|
||||
for(uint_fast8_t j = 0; j < row.size(); ++j) {
|
||||
const TelegramAPI::MarkupButton& btn = row.at(j);
|
||||
|
||||
// button is part of game
|
||||
if(i < g->getSize()) {
|
||||
Game::SYM s = Game::SYM::NONE;
|
||||
if(btn.text == "X") {
|
||||
s = Game::SYM::A;
|
||||
} else if(btn.text == "O") {
|
||||
s = Game::SYM::B;
|
||||
}
|
||||
g->setPos(j, i, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
49
src/game.cpp
49
src/game.cpp
|
@ -3,25 +3,31 @@
|
|||
|
||||
const std::string Game::SYM_NAMES[3] = {"NONE", "X", "O"};
|
||||
|
||||
std::string Game::UserInfo::getUsernameOrName() const {
|
||||
return username.empty() ? name : ("@" + username);
|
||||
}
|
||||
|
||||
bool Game::UserInfo::operator==(const UserInfo& other) const {
|
||||
if(id != 0 && other.id != 0) {
|
||||
return id == other.id;
|
||||
}
|
||||
return username == other.username;
|
||||
}
|
||||
|
||||
Game::Game() {
|
||||
for(uint_fast8_t i = 0; i < SIZE * SIZE; ++i) {
|
||||
field[i] = SYM::NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::setMessageID(uint32_t messageid) {
|
||||
this->messageid = messageid;
|
||||
}
|
||||
|
||||
|
||||
void Game::setPlayerA(uint64_t player) {
|
||||
void Game::setPlayerA(const UserInfo& player) {
|
||||
playerA = player;
|
||||
}
|
||||
void Game::setPlayerB(uint64_t player) {
|
||||
void Game::setPlayerB(const UserInfo& player) {
|
||||
playerB = player;
|
||||
}
|
||||
|
||||
bool Game::addPlayer(uint64_t player) {
|
||||
bool Game::addPlayer(const UserInfo& player) {
|
||||
if(!playerA) {
|
||||
playerA = player;
|
||||
return true;
|
||||
|
@ -34,26 +40,39 @@ bool Game::addPlayer(uint64_t player) {
|
|||
return false;
|
||||
}
|
||||
|
||||
uint64_t Game::getPlayerA() const {
|
||||
const Game::UserInfo& Game::getPlayerA() const {
|
||||
return playerA;
|
||||
}
|
||||
|
||||
uint64_t Game::getPlayerB() const {
|
||||
const Game::UserInfo& Game::getPlayerB() const {
|
||||
return playerB;
|
||||
}
|
||||
|
||||
bool Game::ready() const {
|
||||
return playerA != 0 && playerB != 0 && !done();
|
||||
return playerA && playerB && !done();
|
||||
}
|
||||
|
||||
bool Game::done() const {
|
||||
return checkWinner() != SYM::NONE;
|
||||
return (checkWinner() != SYM::NONE) || isFull();
|
||||
}
|
||||
|
||||
bool Game::turn(uint_fast8_t x, uint_fast8_t y, uint64_t player) {
|
||||
bool Game::isFull() const {
|
||||
for(uint_fast8_t i = 0; i < SIZE * SIZE; ++i) {
|
||||
if(field[i] == SYM::NONE)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Game::turn(uint_fast8_t x, uint_fast8_t y, const UserInfo& player) {
|
||||
if(!isInField(x, y)) return false;
|
||||
if(!ready()) return false;
|
||||
|
||||
Log::info << "turn player: " << player.id << " n: " << player.name << " u: " << player.username;
|
||||
Log::info << "playerA: " << playerA.id << " n: " << playerA.name << " u: " << playerA.username << " e: " << (player == playerA);
|
||||
Log::info << "playerB: " << playerB.id << " n: " << playerB.name << " u: " << playerB.username << " e: " << (player == playerB);
|
||||
|
||||
|
||||
// playerA
|
||||
if(player == playerA && nextturna) {
|
||||
SYM s = getField(x, y);
|
||||
|
@ -92,6 +111,10 @@ Game::SYM Game::getPos(uint_fast8_t x, uint_fast8_t y) const {
|
|||
return getField(x, y);
|
||||
}
|
||||
|
||||
void Game::setPos(uint_fast8_t x, uint_fast8_t y, Game::SYM s) {
|
||||
setField(x, y, s);
|
||||
}
|
||||
|
||||
static constexpr bool checkThree(Game::SYM s1, Game::SYM s2, Game::SYM s3) {
|
||||
return s1 == s2 && s2 == s3;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue