Explorar el Código

internal dumb player and report

master
gtheler hace 5 años
padre
commit
b8f69e8979
Se han modificado 10 ficheros con 199 adiciones y 79 borrados
  1. +2
    -1
      Makefile.am
  2. +1
    -5
      TODO
  3. +20
    -10
      src/base.h
  4. +11
    -16
      src/blackjack.cpp
  5. +3
    -4
      src/conf.cpp
  6. +58
    -0
      src/internal.cpp
  7. +39
    -0
      src/internal.h
  8. +3
    -2
      src/main.cpp
  9. +57
    -23
      src/report.cpp
  10. +5
    -18
      src/tty.cpp

+ 2
- 1
Makefile.am Ver fichero

@@ -23,4 +23,5 @@ blackjack_SOURCES = \
src/blackjack.cpp \
src/cards.cpp \
src/stdinout.cpp \
src/tty.cpp
src/tty.cpp \
src/internal.cpp

+ 1
- 5
TODO Ver fichero

@@ -1,14 +1,10 @@
* handle no readline
* named pipes (a.k.a FIFOs)
* eV = expected value = return
* check that the card distribution is uniform
* initial bankroll
* report
* as a file
* format (yaml, json, markdown table)
* size (extra small, small, medium, large, extra large)
* flat_bet implies =1
* parse 1e5 in hands
* verbosity (extra small, small, medium, large, extra large)
* trap ctrl+c and write report anyway (not sure how to pass arguments to signal handler)
* flag to see if a conf string was used or not
* to_string() for floats

+ 20
- 10
src/base.h Ver fichero

@@ -228,10 +228,11 @@ class Player {
};

struct reportItem {
reportItem(std::string k, std::string f, double v) : key(k), format(f), value(v) {};
reportItem(int l, std::string k, std::string f, double v) : key(k), format(f), value(v), level(l) {};
std::string key;
std::string format;
double value;
int level;
};

class Dealer {
@@ -274,7 +275,6 @@ class Dealer {
void reportPrepare(void);
int writeReportYAML(void);
protected:
// TODO: multiple players
Player *player;
@@ -284,9 +284,13 @@ class Dealer {
// std::list <Hand> hands;
Hand hand;

double error_standard_deviations = 1.0;
int n_decks = -1;
unsigned long int n_hands = 0;
// how many standard deviations does the reported error mean?
double error_standard_deviations = 3.0;
// default infinite number of decks (it's faster)
int n_decks = -1;
// default one million hands
unsigned long int n_hands = 1000000;
unsigned long int n_hand = 0;
struct {
@@ -294,8 +298,8 @@ class Dealer {
std::list<PlayerHand>::iterator currentHand;
unsigned int splits = 0;
// unsigned int currentBet = 0;
// TODO: separate handsDealt from handsPlayed
unsigned int n_hands = 0; // this is different from the dealer's due to splitting
unsigned int handsInsured = 0;
@@ -313,22 +317,28 @@ class Dealer {
unsigned int pushes = 0;
unsigned int losses = 0;
// TODO: blackjack_pushes?
// TODO: blackjack_pushes?
double bankroll = 0;
double worstBankroll = 0;
double totalMoneyWaged = 0;
double result = 0;
// these variables are used to compute the running mean and variance
double currentOutcome = 0;
double mean = 0;
double M2 = 0;
double variance = 0;
} playerStats;

void updateMeanAndVariance(void);
private:
bool done = false;
std::list<reportItem> report;
int reportVerbosity = 3;
};

template <typename ... Args> std::string string_format( const std::string& format, Args ... args);

#endif

+ 11
- 16
src/blackjack.cpp Ver fichero

@@ -92,18 +92,12 @@ void Blackjack::deal(void) {
return;
}
// update the uncertainty (knuth citing welford)
// The Art of Computer Programming, volume 2: Seminumerical Algorithms, 3rd edn., p. 232
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm

if (n_hand != 0) {
double delta = playerStats.result - playerStats.mean;
playerStats.mean += delta / (double)(n_hand);
playerStats.M2 += delta * (playerStats.result - playerStats.mean);
playerStats.variance = playerStats.M2 / (double)(n_hand);
updateMeanAndVariance();
}

i_arranged_cards = 0;
playerStats.currentOutcome = 0;
n_hand++;
// clear dealer's hand
@@ -229,7 +223,7 @@ void Blackjack::deal(void) {
// pay him (her)
playerStats.bankroll += (1.0 + 0.5) * playerStats.currentHand->bet;
playerStats.result += playerStats.currentHand->bet;
playerStats.currentOutcome += playerStats.currentHand->bet;
info(Libreblackjack::Info::PlayerWinsInsurance, 1e3*playerStats.currentHand->bet);

playerStats.winsInsured++;
@@ -247,7 +241,7 @@ void Blackjack::deal(void) {
} else {
playerStats.result -= playerStats.currentHand->bet;
playerStats.currentOutcome -= playerStats.currentHand->bet;
info(Libreblackjack::Info::PlayerLosses, 1e3*playerStats.currentHand->bet);
playerStats.losses++;
@@ -261,7 +255,7 @@ void Blackjack::deal(void) {

// pay him (her)
playerStats.bankroll += (1.0 + blackjack_pays) * playerStats.currentHand->bet;
playerStats.result += blackjack_pays * playerStats.currentHand->bet;
playerStats.currentOutcome += blackjack_pays * playerStats.currentHand->bet;
info(Libreblackjack::Info::PlayerWins, 1e3 * blackjack_pays*playerStats.currentHand->bet);
playerStats.blackjacksPlayer++;
@@ -350,7 +344,7 @@ void Blackjack::deal(void) {
if (playerHand.busted() == false) {
// pay him (her)
playerStats.bankroll += 2 * playerHand.bet;
playerStats.result += playerHand.bet;
playerStats.currentOutcome += playerHand.bet;
info(Libreblackjack::Info::PlayerWins, 1e3*playerHand.bet);
playerStats.wins++;
@@ -364,7 +358,7 @@ void Blackjack::deal(void) {
if (std::abs(player->dealerValue) > std::abs(player->playerValue)) {
playerStats.result -= playerHand.bet;
playerStats.currentOutcome -= playerHand.bet;
info(Libreblackjack::Info::PlayerLosses, 1e3*playerHand.bet, player->playerValue);
playerStats.losses++;
@@ -379,7 +373,7 @@ void Blackjack::deal(void) {
// pay him (her)
playerStats.bankroll += 2 * playerHand.bet;
playerStats.result += playerHand.bet;
playerStats.currentOutcome += playerHand.bet;
info(Libreblackjack::Info::PlayerWins, 1e3*playerHand.bet, player->playerValue);
playerStats.wins++;
playerStats.winsDoubled += playerHand.doubled;
@@ -469,6 +463,7 @@ int Blackjack::process(void) {
if (playerStats.bankroll < playerStats.worstBankroll) {
playerStats.worstBankroll = playerStats.bankroll;
}
playerStats.totalMoneyWaged += playerStats.currentHand->bet;
nextAction = Libreblackjack::DealerAction::DealPlayerFirstCard;
return 1;
@@ -541,7 +536,7 @@ int Blackjack::process(void) {
if (playerStats.currentHand->busted()) {
info(Libreblackjack::Info::PlayerLosses, 1e3*playerStats.currentHand->bet, player->playerValue);
playerStats.result -= playerStats.currentHand->bet;
playerStats.currentOutcome -= playerStats.currentHand->bet;
playerStats.bustsPlayer++;
playerStats.losses++;
}
@@ -661,7 +656,7 @@ int Blackjack::process(void) {

if (playerStats.currentHand->busted()) {
playerStats.result -= playerStats.currentHand->bet;
playerStats.currentOutcome -= playerStats.currentHand->bet;
info(Libreblackjack::Info::PlayerLosses, 1e3*playerStats.currentHand->bet);
playerStats.bustsPlayer++;
playerStats.losses++;

+ 3
- 4
src/conf.cpp Ver fichero

@@ -96,7 +96,7 @@ Configuration::Configuration(int argc, char **argv) {
break;
case 'f':
if (optarg != NULL) {
data["flat_bet"] = optarg;
data["flat_bet"] = optarg;
} else {
data["flat_bet"] = "yes";
}
@@ -151,8 +151,7 @@ Configuration::Configuration(int argc, char **argv) {
set(&error_standard_deviations, {"error_standard_deviations"});
set(yaml_report_path, {"yaml_report", "yaml_report_path"});

return;
}

@@ -234,7 +233,7 @@ bool Configuration::set(unsigned int *value, std::list<std::string> key) {
bool Configuration::set(unsigned long int *value, std::list<std::string> key) {
for (auto it : key) {
if (exists(*(&it))) {
*value = std::stoi(data[*(&it)]);
*value = (unsigned long int)std::stod(data[*(&it)]);
return true;
}
}

+ 58
- 0
src/internal.cpp Ver fichero

@@ -0,0 +1,58 @@
/*------------ -------------- -------- --- ----- --- -- - -
* Libre Blackjack - tty interactive player
*
* Copyright (C) 2020 jeremy theler
*
* This file is part of Libre Blackjack.
*
* Libre Blackjack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Libre Blackjack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Libre Blackjack. If not, see <http://www.gnu.org/licenses/>.
*------------------- ------------ ---- -------- -- - - -
*/

#include "conf.h"
#include "blackjack.h"
#include "internal.h"


Internal::Internal(Configuration &conf) {
return;
}

void Internal::info(Libreblackjack::Info msg, int p1, int p2) {
return;
}

int Internal::play() {

switch (actionRequired) {
case Libreblackjack::PlayerActionRequired::Bet:
currentBet = 1;
actionTaken = Libreblackjack::PlayerActionTaken::Bet;
break;

case Libreblackjack::PlayerActionRequired::Insurance:
actionTaken = Libreblackjack::PlayerActionTaken::DontInsure;
break;
case Libreblackjack::PlayerActionRequired::Play:
actionTaken = (playerValue < 12) ? Libreblackjack::PlayerActionTaken::Hit : Libreblackjack::PlayerActionTaken::Stand;
break;
case Libreblackjack::PlayerActionRequired::None:
break;
}
return 0;
}

+ 39
- 0
src/internal.h Ver fichero

@@ -0,0 +1,39 @@
/*------------ -------------- -------- --- ----- --- -- - -
* Libre Blackjack - internal automatic player
*
* Copyright (C) 2020 jeremy theler
*
* This file is part of Libre Blackjack.
*
* Libre Blackjack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Libre Blackjack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Libre Blackjack. If not, see <http://www.gnu.org/licenses/>.
*------------------- ------------ ---- -------- -- - - -
*/

#ifndef INTERNAL_H
#define INTERNAL_H
#include "blackjack.h"

class Internal : public Player {
public:
Internal(Configuration &);
~Internal() { };
int play(void) override;
void info(Libreblackjack::Info = Libreblackjack::Info::None, int = 0, int = 0) override;

private:
};

#endif

+ 3
- 2
src/main.cpp Ver fichero

@@ -27,6 +27,7 @@
#include "blackjack.h"
#include "tty.h"
#include "stdinout.h"
#include "internal.h"

int main(int argc, char **argv) {
@@ -59,8 +60,8 @@ int main(int argc, char **argv) {
player = new Tty(conf);
} else if (conf.getPlayerName() == "stdinout" || conf.getPlayerName() == "stdio") {
player = new StdInOut(conf);
// TODO: player strategy from file
} else if (conf.getPlayerName() == "internal") {
player = new Internal(conf);
} else {
std::cerr << "Unknown player '" << conf.getPlayerName() <<".'" << std::endl;
return -1;

+ 57
- 23
src/report.cpp Ver fichero

@@ -1,33 +1,64 @@
#include <iostream>
// #include <sys/time.h>
// #include <sys/resource.h>
#include <cmath>
#include <memory>
#include <string>

#include "base.h"


// https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf
template<typename ... Args>
std::string string_format( const std::string& format, Args ... args)
{
size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra space for '\0'
if (size <= 0) {
return std::string("");
}
std::unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), size, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}

void Dealer::updateMeanAndVariance(void) {
double delta = playerStats.currentOutcome - playerStats.mean;
playerStats.mean += delta / (double)(n_hand);
playerStats.M2 += delta * (playerStats.currentOutcome - playerStats.mean);
playerStats.variance = playerStats.M2 / (double)(n_hand);
return;
}


void Dealer::reportPrepare(void) {

double ev = (double) playerStats.result / (double) n_hand;
double error = error_standard_deviations * sqrt (playerStats.variance / (double) n_hand);
report.push_back(reportItem("bankroll", "%g", playerStats.bankroll));
report.push_back(reportItem("result", "%g", playerStats.result));
report.push_back(reportItem("ev", "%g", ev));
report.push_back(reportItem("hands", "%g", n_hand));
report.push_back(reportItem("variance", "%g", playerStats.variance));
report.push_back(reportItem("deviation", "%g", sqrt(playerStats.variance)));
report.push_back(reportItem("error", "%g", error));
// we need to update these statistics after the last played hand
updateMeanAndVariance();
report.push_back(reportItem("played_hands", "%g", n_hands));
report.push_back(reportItem("total_money_waged", "%g", playerStats.totalMoneyWaged));

report.push_back(reportItem("bustsPlayer", "%g", playerStats.bustsPlayer));
report.push_back(reportItem("bustsDealer", "%g", playerStats.bustsDealer));
double error = error_standard_deviations * sqrt (playerStats.variance / (double) n_hand);

report.push_back(reportItem("wins", "%g", playerStats.wins));
report.push_back(reportItem("pushes", "%g", playerStats.pushes));
report.push_back(reportItem("losses", "%g", playerStats.losses));
int precision = (int) (std::ceil(-std::log10(error))) - 2;
if (precision < 0) {
precision = 0;
}
std::string format = "\"(%+." + std::to_string(precision) + "f ± %." + std::to_string(precision) + "f) %%%%\"";
report.push_back(reportItem(1, "result", string_format(format, 100*playerStats.mean, 100*error), 0.0));
report.push_back(reportItem(2, "mean", "%g", playerStats.mean));
report.push_back(reportItem(2, "error", "%g", error));
report.push_back(reportItem(2, "hands", "%g", n_hand));
report.push_back(reportItem(2, "bankroll", "%g", playerStats.bankroll));

report.push_back(reportItem(3, "bustsPlayer", "%g", playerStats.bustsPlayer / (double) n_hand));
report.push_back(reportItem(3, "bustsDealer", "%g", playerStats.bustsDealer / (double) n_hand));
report.push_back(reportItem(3, "wins", "%g", playerStats.wins / (double) n_hand));
report.push_back(reportItem(3, "pushes", "%g", playerStats.pushes / (double) n_hand));
report.push_back(reportItem(3, "losses", "%g", playerStats.losses / (double) n_hand));

report.push_back(reportItem(4, "total_money_waged", "%g", playerStats.totalMoneyWaged));

report.push_back(reportItem(5, "variance", "%g", playerStats.variance));
report.push_back(reportItem(5, "deviation", "%g", sqrt(playerStats.variance)));

return;
}

@@ -44,11 +75,14 @@ int Dealer::writeReportYAML(void) {
// }

// TODO: choose if comments with explanations are to be added
// TODO: choose verbosity level
std::cerr << "---" << std::endl;
for (auto item : report) {
std::cerr << item.key << ": ";
fprintf(stderr, item.format.c_str(), item.value);
std::cerr << std::endl;
if (item.level <= reportVerbosity) {
std::cerr << item.key << ": ";
fprintf(stderr, item.format.c_str(), item.value);
std::cerr << std::endl;
}
}
// std::cerr << "rules:" << std::endl;

+ 5
- 18
src/tty.cpp Ver fichero

@@ -21,7 +21,6 @@
*/

#include <iostream>
#include <cstring>
#include <thread>
#include <chrono>

@@ -106,9 +105,8 @@ void Tty::info(Libreblackjack::Info msg, int p1, int p2) {
break;

case Libreblackjack::Info::NewHand:
// s = "new_hand";
std::cout << std::endl;
s = "Starting new hand #" + std::to_string(p1) + " with bankroll " + std::to_string(1e-3*p2);
s = "Starting new hand #" + std::to_string(p1) + " with bankroll " + string_format("%g", 1e-3*p2, 0.0);
// clear dealer's hand
dealerHand.cards.clear();
@@ -152,7 +150,6 @@ void Tty::info(Libreblackjack::Info msg, int p1, int p2) {
s = "Dealer's up card is " + card[p1].utf8();
break;
default:
// s = "card_dealer";
s = "Dealer's card is " + card[p1].utf8();
break;
}
@@ -220,45 +217,37 @@ void Tty::info(Libreblackjack::Info msg, int p1, int p2) {
break;

case Libreblackjack::Info::PlayerDoubleInvalid:
// s = "player_double_invalid";
s = "Cannot double down";
break;
case Libreblackjack::Info::PlayerNextHand:
// s = "player_next_hand";
s = "Playing next hand #" + std::to_string(p1);
render = true;
break;
case Libreblackjack::Info::PlayerPushes:
// s = "player_pushes";
s = "Player pushes " + std::to_string(1e-3*p1) + ((p2 > 0) ? (" with " + std::to_string(p2)) : "");
s = "Player pushes " + string_format("%g", 1e-3*p1, 0.0) + ((p2 > 0) ? (" with " + std::to_string(p2)) : "");
render = true;
break;
case Libreblackjack::Info::PlayerLosses:
// s = "player_losses";
s = "Player losses " + std::to_string(1e-3*p1) + ((p2 > 0) ? (" with " + std::to_string(p2)) : "");
s = "Player losses " + string_format("%g", 1e-3*p1, 0.0) + ((p2 > 0) ? (" with " + std::to_string(p2)) : "");
render = true;
break;
case Libreblackjack::Info::PlayerBlackjack:
// s = "blackjack_player";
s = "Player has Blackjack";
render = true;
break;
case Libreblackjack::Info::PlayerWins:
// s = "player_wins";
s = "Player wins " + std::to_string(1e-3*p1) + ((p2 > 0) ? (" with " + std::to_string(p2)) : "");
s = "Player wins " + string_format("%g", 1e-3*p1, 0.0) + ((p2 > 0) ? (" with " + std::to_string(p2)) : "");
render = true;
break;
case Libreblackjack::Info::NoBlackjacks:
// s = "no_blackjacks";
s = "No blackjacks";
break;

case Libreblackjack::Info::DealerBusts:
// s = "no_blackjacks";
s = "Dealer busts with " + std::to_string(p1);
break;
@@ -267,17 +256,15 @@ void Tty::info(Libreblackjack::Info msg, int p1, int p2) {
break;

case Libreblackjack::Info::Bankroll:
std::cout << "Your bankroll is " << std::to_string(1e-3*p1) << std::endl;
std::cout << "Your bankroll is " << string_format("%g", 1e-3*p1, 0.0) << std::endl;
break;
case Libreblackjack::Info::CommandInvalid:
// s = "command_invalid";
s = "Invalid command";
break;
case Libreblackjack::Info::Bye:
// s = "bye";
s = "Bye bye! We'll play Blackjack again next time";
break;

Cargando…
Cancelar
Guardar