Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Genetic_Chess.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,12 @@
<ClCompile Include="src\genes\Pawn_Advancement_Gene.cpp" />
<ClCompile Include="src\Genes\Pawn_Structure_Gene.cpp" />
<ClCompile Include="src\genes\Piece_Strength_Gene.cpp" />
<ClCompile Include="src\Genes\Resignation_Gene.cpp" />
<ClCompile Include="src\genes\Sphere_of_Influence_Gene.cpp" />
<ClCompile Include="src\genes\Total_Force_Gene.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\Players\Alpha_Beta_Value.cpp" />
<ClCompile Include="src\Players\Move_Decision.cpp" />
<ClCompile Include="src\players\Xboard_Mediator.cpp" />
<ClCompile Include="src\players\Game_Tree_Node_Result.cpp" />
<ClCompile Include="src\Players\Genetic_AI.cpp" />
Expand Down Expand Up @@ -313,11 +315,13 @@
<ClInclude Include="src\Genes\Pawn_Advancement_Gene.h" />
<ClInclude Include="src\Genes\Pawn_Structure_Gene.h" />
<ClInclude Include="src\Genes\Piece_Strength_Gene.h" />
<ClInclude Include="src\Genes\Resignation_Gene.h" />
<ClInclude Include="src\Genes\Sphere_of_Influence_Gene.h" />
<ClInclude Include="src\Genes\Total_Force_Gene.h" />
<ClInclude Include="src\Players\Alpha_Beta_Value.h" />
<ClInclude Include="src\Players\Game_Tree_Node_Result.h" />
<ClInclude Include="src\Players\Genetic_AI.h" />
<ClInclude Include="src\Players\Move_Decision.h" />
<ClInclude Include="src\Players\Outside_Communicator.h" />
<ClInclude Include="src\Players\Player.h" />
<ClInclude Include="src\Players\Proxy_Player.h" />
Expand Down
75 changes: 50 additions & 25 deletions analysis/win_lose_draw_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class Game_Ending(Enum):
MATERIAL_DRAW = auto()
STALEMATE = auto()
TIME_WITHOUT_MATERIAL = auto()
WHITE_RESIGNATION = auto()
BLACK_RESIGNATION = auto()


def extract_game_endings(game_file_name: str):
Expand Down Expand Up @@ -51,6 +53,10 @@ def extract_game_endings(game_file_name: str):
result_type = Game_Ending.STALEMATE
elif result_text.lower() == 'time expired with insufficient material':
result_type = Game_Ending.TIME_WITHOUT_MATERIAL
elif result_text.lower() == "white resigned":
result_type = Game_Ending.WHITE_RESIGNATION
elif result_text.lower() == "black resigned":
result_type = Game_Ending.BLACK_RESIGNATION
else:
raise Exception('Unrecognized result type: ' + result_text)
except KeyError:
Expand Down Expand Up @@ -119,6 +125,8 @@ def draw_result_plot(result_data, label):
material = result_type == Game_Ending.MATERIAL_DRAW
no_legal = result_type == Game_Ending.STALEMATE
time_and_material = result_type == Game_Ending.TIME_WITHOUT_MATERIAL
white_resignations = result_type == Game_Ending.WHITE_RESIGNATION
black_resignations = result_type == Game_Ending.BLACK_RESIGNATION
number_of_games = len(game_number)

outcome_figure, outcome_axes = plt.subplots()
Expand All @@ -138,6 +146,8 @@ def draw_outcome_plot(outcome, label):
draw_outcome_plot(material, "Material draw")
draw_outcome_plot(no_legal, "Stalemate")
draw_outcome_plot(time_and_material, "Time w/o material")
draw_outcome_plot(white_resignations, "White resigned")
draw_outcome_plot(black_resignations, "Black resigned")

outcome_axes.set_xlabel('Games played')
outcome_axes.set_ylabel('Percentage')
Expand Down Expand Up @@ -236,36 +246,38 @@ def draw_outcome_plot(outcome, label):
plt.close(move_count_histogram_figure)

winning_games_lengths = moves_in_game[white_checkmates | black_checkmates]
winning_move_counts, winning_move_bins = np.histogram(winning_games_lengths, range(1, max(moves_in_game) + 1))
winning_move_counts, winning_move_bins = np.histogram(winning_games_lengths, range(max(moves_in_game) + 1))
checkmate_figure, checkmate_axes = plt.subplots()
checkmate_axes.bar(winning_move_bins[0:-1], winning_move_counts, width=1, facecolor=bar_color, edgecolor=bar_color, linewidth=bar_line_width, label='All checkmates')
checkmate_axes.set_title('Checkmate game lengths')
checkmate_axes.set_xlim(0, max_game_length_display)

# Log-normal fit
bins_fit = winning_move_bins[winning_move_bins > 0]
mean_log = np.mean(np.log(winning_games_lengths))
std_log = np.std(np.log(winning_games_lengths))
winning_games_count = len(winning_games_lengths)
fit = winning_games_count*np.exp(-.5*np.power((np.log(bins_fit) - mean_log)/std_log, 2))/(bins_fit*std_log*np.sqrt(2*np.pi))
checkmate_axes.plot(bins_fit, fit, linewidth=line_width, label='Log-normal fit')

checkmate_axes.set_xlabel('Moves in Game')
checkmate_axes.set_ylabel(f'Counts (total = {winning_games_count})')

stats = [f'Mean = {np.mean(winning_games_lengths):.2f}',
f'Median = {np.median(winning_games_lengths):.2f}',
f'Std. Dev. = {np.std(winning_games_lengths):.2f}',
f'Min = {min(winning_games_lengths)}',
f'Max = {max(winning_games_lengths)}',
'',
f'Log-Norm Peak = {np.exp(mean_log - np.power(std_log, 2)):.2f}',
f'Log-Norm Width = {std_log:.2f}']

xl = checkmate_axes.get_xlim()
yl = checkmate_axes.get_ylim()
checkmate_axes.text(0.65*xl[1], 0.5*yl[1], '\n'.join(stats), fontsize=stat_text_size)

def log_normal_fit(game_lengths, game_length_bins, axes):
game_lengths = game_lengths[game_lengths > 1]
bins_fit = game_length_bins[game_length_bins > 0]
mean_log = np.mean(np.log(game_lengths))
std_log = np.std(np.log(game_lengths))
games_count = len(game_lengths)
fit = games_count*np.exp(-.5*np.power((np.log(bins_fit) - mean_log)/std_log, 2))/(bins_fit*std_log*np.sqrt(2*np.pi))
axes.plot(bins_fit, fit, linewidth=line_width, label='Log-normal fit')

axes.set_xlabel('Moves in Game')
axes.set_ylabel(f'Counts (total = {games_count})')

stats = [f'Mean = {np.mean(game_lengths):.2f}',
f'Median = {np.median(game_lengths):.2f}',
f'Std. Dev. = {np.std(game_lengths):.2f}',
f'Min = {min(game_lengths)}',
f'Max = {max(game_lengths)}',
'',
f'Log-Norm Peak = {np.exp(mean_log - np.power(std_log, 2)):.2f}',
f'Log-Norm Width = {std_log:.2f}']

xl = axes.get_xlim()
yl = axes.get_ylim()
axes.text(0.65*xl[1], 0.5*yl[1], '\n'.join(stats), fontsize=stat_text_size)

log_normal_fit(winning_games_lengths, winning_move_bins, checkmate_axes)
checkmate_axes.legend(fontsize=common.plot_params["legend text size"])
checkmate_figure.savefig(f'{file_name}_moves_in_game_histogram_checkmate.{pic_ext}', **common.picture_file_args)
plt.close(checkmate_figure)
Expand Down Expand Up @@ -303,3 +315,16 @@ def draw_outcome_plot(outcome, label):

timeout_figure.savefig(f'{file_name}_moves_in_game_histogram_timeout.{pic_ext}', **common.picture_file_args)
plt.close(timeout_figure)

resignation_games = (white_resignations | black_resignations)
resignation_game_lengths = moves_in_game[resignation_games]
resignation_counts, resignation_bins = np.histogram(resignation_game_lengths, range(max(moves_in_game) + 1))
resignation_figure, resignation_axes = plt.subplots()
resignation_axes.bar(resignation_bins[0:-1], resignation_counts, width=1, facecolor=bar_color, edgecolor=bar_color, linewidth=bar_line_width)
resignation_axes.set_title("Resignation game lengths")
resignation_axes.set_xlabel("Moves in Game")
resignation_axes.set_ylabel(f"Counts (total = {sum(resignation_games)})")
resignation_axes.set_xlim(0, max_game_length_display)
log_normal_fit(resignation_game_lengths, resignation_bins, resignation_axes)

resignation_figure.savefig(f"{file_name}_moves_in_game_histogram_resignation.{pic_ext}", **common.picture_file_args)
16 changes: 13 additions & 3 deletions src/Game/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,18 @@ Game_Result play_game(Board board,
while( ! result.game_has_ended())
{
const auto& player = board.whose_turn() == Piece_Color::WHITE ? white : black;
const auto& move_chosen = player.choose_move(board, game_clock);
const auto decision = player.choose_move(board, game_clock);
if(decision.resigned())
{
result = Game_Result(opposite(board.whose_turn()), Game_Result_Type::RESIGNATION);
if(decision.resigned_due_to_checkmate())
{
result.set_resigned_on_checkmate();
}
break;
}

const auto& move_chosen = decision.move();
if(Player::thinking_mode() != Thinking_Output_Type::NO_THINKING)
{
std::cout << player.name() << " chose " << move_chosen.algebraic(board) << '\n';
Expand Down Expand Up @@ -175,10 +185,10 @@ void play_game_with_outsider(const Player& player,
print_game_record = true;

player_color = board.whose_turn();
const auto& chosen_move = player.choose_move(board, clock);
const auto& decision = player.choose_move(board, clock);
clock.punch(board);

game_result = outsider->handle_move(board, chosen_move, game_record);
game_result = outsider->handle_decision(board, decision, game_record);
} while( ! game_result.game_has_ended());

outsider->log("Game ended with: {}", game_result.ending_reason());
Expand Down
13 changes: 13 additions & 0 deletions src/Game/Game_Result.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Game/Game_Result.h"

#include <string>
#include <cmath>

#include "Game/Color.h"

Expand Down Expand Up @@ -64,6 +65,8 @@ std::string Game_Result::ending_reason() const noexcept
return "Time forfeiture";
case Game_Result_Type::TIME_EXPIRED_WITH_INSUFFICIENT_MATERIAL:
return "Time expired with insufficient material";
case Game_Result_Type::RESIGNATION:
return color_text(opposite(static_cast<Piece_Color>(winner()))) + " resigned";
case Game_Result_Type::OTHER:
return alternate_reason;
default:
Expand All @@ -84,6 +87,16 @@ std::string Game_Result::game_ending_annotation() const noexcept
}
}

void Game_Result::set_resigned_on_checkmate() noexcept
{
resigned_due_to_checkmate = true;
}

bool Game_Result::resigned_on_checkmate() const noexcept
{
return resigned_due_to_checkmate;
}

bool Game_Result::game_has_ended_by_rule() const noexcept
{
return game_has_ended() && cause != Game_Result_Type::OTHER;
Expand Down
5 changes: 5 additions & 0 deletions src/Game/Game_Result.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class Game_Result_Type
INSUFFICIENT_MATERIAL,
TIME_FORFEIT,
TIME_EXPIRED_WITH_INSUFFICIENT_MATERIAL,
RESIGNATION,
OTHER
};

Expand Down Expand Up @@ -65,13 +66,17 @@ class Game_Result
//! \brief Returns the part of the PGN move annotation that goes after the # (checkmate) or + (check).
std::string game_ending_annotation() const noexcept;

void set_resigned_on_checkmate() noexcept;
bool resigned_on_checkmate() const noexcept;

//! \brief Returns whether or not the program should shutdown after getting this result.
bool exit_program() const noexcept;

private:
Winner_Color victor;
Game_Result_Type cause;
std::string alternate_reason;
bool resigned_due_to_checkmate = false;
bool shutdown_program;

bool game_has_ended_by_rule() const noexcept;
Expand Down
6 changes: 5 additions & 1 deletion src/Genes/Gene_Pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,12 @@ void gene_pool(const std::string& config_file)
const auto mating_winner = (winner == Winner_Color::NONE ? (Random::coin_flip() ? Winner_Color::WHITE : Winner_Color::BLACK) : winner);
auto& winning_player = (mating_winner == Winner_Color::WHITE ? white : black);
auto& losing_player = (winning_player.id() == white.id() ? black : white);
const auto loser_mates = winner == Winner_Color::NONE || (String::contains(result.ending_reason(), "resign")
&& ! result.resigned_on_checkmate()
&& Random::coin_flip());
const auto& mating_player = (loser_mates ? losing_player : winning_player);

auto offspring = Genetic_AI(white, black);
auto offspring = Genetic_AI(winning_player, mating_player);
offspring.mutate(mutation_rate);
offspring.print(genome_file_name);
losing_player = offspring;
Expand Down
13 changes: 13 additions & 0 deletions src/Genes/Genome.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "Genes/Checkmate_Material_Gene.h"
#include "Genes/Pawn_Structure_Gene.h"
#include "Genes/Move_Sorting_Gene.h"
#include "Genes/Resignation_Gene.h"

namespace
{
Expand All @@ -42,6 +43,7 @@ Genome::Genome() noexcept :
std::make_unique<Piece_Strength_Gene>(),
std::make_unique<Look_Ahead_Gene>(),
std::make_unique<Move_Sorting_Gene>(),
std::make_unique<Resignation_Gene>(),
std::make_unique<Total_Force_Gene>(nullptr),
std::make_unique<Freedom_To_Move_Gene>(),
std::make_unique<Pawn_Advancement_Gene>(),
Expand All @@ -60,6 +62,7 @@ Genome::Genome() noexcept :
assert(gene_reference<Piece_Strength_Gene>().name() == "Piece Strength Gene");
assert(gene_reference<Look_Ahead_Gene>().name() == "Look Ahead Gene");
assert(gene_reference<Move_Sorting_Gene>().name() == "Move Sorting Gene");
assert(gene_reference<Resignation_Gene>().name() == "Resignation Gene");
}

Genome::Genome(const Genome& other) noexcept : id_number(other.id())
Expand Down Expand Up @@ -257,6 +260,16 @@ void Genome::print(std::ostream& os) const noexcept
os << "END\n\n";
}

bool Genome::should_resign(const std::vector<Game_Tree_Node_Result>& commentary, const Piece_Color perspective) const noexcept
{
return gene_reference<Resignation_Gene>().should_resign(commentary, perspective);
}

void Genome::use_centipawn_value(const double centipawn_value) const noexcept
{
gene_reference<Resignation_Gene>().set_centipawn_value(centipawn_value);
}

Clock::seconds Genome::time_to_examine(const Board& board, const Clock& clock) const noexcept
{
return gene_reference<Look_Ahead_Gene>().time_to_examine(board, clock);
Expand Down
6 changes: 5 additions & 1 deletion src/Genes/Genome.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

class Board;
class Move;
class Game_Tree_Node_Result;

//! \brief A software analog to a biological chromosome containing a collection of Gene instances that control the chess player's behavior.
class Genome
Expand Down Expand Up @@ -120,9 +121,12 @@ class Genome
//! \param os The output stream.
void print(std::ostream& os) const noexcept;

bool should_resign(const std::vector<Game_Tree_Node_Result>& commentary, const Piece_Color perspective) const noexcept;
void use_centipawn_value(double centipawn_value) const noexcept;

private:
int id_number;
std::array<std::unique_ptr<Gene>, 14> genome;
std::array<std::unique_ptr<Gene>, 15> genome;

double score_board(const Board& board, Piece_Color perspective, size_t depth) const noexcept;
void reset_piece_strength_gene() noexcept;
Expand Down
73 changes: 73 additions & 0 deletions src/Genes/Resignation_Gene.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "Resignation_Gene.h"

#include "Gene_Value.h"
#include "Utility/Random.h"
#include "Game/Color.h"
#include "Players/Game_Tree_Node_Result.h"

Resignation_Gene::Resignation_Gene() noexcept : Clonable_Gene("Resignation Gene")
{
}

bool Resignation_Gene::should_resign(const std::vector<Game_Tree_Node_Result>& commentary, const Piece_Color perspective) const noexcept
{
auto under_floor_streak = 0;
for(auto it = commentary.rbegin(); it != commentary.rend(); ++it)
{
if(it->corrected_score(perspective) < internal_score_floor)
{
++under_floor_streak;
if(under_floor_streak > max_under_floor_streak.value())
{
return true;
}
}
else
{
return false;
}
}

return false;
}

void Resignation_Gene::set_centipawn_value(const double input_centipawn_value) const noexcept
{
const auto pawn_value = input_centipawn_value*100.0;
internal_score_floor = board_score_floor.value()*pawn_value;
}

void Resignation_Gene::gene_specific_mutation() noexcept
{
if(Random::coin_flip())
{
board_score_floor.mutate();
}
else
{
max_under_floor_streak.mutate();
if(max_under_floor_streak.value() < 0.0)
{
max_under_floor_streak.value() *= -1;
}
}
}

void Resignation_Gene::adjust_properties(std::map<std::string, std::string>& properties) const noexcept
{
delete_activations(properties);
delete_priorities(properties);
board_score_floor.write_to_map(properties);
max_under_floor_streak.write_to_map(properties);
}

void Resignation_Gene::load_gene_properties(const std::map<std::string, std::string>& properties)
{
board_score_floor.load_from_map(properties);
max_under_floor_streak.load_from_map(properties);
}

double Resignation_Gene::score_board(const Board&, Piece_Color, size_t) const noexcept
{
return 0.0;
}
Loading