Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • francesco-bariatti/pingouins
  • Samuel.Felton/pingouins
  • Lucas.Clement/pingouins
3 results
Show changes
Showing
with 21 additions and 1517 deletions
# PINGOUINS
Le jeu des pingouins!
## Objectif
Créer une Intelligence Artificielle qui soit capable de jouer au [jeu des pingouins](http://www.jeuxavolonte.asso.fr/regles/pingouin.pdf) grâce à l'algorithme *Montecarlo Tree Search*
# Penguin game
Implementation of the Penguin Game with an AI that uses MonteCarlo Tree Search algorithm.
## Auteurs
Le groupe K d'études pratiques de 3INFO
## Prerequisites
- g++ >= 4.9
- java >= 1.8
## Compiling/launching
Compile the project by running `make`
Run the project by running `make run`
## Documentation
A documentation (french only) is available in the `Doc` folder. It explains how to use the program and how it is implemented.
## License
This project is licensed under the MIT License. License information and authors attributions can be found in `LICENSE.txt`
## Authors
A group of students from INSA Rennes
- Francesco Bariatti
- Adrien Gasté
- Mikael Le
- Romain Lebouc
And their teacher
- Pascal Garcia
# Fonctions Test
Fonctions qui servent pour tester le programme: ces fonctions sont appelées par le programme principal.
## play()
Jouer une partie joueur vs joueur, sert pour tester si les règles sont bien implementées dans le programme
## playout()
Jouer une partie joueur vs ordi: programme final. Comment ça marche ?
- Créer une instance du jeu
- Créer une instance du mcts pour ce jeu: `auto mctsPlayer = mcts::make_mcts_two_players(JEU, 5000, 0.3, 4); `
- 5000: temps de reflexion en ms pour chaque tour du jeu
- 0.3 Facteur d'exploration (?)
- 4 nombre de visites avant expansion: un noeud sera visité 4 fois avant que son expansion soit faite dans l'arbre
- Tant que le jeu n'est pas fini, si c'est le tour de l'ordi
- Soit dire à l'ordi ce qu'on a choisi comme coup(?), soit lui faire un `mctsPlayer.reset()` qui efface tout l'arbre.
- Demander à l'ordi de choisir un coup `mctsPlayer.select_move();`
- Jouer ce coup
- Quand le jeu est fini dire à l'humain qu'il a perdu (parce que le MCTS est trop fort)
\ No newline at end of file
# Game interface (game/game.hpp)
Interface qui décrit un jeu, un jeu doit implémenter les fonctions décrites dans cette interface
Comment implementer l'interface: squelette du fichier .h du jeu
```C++
namespace game
{
struct JEU_state //structure qui décrit un état du jeu
{
[...]
}
class JEU : public game<JEU_state>
{
public:
JEU();
JEU(const JEU& J) = default;
JEU& operator=(const JEU& J) = default;
bool end_of_game() const;
int value(std::uint8_t player) const;
bool won(std::uint8_t player) const;
bool lost(std::uint8_t player) const;
bool draw(std::uint8_t player) const;
uint8_t current_player() const;
std::uint16_t number_of_moves() const;
void play(std::uint16_t m);
void undo(std::uint16_t m) {}
std::string player_to_string(std::uint8_t player) const;
std::string move_to_string(std::uint16_t m) const;
std::string to_string() const;
void playout(std::mt19937& engine, int max_depth = -1);
std::set<int> to_input_vector() const;
void from_input_vector(const std::set<int>& input);
morpion_state get_state();
void set_state(const morpion_state& state);
std::shared_ptr<game<morpion_state>> do_copy() const;
std::uint64_t hash(std::uint16_t m) const;
std::uint64_t hash() const;
private:
[... Methodes utiles pour le jeu mais pas nécessaires pour le MCTS ...]
};
std::ostream& operator<<(std::ostream& os, const JEU& J);
}
```
## Explication des différentes fonctions:
### bool end_of_game()
Est-ce que le jeu est terminé ? Soit parce que quelqu'un a gagné, soit parce que c'est égalité
### int value (int player)
Quelle est la valeur pour le joueur `player` dans l'état actuel du jeu ? Normalement 1 veut dire qu'il a gagné, -1 qu'il a perdu, 0 c'est égalité
### bool won/lost/draw(int player)
Est-ce que le joueur `player` a gagné/perdu/fait égalité ?
### int current_player()
C'est à quel joueur de jouer maintenant ? (Au début ce sera le joueur 0)
### int number_of_moves()
Nombre de coups que le joueur qui doit jouer maintenant peut jouer.
### play(int move)
Jouer le coup numéro `move` parmi les coups possibles: mets à jour l'état.
`move` represente la position du coup parmi tous les coups possibles, ce n'est pas la réprésentation du coup.
### undo(int move)
Pas implementé.
### string player_to_string(int player)
Representation d'un joueur de façon comprehensible par un humain
### string move_to_string(int move)
Representation d'un coup de façon comprehensible par un humain (par exemple B7).
`move` est l'indice du coup parmi tous les coups possibles
### string to_string()
Representation de l'état actuel du jeu (c'est à dire, dessiner l'état actuel du jeu)
### playout(engine, max_depth)
Joue le jeu en choisissant des coups au hasard jusqu'à la fin du jeu.
### get/set_state()
Retourne/charge l'état
### Les autres
Non utilisées
## Fonction play(int move)
Elle doit jouer le coup numéro `move` parmi les coups possibles. Algorithme:
- Recuperer quel coup est le coup numéro `move` dans la liste des coups possibles
- Mettre à jour le bon bitboard selon quel jouer doit jouer maintenant
- Ajouter 1 au nombre de coups joués jusqu'à maintenant (si nécessaire)
- Vérifier si un joueur a gagné et mettre à jour l'état
- Mettre à jour la liste de tous les coups possibles
## Fonction playout
```C++
void JEU::playout(mt19937& engine, int max_depth)
{
while (!end_of_game())
{
uniform_int_distribution<uint16_t> distribution(0, number_of_moves() -1);
uint16_t move = distribution(engine);
play(move);
}
}
```
Choisit un coup au hasard parmi les coups possibles et le joue.
Calcul des coups possibles à partir de la case i :
Vers direction A :
tantQue ((i+7) < 60 && i%15 != 0 && (i+7) estLibre)
{
i += 7;
ajout i dans listeCoupsPossibles;
}
finTantQue
Vers B :
tantQue (i%15 != 0 && i%15 != 8 && (i-1) estLibre)
{
i --;
ajout i dans listeCoupsPossibles;
}
finTantQue
Vers C :
tantQue ((i-8) >0 && i%15 != 0 && (i-8) estLibre)
{
i -= 8;
ajout i dans listeCoupsPossibles;
}
finTantQue
Vers D :
tantQue ((i-7) >0 && i%15 != 7 && (i-7) estLibre)
{
i -= 7;
ajout i dans listeCoupsPossibles;
}
finTantQue
Vers E :
tantQue (i%15 != 7 && i%15 != 14 && (i+1) estLibre)
{
i ++;
ajout i dans listeCoupsPossibles;
}
finTantQue
Vers F :
tantQue ((i+8) <60 && i%15 != 7 && (i+8) estLibre)
{
i += 8;
ajout i dans listeCoupsPossibles;
}
finTantQue
PLateau:
60 cases hexagonales verticales(sommet vers le bas)
Ligne du bas: 8 cases
Ligne du haut: 7 cases
8 lignes au total
Chaque ligne numerotée à partir de la droite. Le 0 est en bas à droite, le 59 en haut à gauche
Cases:
La direction A est vers le haut à droite (+7)
La direction B est vers la droite (-1)
La direction C est vers le bas à droite (-8)
etc...
==> Nom des joueurs : Red, Blue
Le premier joueur est Red
Nombre maximum de coups possibles par direction : 7
Structure 1 pingouin (bitlist sur 32 bits) :
+ + + + + +
| ??|Nb coups possibles ...|Nb coups possibles| Nombre total | Position sur |
| |direction F |direction A | de coups | le plateau (0-59) |
+------------------------------------------------------------+-------------------+
2 | 3 bits 3 bits | 6 bits 6 bits
| |
| |
+-----------------------------------------+
18 bits
==> 8 structures pingouin
Vocabulaire :
Obstacle = Trou ou Pingouin
==> 3 bitboards pour les poissons
Bitboards Points : {
1POISSON
2POISSONS
3POISSONS
}
=> 1POISSON|2POISSONS|3POISSONS = BITB_TROUS (0=trou)
Bitboard obstacles {
1 s'il y a un obstacle
0 sinon
}
==> ~Bitboard trous (OR 1 << Pos. pingouin1) (OR 1 << Pos. pingouin2)...(OR 1 << Pos. pingouin8)
File deleted
package controller;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import model.GameState;
import model.Player;
import model.Tile;
import view.TileView;
import java.io.*;
public class UpdateThread extends Thread
{
BufferedReader reader;
PrintWriter writer;
GameState gameState;
Tile[] board;
TileView[] boardView;
Player humanPlayer;
Label scoreRed, scoreBlue, turnLabel;
public UpdateThread(Process program, GameState gameState, Tile[] board, TileView[] boardView, Player humanPlayer, Label scoreRed, Label scoreBlue, Label turnLabel)
{
this.reader = new BufferedReader(new InputStreamReader(program.getInputStream()));
this.writer = new PrintWriter(new OutputStreamWriter(program.getOutputStream()), true);
this.gameState = gameState;
this.board = board;
this.boardView = boardView;
this.humanPlayer = humanPlayer;
this.scoreRed = scoreRed;
this.scoreBlue = scoreBlue;
this.turnLabel = turnLabel;
}
public void run()
{
boolean gameRunning = true;
while (gameRunning)
{
try
{
String line = reader.readLine();
System.out.println(line);
if (line == null)
{
gameRunning = false;
} else if (line.startsWith(Player.Red + " won") || line.startsWith(Player.Blue + " won") || line.startsWith("draw"))
{
//System.out.println("======= END OF GAME=======");
Platform.runLater(() -> {
Player computer = humanPlayer.equals(Player.Red) ? Player.Blue : Player.Red;
String message = "";
if (line.startsWith(humanPlayer.toString())) //Human won
message = "You won!!! I can't believe it!";
else if (line.startsWith(computer.toString()))
message = "You just lost the penguin game.";
else
message = "That's a draw. Not bad!";
Alert alert = new Alert(Alert.AlertType.INFORMATION, message, ButtonType.OK);
alert.showAndWait();
});
gameRunning = false;
} else if (line.contains("{")) //Line contains JSON
{
//UPDATE MODEL
gameState.update(line.substring(line.indexOf("{"), line.lastIndexOf("}") + 1)); //Extract JSON string
for (int i = 0; i < board.length; i++)
{
board[i].setNbFish(gameState.getNbFish(i));
board[i].setPenguinPresence(Tile.PenguinPresence.NO_PENGUIN);
}
for (int i = 0; i < 4; i++)
{
board[gameState.getPenguinPos(Player.Red, i)].setPenguinPresence(Tile.PenguinPresence.RED_PENGUIN);
board[gameState.getPenguinPos(Player.Blue, i)].setPenguinPresence(Tile.PenguinPresence.BLUE_PENGUIN);
}
//UPDATE VIEW
Platform.runLater(() -> {
for (int i = 0; i < boardView.length; i++)
boardView[i].update();
scoreRed.setText(Integer.toString(gameState.getScore(Player.Red)));
scoreBlue.setText(Integer.toString(gameState.getScore(Player.Blue)));
turnLabel.setText(gameState.getCurrent_player()+ "'s turn");
});
if (gameState.getCurrent_player() == humanPlayer)
{
if (!gameState.getCanPlay(humanPlayer))
{
Platform.runLater(() ->
{
Alert alert = new Alert(Alert.AlertType.INFORMATION, "You can't play any move!", ButtonType.OK);
alert.showAndWait();
writer.println("0"); //This pass the turn
});
}
}
}
} catch (IOException e)
{
gameRunning = false;
Platform.runLater(() ->
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Error during reading from penguin program!", ButtonType.FINISH);
alert.showAndWait();
e.printStackTrace();
Platform.exit();
});
} catch (Throwable e)
{
gameRunning = false;
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR, "Unhandled exception in update thread: " + e.getMessage(), ButtonType.FINISH);
alert.showAndWait();
e.printStackTrace();
Platform.exit();
});
}
}
}
}
package main;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
import java.io.FileInputStream;
import java.io.InputStream;
public class Main extends Application
{
public static void main(String[] args)
{
launch(args);
}
@Override
public void start(Stage primaryStage)
{
try
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(Main.class.getResource("../view/view.fxml"));
BorderPane root = (BorderPane) loader.load();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Penguin game");
primaryStage.show();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
#include "connect4.hpp"
#include <sstream>
using namespace std;
namespace game
{
static vector<vector<uint64_t>> create_hash_values()
{
default_random_engine generator;
uniform_int_distribution<uint64_t> distribution;
vector<vector<uint64_t>> res(7, vector<uint64_t>(6, 0));
for (int i = 0; i < 7; ++i)
{
for (int j = 0; j < 6; ++j)
{
res[i][j] = distribution(generator);
}
}
return res;
}
vector<vector<uint64_t>> connect4::cross_hash_values = create_hash_values();
vector<vector<uint64_t>> connect4::circle_hash_values = create_hash_values();
connect4::connect4()
{
}
shared_ptr<game<connect4_state>> connect4::do_copy() const
{
return shared_ptr<connect4>(new connect4(*this));
}
connect4_state connect4::get_state()
{
return state;
}
void connect4::set_state(const connect4_state& s)
{
state = s;
}
bool connect4::end_of_game() const
{
return state.first_player_win || state.second_player_win || state.total_moves == 42;
}
bool connect4::won(std::uint8_t player) const
{
if (player == 0) return state.first_player_win;
return state.second_player_win;
}
bool connect4::lost(std::uint8_t player) const
{
if (player == 0) return state.second_player_win;
return state.first_player_win;
}
bool connect4::draw(std::uint8_t player) const
{
if (state.first_player_win || state.second_player_win) return false;
return state.total_moves == 42;
}
uint8_t connect4::current_player() const
{
return state.total_moves & 1 ? CIRCLE : CROSS;
}
// uint8_t connect4::current_player_representation() const
// {
// return state.total_moves & 1 ? CIRCLE_REPRESENTATION : CROSS_REPRESENTATION;
// }
// uint8_t connect4::player_representation_to_player(uint8_t player) const
// {
// return player == CIRCLE_REPRESENTATION ? CIRCLE : (player == CROSS_REPRESENTATION ? CROSS : -1);
// }
int connect4::value(uint8_t player) const
{
if (player == CROSS)
{
return state.first_player_win ? 1 : (state.second_player_win ? -1 : 0);
}
else if (player == CIRCLE)
{
return state.second_player_win ? 1 : (state.first_player_win ? -1 : 0);
}
return 0;
}
uint16_t connect4::number_of_moves() const
{
return state.nb_moves;
}
bool connect4::get(uint64_t bitboard, uint8_t col, uint8_t row) const
{
return bitboard & (1LL << ((col << 3) + row));
//return (state.board[i] >> (j << 1)) & 3;
}
#define set(bitboard, col, row) (bitboard |= (1LL << (((col) << 3) + (row))))
// bool connect4::vertical(uint8_t position, uint8_t free, uint8_t player) const
// {
// return free >= 3 && get(position, free - 1) == player && get(position, free - 2) == player && get(position, free - 3) == player;
// }
// bool connect4::horizontal(uint8_t position, uint8_t free, uint8_t player) const
// {
// uint8_t sum = 0;
// if (position >= 1 && get(position - 1, free) == player)
// {
// ++sum;
// if (position >= 2 && get(position - 2, free) == player)
// {
// ++sum;
// if (position >= 3 && get(position - 3, free) == player) ++sum;
// }
// }
// if (position <= 5 && get(position + 1, free) == player)
// {
// ++sum;
// if (position <= 4 && get(position + 2, free) == player)
// {
// ++sum;
// if (position <= 3 && get(position + 3, free) == player) ++sum;
// }
// }
// return sum >= 3;
// }
// bool connect4::diagonal(uint8_t position, uint8_t free, uint8_t player) const
// {
// uint8_t sum = 0;
// if (position >= 1 && free <= 4 && get(position - 1, free + 1) == player)
// {
// ++sum;
// if (position >= 2 && free <= 3 && get(position - 2, free + 2) == player)
// {
// ++sum;
// if (position >= 3 && free <= 2 && get(position - 3, free + 3) == player) ++sum;
// }
// }
// if (position <= 5 && free >= 1 && get(position + 1, free - 1) == player)
// {
// ++sum;
// if (position <= 4 && free >= 2 && get(position + 2, free - 2) == player)
// {
// ++sum;
// if (position <= 3 && free >= 3 && get(position + 3, free - 3) == player) ++sum;
// }
// }
// return sum >= 3;
// }
// bool connect4::other_diagonal(uint8_t position, uint8_t free, uint8_t player) const
// {
// uint8_t sum = 0;
// if (position >= 1 && free >= 1 && get(position - 1, free - 1) == player)
// {
// ++sum;
// if (position >= 2 && free >= 2 && get(position - 2, free - 2) == player)
// {
// ++sum;
// if (position >= 3 && free >= 3 && get(position - 3, free - 3) == player) ++sum;
// }
// }
// if (position <= 5 && free <= 4 && get(position + 1, free + 1) == player)
// {
// ++sum;
// if (position <= 4 && free <= 3 && get(position + 2, free + 2) == player)
// {
// ++sum;
// if (position <= 3 && free <= 2 && get(position + 3, free + 3) == player) ++sum;
// }
// }
// return sum >= 3;
// }
// void connect4::update_win(uint8_t position, uint8_t free)
// {
// uint8_t player = current_player_representation();
// bool win =
// vertical(position, free, player)
// ||
// horizontal(position, free, player)
// ||
// diagonal(position, free, player)
// ||
// other_diagonal(position, free, player);
// if (win)
// {
// if (player == CROSS_REPRESENTATION) state.first_player_win = true;
// else state.second_player_win = true;
// }
// }
void connect4::update_win()
{
if (has_won(state.cross_bitboard)) state.first_player_win = true;
else if (has_won(state.circle_bitboard)) state.second_player_win = true;
}
bool connect4::has_won(uint64_t bitboard)
{
int64_t y = bitboard & (bitboard >> 7);
if (y & (y >> 2 * 7)) // check \ diagonal
return true;
y = bitboard & (bitboard >> 8);
if (y & (y >> 2 * 8)) // check horizontal -
return true;
y = bitboard & (bitboard >> 9);
if (y & (y >> 2 * 9)) // check / diagonal
return true;
y = bitboard & (bitboard >> 1);
if (y & (y >> 2)) // check vertical |
return true;
return false;
}
void connect4::update_moves(uint16_t move)
{
--state.nb_moves;
uint32_t prefix = state.moves >> ((move + 1) * 3);
uint8_t shift = 32 - move * 3;
state.moves = (uint32_t)((uint64_t)state.moves << shift) >> shift;
state.moves |= prefix << (move * 3);
}
void connect4::play(uint16_t m)
{
uint8_t position = ((state.moves >> (m * 3)) & 7);
uint8_t p = position * 3;
uint8_t f = (state.free >> p) & 7;
// state.hash_value ^= current_player() == CIRCLE ? circle_hash_values[position][f] : cross_hash_values[position][f];
// state.board[position] |= current_player_representation() << (f << 1);
if (current_player() == CROSS)
{
set(state.cross_bitboard, position, f);
}
else
{
set(state.circle_bitboard, position, f);
}
update_win();
++f;
state.free = (state.free & ~(((uint32_t)7) << p)) | (f << p);
if (f == 6)
{
update_moves(m);
}
++state.total_moves;
}
string connect4::player_to_string(uint8_t player) const
{
return player == CROSS ? "X" : (player == CIRCLE ? "O" : " ");
}
string connect4::move_to_string(uint16_t m) const
{
uint8_t position = ((state.moves >> (m * 3)) & 7);
return std::to_string(position);
}
set<int> connect4::to_input_vector() const
{
set<int> res;
int k = 0;
if (current_player()) res.insert(k++);
for (int col = 0; col < 7; ++col)
{
for (int row = 0; row < 6; ++row)
{
if (get(state.cross_bitboard, col, row)) res.insert(k++);
}
}
for (int col = 0; col < 7; ++col)
{
for (int row = 0; row < 6; ++row)
{
if (get(state.circle_bitboard, col, row)) res.insert(k++);
}
}
return res;
}
void connect4::from_input_vector(const std::set<int>& input)
{
state = connect4_state();
for (int index : input)
{
if (index == 1) continue;
if (index <= 43)
{
index -= 2;
state.cross_bitboard |= 1LL << (index + (index / 6) * 2);
}
else
{
index -= 44;
state.circle_bitboard |= 1LL << (index + (index / 6) * 2);
}
state.total_moves += 1;
}
}
string connect4::to_string() const
{
stringbuf buffer;
ostream os(&buffer);
for (int y = 5; y >= 0; --y)
{
for (int k = 0; k < 7; ++k) os << "+-";
os << "+" << endl;
for (int x = 0; x < 7; ++x)
{
os << "|" << (get(state.cross_bitboard, x, y) ? player_to_string(CROSS) : (get(state.circle_bitboard, x, y) ? player_to_string(CIRCLE) : " "));
}
os << "|" << endl;
}
for (int k = 0; k < 7; ++k) os << "+-";
os << "+" << endl;
for (int k = 0; k < 7; ++k) os << " " << k;
os << endl;
return buffer.str();
}
// void connect4::playout(mt19937& engine, int max_depth)
// {
// while (!end_of_game())
// {
// uniform_int_distribution<uint16_t> distribution(0, number_of_moves() - 1);
// uint16_t move = distribution(engine);
// play(move);
// }
// }
std::uint64_t connect4::hash() const
{
return state.hash_value;
}
std::uint64_t connect4::hash(std::uint16_t m) const
{
uint8_t position = ((state.moves >> (m * 3)) & 7);
uint8_t p = position * 3;
uint8_t f = (state.free >> p) & 7;
return state.hash_value ^ (current_player() == CIRCLE ? circle_hash_values[position][f] : cross_hash_values[position][f]);
}
ostream& operator<<(ostream& os, const connect4& c4)
{
os << c4.to_string() << endl;
return os;
}
}
#ifndef __CONNECT4_HPP__
#define __CONNECT4_HPP__
#include "game.hpp"
#include <random>
#include <array>
#include <iostream>
#include <memory>
namespace game
{
struct connect4_state
{
uint64_t cross_bitboard = 0;
uint64_t circle_bitboard = 0;
// std::array<uint16_t, 7> board{};
std::uint32_t moves = 0x1AC688;
std::uint32_t free = 0;
uint64_t hash_value = 0;
std::uint16_t nb_moves = 7;
uint8_t total_moves = 0;
bool first_player_win = false;
bool second_player_win = false;
};
class connect4 : public game<connect4_state>
{
public:
connect4();
connect4(const connect4& c4) = default;
connect4& operator=(const connect4& c4) = default;
bool end_of_game() const;
int value(std::uint8_t player) const;
bool won(std::uint8_t player) const;
bool lost(std::uint8_t player) const;
bool draw(std::uint8_t player) const;
uint8_t current_player() const;
std::uint16_t number_of_moves() const;
void play(std::uint16_t m);
void undo(std::uint16_t m) {}
std::string player_to_string(std::uint8_t player) const;
std::string move_to_string(std::uint16_t m) const;
std::string to_string() const;
// void playout(std::mt19937& engine, int max_depth = -1);
std::set<int> to_input_vector() const;
void from_input_vector(const std::set<int>& input);
connect4_state get_state();
void set_state(const connect4_state& state);
std::shared_ptr<game<connect4_state>> do_copy() const;
std::uint64_t hash(std::uint16_t m) const;
std::uint64_t hash() const;
private:
inline void update_win();
inline bool has_won(uint64_t bitboard);
inline void update_moves(uint16_t move);
inline bool get(uint64_t bitboard, uint8_t i, uint8_t j) const;
// inline bool vertical(uint8_t position, uint8_t free, uint8_t player) const;
// inline bool horizontal(uint8_t position, uint8_t free, uint8_t player) const;
// inline bool diagonal(uint8_t position, uint8_t free, uint8_t player) const;
// inline bool other_diagonal(uint8_t position, uint8_t free, uint8_t player) const;
// inline uint8_t current_player_representation() const;
// inline uint8_t player_representation_to_player(uint8_t player) const;
const uint8_t CROSS = 0;
const uint8_t CIRCLE = 1;
// const uint8_t CROSS_REPRESENTATION = 1;
// const uint8_t CIRCLE_REPRESENTATION = 2;
connect4_state state;
static std::vector<std::vector<uint64_t>> cross_hash_values;
static std::vector<std::vector<uint64_t>> circle_hash_values;
};
std::ostream& operator<<(std::ostream& os, const connect4& c4);
}
#endif
#include "morpion.hpp"
#include <sstream>
using namespace std;
namespace game
{
static vector<vector<uint64_t>> create_hash_values()
{
default_random_engine generator;
uniform_int_distribution<uint64_t> distribution;
vector<vector<uint64_t>> res(7, vector<uint64_t>(6, 0));
for (int i = 0; i < 7; ++i)
{
for (int j = 0; j < 6; ++j)
{
res[i][j] = distribution(generator);
}
}
return res;
}
vector<vector<uint64_t>> morpion::cross_hash_values = create_hash_values();
vector<vector<uint64_t>> morpion::circle_hash_values = create_hash_values();
morpion::morpion()
{
}
shared_ptr<game<morpion_state>> morpion::do_copy() const
{
return shared_ptr<morpion>(new morpion(*this));
}
morpion_state morpion::get_state()
{
return state;
}
void morpion::set_state(const morpion_state& s)
{
state = s;
}
bool morpion::end_of_game() const
{
return state.first_player_win || state.second_player_win || state.total_moves == 9;
}
bool morpion::won(std::uint8_t player) const
{
if (player == CROSS) return state.first_player_win;
return state.second_player_win;
}
bool morpion::lost(std::uint8_t player) const
{
if (player == CIRCLE) return state.first_player_win;
return state.second_player_win;
}
bool morpion::draw(std::uint8_t player) const
{
if (state.first_player_win || state.second_player_win) return false;
return state.total_moves == 9;
}
uint8_t morpion::current_player() const
{
return state.total_moves & 1 ? CIRCLE : CROSS; // CROSS even, CIRCLE odd
}
int morpion::value(uint8_t player) const
{
if (player == CROSS) {
return state.first_player_win ? 1 : (state.second_player_win ? -1 : 0);
}
else if (player == CIRCLE) {
return state.second_player_win ? 1 : (state.first_player_win ? -1 : 0);
}
return 0;
}
/* Number of moves that you can play */
uint16_t morpion::number_of_moves() const
{
return 9 - state.total_moves;
}
uint16_t morpion::number_moves_played() const
{
return state.total_moves;
}
bool morpion::get(uint16_t bitboard, uint8_t col, uint8_t row) const
{
return bitboard & (1LL << (3*row)) << col;
}
//#define set(bitboard, col, row) (bitboard |= (1LL << (((col) << 3) + (row))))
void morpion::update_win()
{
if(has_won(state.cross_bitboard))
state.first_player_win = true;
else if(has_won(state.circle_bitboard))
state.second_player_win = true;
}
bool morpion::has_won(uint16_t bitboard)
{
if(((bitboard | ROW0_MASK) == ALL_ONES) || ((bitboard | ROW1_MASK) == ALL_ONES) || ((bitboard | ROW2_MASK) == ALL_ONES)) // Check horizontal ---
return true;
if(((bitboard | COL0_MASK) == ALL_ONES) || ((bitboard | COL1_MASK) == ALL_ONES) || ((bitboard | COL2_MASK) == ALL_ONES)) // Check vertical |
return true;
if(((bitboard | DIA0_MASK) == ALL_ONES) || ((bitboard | DIA1_MASK) == ALL_ONES)) // Chack diagonal \ /
return true;
return false;
}
void morpion::update_moves()
{
uint16_t free_bitboard = ~(state.cross_bitboard | state.circle_bitboard);
//free_bitboard &= ALL_ONES; //When we complements, all unused bits in uint_16 are complemented to. ALL_ONES is a mask in which we have ones in the position used by the bitboard
//cout << "Free bitboard: " << (free_bitboard & ALL_ONES) << endl;
state.possible_moves = 0;
uint16_t mask = 256; //256 = 100 000 000
for(int i = 8; i >=0; i--)
{
if(free_bitboard & mask)
{
state.possible_moves = state.possible_moves << 4;
state.possible_moves |= i;
}
mask = mask >> 1;
}
//cout << "Possible moves: " << state.possible_moves << endl;
}
//Play the mth move in the possible moves list.
void morpion::play(uint16_t m)
{
uint64_t possible_moves = state.possible_moves;
possible_moves = possible_moves >> 4*m; //A move is coded with 4 bit
uint16_t move = possible_moves & 15; //15 = 1111
//cout << "You choose the possible move number " << m << endl;
//cout << "You choose move " << move << endl;
if (current_player() == CROSS)
state.cross_bitboard |= (((uint16_t) 1) << move);
else
state.circle_bitboard |= (((uint16_t) 1) << move);
//State update
state.total_moves++;
update_win();
update_moves();
return;
}
/**
* player_to_string
* Retourne X si le joueur joue les croix, O s'il joue les ronds,
* et un espace sinon.
*/
string morpion::player_to_string(uint8_t player) const
{
return player == CROSS ? "X" : (player == CIRCLE ? "O" : " ");
}
string morpion::move_to_string(uint16_t m) const
{
return std::to_string((state.possible_moves >> (4 * m)) & 0xf);
}
set<int> morpion::to_input_vector() const
{
return set<int>();
}
void morpion::from_input_vector(const std::set<int>& input)
{
}
string morpion::to_string() const
{
string result = "-------\n";
for (int row = 2; row >= 0; row--)
{
result += "|";
for (int col = 2; col >= 0; col--)
{
if(((state.cross_bitboard >> (3*row)) >> col) & 1)
result += player_to_string(CROSS)+"|";
else if (((state.circle_bitboard >> (3*row)) >> col) & 1)
result += player_to_string(CIRCLE)+"|";
else
result += std::to_string(row * 3 + col) + "|";
}
result += "\n-------\n";
}
return result;
}
std::uint64_t morpion::hash() const
{
//TODO: Implement
return 0;
}
std::uint64_t morpion::hash(std::uint16_t m) const
{
//TODO: Implement
return 0;
}
ostream& operator<<(ostream& os, const morpion& mor)
{
os << mor.to_string() << endl;
return os;
}
}
#ifndef __MORPION_HPP__
#define __MORPION_HPP__
#include "game.hpp"
#include <random>
#include <array>
#include <iostream>
#include <memory>
namespace game
{
struct morpion_state
{
uint16_t cross_bitboard = 0; //bitboard with the played moves of the cross
uint16_t circle_bitboard = 0; //bitboard with the played moves of the circle
uint8_t total_moves = 0; //Total played moves (<= 9)
uint64_t possible_moves = 0x876543210; //List of possible moves left
bool first_player_win = false; //First player is always the cross
bool second_player_win = false; //Second player is always the circle
};
class morpion : public game<morpion_state>
{
public:
morpion();
morpion(const morpion& mor) = default;
morpion& operator=(const morpion& mor) = default;
bool end_of_game() const; //Is the game ended? (draw or won)
int value(std::uint8_t player) const; //Returns if the player win, loose or nothing
bool won(std::uint8_t player) const;
bool lost(std::uint8_t player) const;
bool draw(std::uint8_t player) const;
uint8_t current_player() const; //The player that has to play next (at the beginning, the first player)
std::uint16_t number_of_moves() const; //Number of moves that you can play
std::uint16_t number_moves_played() const; //Number of moves played until now
void play(std::uint16_t m); //Play the move m (updates the state). m is the position on the board of where you want to play
void undo(std::uint16_t m) {}
std::string player_to_string(std::uint8_t player) const; //String representation of a player
std::string move_to_string(std::uint16_t m) const; //String representation of a move (for example, A1)
std::string to_string() const; //String representation of the entire game
std::set<int> to_input_vector() const;
void from_input_vector(const std::set<int>& input);
morpion_state get_state(); //Return the state
void set_state(const morpion_state& state); //Replace the current state with the one passed as a parameter
std::shared_ptr<game<morpion_state>> do_copy() const;
std::uint64_t hash(std::uint16_t m) const;
std::uint64_t hash() const;
private:
inline void update_win(); //Check if someone won and update the state
inline bool has_won(uint16_t bitboard); //Check if the player whose bitboard was passed as a param has won
inline bool get(uint16_t bitboard, uint8_t i, uint8_t j) const; //Get a case of the board
inline void update_moves(); //Update the list of all possible moves
const uint8_t CROSS = 0;
const uint8_t CIRCLE = 1;
morpion_state state;
//WIN CONSTANTS
const uint16_t ROW0_MASK = 504;
const uint16_t ROW1_MASK = 455;
const uint16_t ROW2_MASK = 63;
const uint16_t COL0_MASK = 438;
const uint16_t COL1_MASK = 365;
const uint16_t COL2_MASK = 219;
const uint16_t DIA0_MASK = 238;
const uint16_t DIA1_MASK = 427;
const uint16_t ALL_ONES = 511;
static std::vector<std::vector<uint64_t>> cross_hash_values;
static std::vector<std::vector<uint64_t>> circle_hash_values;
};
std::ostream& operator<<(std::ostream& os, const morpion& mor);
}
#endif
#include "connect4.hpp"
#include "test_connect4.hpp"
#include <iostream>
#include <map>
using namespace std;
namespace game
{
test_connect4::test_connect4()
{
playout();
play();
}
void test_connect4::playout()
{
mt19937 mt;
connect4 c4;
cout << "playout" << endl;
for (int i = 0; i < 100; ++i)
{
uint8_t player = c4.current_player();
auto state = c4.get_state();
c4.playout(mt);
cout << "value: " << c4.value(player) << endl << c4 << endl;
c4.set_state(state);
string wait;
getline(cin, wait);
}
}
void test_connect4::play()
{
connect4 c4;
int player = 0;
cout << "play one game" << endl;
while (!c4.end_of_game())
{
cout << c4 << endl;
cout << c4.player_to_string(player) << " move: ";
map<string, int> m;
for (int i = 0; i < c4.number_of_moves(); ++i)
{
m[c4.move_to_string(i)] = i;
}
string move;
cin >> move;
c4.play(m[move]);
player = 1 - player;
}
cout << c4 << endl;
if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won";
else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won";
else cout << "draw";
cout << endl;
}
}
#ifndef __TEST_CONNECT4_HPP__
#define __TEST_CONNECT4_HPP__
namespace game
{
class test_connect4
{
void playout();
void play();
public:
test_connect4();
};
}
#endif
#include "test_connect4.hpp"
#include "connect4.hpp"
#include "morpion.hpp"
#include "penguin.hpp"
#include "test_two_players_game.hpp"
#include "test_monte_carlo.hpp"
#include "test_fast_log.hpp"
#include "test_allocator.hpp"
#include "test_mcts_two_players.hpp"
#include "test_minmax.hpp"
#include "test_bits.hpp"
#include "learning.hpp"
#include <omp.h>
using namespace std;
int main(int argc, char *argv[])
{
// game::test_connect4();
// util::test_fast_log(100000000);
// mcts::test_allocator(10, 2);
// omp_set_num_threads(8);
// game::run_test_two_players_game(game::connect4());
//mcts::run_test_mcts_two_players(game::connect4());
//game::run_test_two_players_game(game::morpion());
//mcts::run_test_mcts_two_players(game::morpion());
//game::run_test_two_players_game(game::penguin());
mcts::run_test_mcts_two_players(game::penguin());
// minmax::test_minmax();
//util::test_bits(200000000);
//game::connect4 c4;
// util::learning::display_file(c4, "learning_examples.txt");
return 0;
}
#include "connect4_heuristic.hpp"
namespace minmax
{
double connect4_heuristic::value(const connect4& c4) const
{
const connect4_state& state = c4.get_state();
return 0;
}
}
#ifndef __CONNECT4_HEURISTIC_HPP__
#define __CONNECT4_HEURISTIC_HPP__
#include <connect4.hpp>
namespace minmax
{
class connect4_heuristic : public heuristic<connect4>
{
double value(const connect4& c4) const;
};
}
#endif
#ifndef __HEURISTIC_HPP__
#define __HEURISTIC_HPP__
namespace minmax
{
template <typename Game>
class heuristic
{
public:
virtual double value(const Game& game) const = 0;
};
template <typename Game>
class null_heuristic : public heuristic<Game>
{
double value(const Game& game) const;
};
template <typename Game>
double null_heuristic<Game>::value(const Game& game) const
{
return 0;
}
}
#endif
#ifndef __MINMAX_HPP__
#define __MINMAX_HPP__
#include <chrono>
#include <vector>
#include "game.hpp"
#include "heuristic.hpp"
#include <iostream>
#include <limits>
namespace minmax
{
struct cannot_prove : std::exception
{
const char* what() const noexcept { return "cannot prove the game theoritical value"; }
};
template <typename Game>
class minmax
{
protected:
Game& game;
std::chrono::milliseconds milliseconds;
const heuristic<Game>& h;
std::vector<uint16_t> principal_variation;
std::chrono::steady_clock::time_point start;
public:
minmax(Game& game, uint32_t milliseconds, const heuristic<Game>& h = null_heuristic<Game>())
: game(game), milliseconds(milliseconds), h(h)
{
}
int prove();
double solve();
uint16_t select_move();
private:
long prove(int depth);
double solve(int depth);
};
template <typename Game>
minmax<Game> make_minmax(Game& game, uint32_t milliseconds, const heuristic<Game>& h = null_heuristic<Game>())
{
return minmax<Game>(game, milliseconds, h);
}
template <typename Game>
double minmax<Game>::solve()
{
return 0;
}
template <typename Game>
double minmax<Game>::solve(int depth)
{
return 0;
}
template <typename Game>
uint16_t minmax<Game>::select_move()
{
return 0;
}
const long UNKNOWN = std::numeric_limits<long>::max();
template <typename Game>
int minmax<Game>::prove()
{
start = std::chrono::steady_clock::now();
uint16_t nb_moves = game.number_of_moves();
long value[nb_moves];
std::fill_n(value, nb_moves, UNKNOWN);
int depth = 3;
auto state = game.get_state();
try
{
int nb_proved_moves = 0, nb_draws = 0;
while (true)
{
for (uint16_t move = 0; move < nb_moves; ++move)
{
if (value[move] != UNKNOWN) continue;
game.play(move);
value[move] = prove(depth);
game.set_state(state);
if (value[move] == -1) return 1;
if (value[move] == 0) ++nb_draws;
if (value[move] != UNKNOWN) ++nb_proved_moves;
}
if (nb_proved_moves == nb_moves)
{
if (nb_draws != 0) return 0;
return -1;
}
++depth;
}
}
catch (cannot_prove fail)
{
game.set_state(state);
}
throw cannot_prove();
}
template <typename Game>
long minmax<Game>::prove(int depth)
{
if (game.end_of_game()) return game.value(game.current_player());
if (depth == 0) return UNKNOWN;
if ((depth & 3) && std::chrono::steady_clock::now() >= start + milliseconds) throw cannot_prove();
uint16_t nb_moves = game.number_of_moves();
auto state = game.get_state();
int nb_proved_moves = 0, nb_draws = 0;
for (uint16_t move = 0; move < nb_moves; ++move)
{
game.play(move);
long v = prove(depth - 1);
game.set_state(state);
if (v == -1) return 1;
if (v == 0) ++nb_draws;
if (v != UNKNOWN) ++nb_proved_moves;
}
if (nb_proved_moves == nb_moves)
{
if (nb_draws != 0) return 0;
return -1;
}
return UNKNOWN;
}
}
#endif
#include "minmax.hpp"
#include "test_minmax.hpp"
#include "connect4.hpp"
#include <iostream>
#include <iomanip>
#include <map>
using namespace std;
using namespace game;
namespace minmax
{
test_minmax::test_minmax()
{
play();
}
template <typename Game>
int test_minmax::select_move(Game& game)
{
cout << game.player_to_string(game.current_player()) << " move: ";
map<string, int> m;
for (int i = 0; i < game.number_of_moves(); ++i)
{
m[game.move_to_string(i)] = i;
}
string move;
getline(cin, move);
game.play(m[move]);
return m[move];
}
void test_minmax::play()
{
connect4 c4;
auto minimax = make_minmax(c4, 2000);
cout << "play one game" << endl;
cout << c4 << endl;
while (!c4.end_of_game())
{
cout << "try to prove? (y/n)" << endl;
string ans;
getline(cin, ans);
if (ans == "y")
{
try
{
int v = minimax.prove();
if (v == 1) cout << "won position" << endl;
if (v == -1) cout << "lost position" << endl;
if (v == 0) cout << "drawn position" << endl;
}
catch (exception fail)
{
cout << "unable to prove" << endl;
}
}
select_move(c4);
cout << c4 << endl;
}
if (c4.value(0) == 1) cout << c4.player_to_string(0) << " won";
else if (c4.value(1) == 1) cout << c4.player_to_string(1) << " won";
else cout << "draw";
cout << endl;
}
}