Skip to content

Instantly share code, notes, and snippets.

@skeeto
Last active August 29, 2015 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skeeto/11222269 to your computer and use it in GitHub Desktop.
Save skeeto/11222269 to your computer and use it in GitHub Desktop.
*.o
rpsls
*.db
#include <ctime>
#include <cstring>
#include <stdexcept>
#include <sqlite3.h>
#include "db.hh"
const char *sql_timeout = "PRAGMA busy_timeout = 30000";
const char *sql_table = "CREATE TABLE IF NOT EXISTS plays (time, host, choice)";
const char *sql_insert = "INSERT INTO plays VALUES (?, ?, ?)";
const char *sql_count = "SELECT choice, count(*) FROM plays GROUP BY choice";
void DB::prepare(const char *sql, sqlite3_stmt **stmt) {
if (sqlite3_prepare_v2(db_, sql, std::strlen(sql), stmt, NULL) != SQLITE_OK) {
throw std::runtime_error{sqlite3_errmsg(db_)};
}
}
int DB::step(sqlite3_stmt *stmt) {
int result = sqlite3_step(stmt);
if (result != SQLITE_ROW && result != SQLITE_DONE) {
throw std::runtime_error{sqlite3_errmsg(db_)};
}
return result;
}
void DB::reset(sqlite3_stmt *stmt) {
if (sqlite3_reset(stmt) != SQLITE_OK) {
throw std::runtime_error{sqlite3_errmsg(db_)};
}
}
void DB::finalize(sqlite3_stmt *stmt) {
if (sqlite3_finalize(stmt) != SQLITE_OK) {
throw std::runtime_error{sqlite3_errmsg(db_)};
}
}
DB::~DB() { sqlite3_close(db_); }
DB::DB(const char *file) {
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
if (sqlite3_open_v2(file, &db_, flags, nullptr) != SQLITE_OK) {
throw std::runtime_error{sqlite3_errmsg(db_)};
}
sqlite3_stmt *stmt_table, *stmt_timeout;
prepare(sql_timeout, &stmt_timeout);
step(stmt_timeout);
finalize(stmt_timeout);
prepare(sql_table, &stmt_table);
step(stmt_table);
finalize(stmt_table);
prepare(sql_insert, &stmt_insert);
prepare(sql_count, &stmt_count);
}
void DB::event(const char *hostname, int choice) {
sqlite3_bind_int64(stmt_insert, 1, time(nullptr));
sqlite3_bind_text(stmt_insert, 2, hostname, strlen(hostname), SQLITE_STATIC);
sqlite3_bind_int(stmt_insert, 3, choice);
step(stmt_insert);
reset(stmt_insert);
}
std::vector<int64_t> DB::counts() {
std::vector<int64_t> counts{};
counts.resize(5);
while (step(stmt_count) == SQLITE_ROW) {
int choice = sqlite3_column_int(stmt_count, 0);
int64_t count = sqlite3_column_int64(stmt_count, 1);
counts[choice - 1] = count;
}
reset(stmt_count);
return std::move(counts);
}
int64_t DB::total() {
auto totals = counts();
int64_t total = 0;
for (auto &i : totals) total += i;
return total;
}
#ifndef RPSLS_DB_HH
#define RPSLS_DB_HH
#include <vector>
struct sqlite3;
struct sqlite3_stmt;
class DB {
public:
DB(const char *file);
~DB();
void event(const char *hostname, int choice);
std::vector<int64_t> counts();
int64_t total();
private:
void prepare(const char *, sqlite3_stmt **);
int step(sqlite3_stmt *);
void reset(sqlite3_stmt *);
void finalize(sqlite3_stmt *);
sqlite3 *db_;
sqlite3_stmt *stmt_insert, *stmt_count;
};
#endif
CXX = clang++
CXXFLAGS = -std=c++11 -Wall
LDLIBS = -lstdc++ -lsqlite3
rpsls : rpsls.o db.o player.o
.PHONY : clean run
clean :
$(RM) *.o rpsls
run : rpsls
./$^
#include <iostream>
#include "db.hh"
#include "rpsls.hh"
#include "player.hh"
Choice Human::play() {
std::cout << "1. Rock" << std::endl;
std::cout << "2. Paper" << std::endl;
std::cout << "3. Scissors" << std::endl;
std::cout << "4. Lizard" << std::endl;
std::cout << "5. Spock" << std::endl;
std::cout << "(Any other input quits)" << std::endl;
std::cout << "Choice (1-5)? ";
int input;
if (!(std::cin >> input)) input = 0;
if (input < 0 || input > 5) input = 0;
choice_ = static_cast<Choice>(input);
std::cout << std::endl;
if (input != 0) {
db_->event(hostname_, input);
}
return choice_;
}
#define BOLD "\033[33m\033[1m"
#define UNBOLD "\033[22m\033[39m"
void Human::outcome(Choice other) {
std::cout << "You choose " << choice_ << "." << std::endl;
std::cout << "Other player chooses " << other << "." << std::endl;
if (other == choice_) {
std::cout << BOLD "Tie!" UNBOLD << std::endl << std::endl;
ties_++;
return;
}
const char *result = can_defeat(choice_, other);
if (result) {
std::cout << BOLD "You win" UNBOLD ": "
<< ": " << choice_ << " " << result << " " << other << std::endl;
wins_++;
} else {
result = can_defeat(other, choice_);
std::cout << BOLD "You lose" UNBOLD ": "
<< ": " << other << " " << result << " " << choice_ << std::endl;
losses_++;
}
std::cout << "W/T/L: " << wins_ << "/" << ties_ << "/" << losses_
<< std::endl << std::endl;
}
Choice SmartAI::play() {
auto counts = db_->counts();
int64_t total = 0;
for (auto &i : counts) total += i;
/* Guess other player's move, weighted by history. */
Choice expected;
int64_t selection = rand() % total;
for (int i = 0; i < 5; i++) {
if (selection < counts[i]) {
expected = static_cast<Choice>(i + 1);
} else {
selection -= counts[i];
}
}
/* Choice a random countermove. */
Choice counter[2];
int i = 0;
for (auto &outcome : outcomes) {
if (outcome.victim == expected) {
counter[i++] = outcome.attacker;
}
}
return counter[rand() % 2];
}
#ifndef RPSLS_PLAYER_HH
#define RPSLS_PLAYER_HH
#include <cstdlib>
#include "db.hh"
#include "rpsls.hh"
class Player {
public:
virtual Choice play() = 0;
virtual void outcome(Choice other) = 0;
};
class Human : public Player {
public:
Human(DB *db, const char *hostname) : db_{db}, hostname_{hostname} {}
Choice play();
void outcome(Choice other);
private:
DB *db_;
Choice choice_;
const char *hostname_;
int wins_ = 0, losses_ = 0, ties_ = 0;
};
class RandomAI : public Player {
public:
Choice play() { return static_cast<Choice>(1 + (rand() % 5)); }
void outcome(Choice other) {};
};
class SmartAI : public Player {
public:
SmartAI(DB *db) : db_{db} {}
Choice play();
void outcome(Choice other) {};
private:
DB *db_;
};
#endif
#include <iostream>
#include <unistd.h>
#include "db.hh"
#include "rpsls.hh"
#include "player.hh"
const struct outcome outcomes[] = {
{Choice::SPOCK, "smashes", Choice::SCISSORS},
{Choice::SPOCK, "vaporizes", Choice::ROCK},
{Choice::SCISSORS, "cuts", Choice::PAPER},
{Choice::SCISSORS, "decapitates", Choice::LIZARD},
{Choice::PAPER, "covers", Choice::ROCK},
{Choice::PAPER, "disproves", Choice::SPOCK},
{Choice::ROCK, "crushes", Choice::LIZARD},
{Choice::ROCK, "crushes", Choice::SCISSORS},
{Choice::LIZARD, "eats", Choice::PAPER},
{Choice::LIZARD, "poisons", Choice::SPOCK}};
const char *can_defeat(Choice attacker, Choice victim) {
for (auto &outcome : outcomes) {
if (outcome.attacker == attacker && outcome.victim == victim) {
return outcome.action;
}
}
return nullptr;
}
std::ostream &operator<<(std::ostream &out, const Choice c) {
const char *names[] = {"QUIT", "rock", "paper",
"scissors", "lizard", "spock"};
out << "\033[1m\033[4m" << names[static_cast<int>(c)] << "\033[24m\033[22m";
return out;
}
bool play(Player &a, Player &b) {
Choice a_play, b_play;
do {
a_play = a.play();
b_play = b.play();
if (a_play == Choice::QUIT || b_play == Choice::QUIT) return false;
a.outcome(b_play);
b.outcome(a_play);
} while (a_play == b_play);
return true;
}
int main(int argc, char **argv) {
const char *hostname = "localhost", *dbfile = "rpsls.db";
int opt;
while ((opt = getopt(argc, argv, "d:h:p")) != -1) {
switch (opt) {
case 'd':
dbfile = optarg;
break;
case 'h':
hostname = optarg;
break;
}
}
DB db{dbfile};
std::cout << "Total games played: " << db.total() << std::endl << std::endl;
Human human{&db, hostname};
SmartAI ai{&db};
while (play(human, ai))
;
return 0;
}
#ifndef RPSLS_RPSLS_HH
#define RPSLS_RPSLS_HH
enum class Choice { QUIT = 0, ROCK, PAPER, SCISSORS, LIZARD, SPOCK };
struct outcome {
Choice attacker;
const char *action;
Choice victim;
};
extern const struct outcome outcomes[10];
const char *can_defeat(Choice attacker, Choice victim);
std::ostream &operator<<(std::ostream &, const Choice);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment