tictactoebot/src/bot.cpp

225 lines
6.0 KiB
C++

#include "bot.h"
#include <sstream>
Bot::Bot(TelegramAPI::Manager* tapi, Conf& conf) : tapi(tapi), conf(conf) {}
Bot::~Bot() {}
bool Bot::handleStart(TelegramAPI::Manager* api, const TelegramAPI::Message& msg) {
if(msg.chat.id > 0) {
api->sendMessage(msg.chat.id, "This bot should be used in group chats");
return true;
}
// create a empty game
Game g;
std::vector<TelegramAPI::MessageEntity> entities;
const std::string text = gameToMessage(g, entities);
auto sendmsg = api->sendMessage(msg.chat.id, text, gameToKeyboard(g), entities);
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;
Game* g = messageToGame(clbq.message);
if(!g) {
api->answerCallbackQuery(clbq.id, "Invalid game");
return true;
}
if(data == "join") {
Game::UserInfo newuser = userToUserInfo(clbq.from);
if(g->addPlayer(newuser)) {
api->answerCallbackQuery(clbq.id, "You joined the game");
updateGame(chatid, msgid, *g);
delret;
}
api->answerCallbackQuery(clbq.id, "You can not join");
delret;
}
// try to parse "x y"
std::istringstream datastream(data);
int x = -1, y = -1;
datastream >> x >> y;
if(x != -1 && y != -1) {
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);
delret;
}
api->answerCallbackQuery(clbq.id, "Error - this is not your turn");
delret;
}
api->answerCallbackQuery(clbq.id, "Error - invalid button");
delret;
}
#undef delret
void Bot::stop() {
}
void Bot::updateGame(int64_t chatid, uint64_t msgid, const Game& 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 {
static const std::string text[3] = {" ", "X", "O"};
TelegramAPI::InlineKeyboard kb;
for(uint_fast8_t row = 0; row < g.getSize(); row++) {
for(uint_fast8_t col = 0; col < g.getSize(); col++) {
Game::SYM s = g.getPos(col, row);
const std::string clb = std::to_string(col) + " " + std::to_string(row);
auto btn = TelegramAPI::InlineButton::createCallback(text[(int) s], clb);
kb.addButton(btn, row);
}
}
if(!g.ready() && !g.done()) {
// missing player
kb.addButton(TelegramAPI::InlineButton::createCallback("Join Game", "join"), g.getSize());
}
return kb;
}
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().getUsernameOrName() << std::endl;
out << "O: " << g.getPlayerB().getUsernameOrName() << std::endl;
if(g.done()) {
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;
}