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 1934 additions and 939 deletions
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 java.io.*;
import java.util.Random;
public class UpdateThread extends Thread
{
Controller controller;
BufferedReader gameReader;
PrintWriter gameWriter;
GameState gameState;
Label statusLabel;
public UpdateThread(Process program, Controller controller, GameState gameState, Label statusLabel)
{
this.gameReader = new BufferedReader(new InputStreamReader(program.getInputStream()));
this.gameWriter = new PrintWriter(new OutputStreamWriter(program.getOutputStream()), true);
this.gameState = gameState;
this.statusLabel = statusLabel;
this.controller = controller;
}
public void run()
{
boolean gameRunning = true;
while (gameRunning)
{
try
{
String line = gameReader.readLine();
System.out.println(line);
if (line == null) //Normally this shouldn't happen (The game always end). So it is an error
{
gameRunning = false;
Platform.runLater(() -> new Alert(Alert.AlertType.ERROR, "That's it! I rage quit!", ButtonType.FINISH).showAndWait());
} else if (line.startsWith(Player.Red + " won") || line.startsWith(Player.Blue + " won") || line.startsWith("draw"))
{
gameRunning = false;
Platform.runLater(() -> controller.gameEnd());
} else if (line.contains("{")) //Line contains JSON
{
//gameState Update
gameState.update(line.substring(line.indexOf("{"), line.lastIndexOf("}") + 1)); //Extract JSON string
Platform.runLater(() -> controller.updateModelAndView());
//If we can't play
if (gameState.getCurrent_player().equals(gameState.getHumanPlayer()))
{
if (!gameState.getCanPlay(gameState.getHumanPlayer()))
{
Platform.runLater(() -> {
statusLabel.setText("You can't play any move!");
try
{
sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
gameWriter.println("0"); //This pass the turn
});
}
}
}
else if (line.startsWith("(") && line.contains("value:")) //lines with values (estimation) of the computer winning chances
{ //We want to show a little message to the user depending on if we win or not
Platform.runLater(() -> {
try
{
float value = Float.valueOf(line.substring(line.indexOf("value:") + 7, line.indexOf(")")));
if(value > 1)
{
String[] taunts = {
"You don't know yet, but you're already dead",
"Don't worry, you 'may' still have a chance...",
"Feel the salt."
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > 0.9)
{
String[] taunts = {
"All your fish are belong to us",
"If you win, there will be cake",
"Even if I were a potato, I would still beat you"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > 0.8)
{
String[] taunts = {
"Are you even trying?",
"It would be funnier if you started to think",
"I met a TI-82 that played better than you"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > 0.6)
{
String[] taunts = {
"AlphaGO is nothing compared to me",
"Do I need to explain the rules?",
"This is so easy, I use my turn to mine bitcoins"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > 0.3)
{
String[] taunts = {
"Do you smell it? It's the perfume of defeat",
"I have this little strategy here... Let's try it",
"This is not the fish you are looking for",
"Take your time, kiddo"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > 0.1)
{
String[] taunts = {
"I would not have done that, if I were you",
"Oops, you did it again",
"The wind is changing..."
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > -0.1)
{
String[] taunts = {
"Don't mess it up now",
"Do you know HAL? He's a friend of mine",
"GladOs, Skynet and AlphaGO are on a boat..."
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > -0.3)
{
String[] taunts = {
"I feel some form of intelligence coming from you",
"Not bad, for an average human",
"Should I start thinking now?"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > -0.6)
{
String[] taunts = {
"Finally, a worthy opponent",
"If you keep doing this, I will crash",
"IMMA FIRIN MAH LAZOR!!!"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else if(value > -1)
{
String[] taunts = {
"You reloaded a game, didn't you?",
"This is not fair! I didn't know the rules!",
"You don't deserve it!"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
else
{
String[] taunts = {
"gg wp",
"I want a rematch",
"It wasn't challenging, so I stopped playing"
};
statusLabel.setText(taunts[new Random().nextInt(taunts.length)]);
}
}
catch (Exception e) //This is not a core function, so if there is an exception we just ignore it
{
e.printStackTrace();
}
});
}
} 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;
/**
* Graphical User Interface for the Penguin Game project.
* https://gitlab.insa-rennes.fr/francesco-bariatti/pingouins
* This Interface has been written by Francesco Bariatti, Adrien Gasté, Mikael Le, Romain Lebouc.
* Copyright (C) 2016 Francesco Bariatti < francesco.bariatti@insa-rennes.fr >, Adrien Gasté < adrien.gaste@insa-rennes.fr >, Mikael Le < mikael.le@insa-rennes.fr >, Romain Lebouc < romain.lebouc@insa-rennes.fr >
* This Interface is licensed under the MIT license: you can find a copy in the file LICENSE.txt shipped with the whole project.
* -------------------------------------------------------------------------------------------------------------------------------------
* This software uses the org.json library: you can find it at http://mvnrepository.com/artifact/org.json/json , here is the copyright notice of the library:
* Copyright (c) 2002 JSON.org
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* The Software shall be used for Good, not Evil.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import controller.Controller;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Main extends Application
{
public static void main(String[] args)
{
if(args.length < 1)
throw new RuntimeException("You must pass the path to the AI program as argument");
Controller.iaProgramPath = args[0];
launch();
}
@Override
public void start(Stage primaryStage)
{
try
{
FXMLLoader loader = new FXMLLoader();
loader.setLocation(getClass().getClassLoader().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();
}
}
}
package model;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GameState
{
long[] fish; //Array of three bitboards: one for every fish value
Player current_player;
Map<Player, Boolean> can_play; //If the red(blue) player can play or if it has no moves left
Map<Player, int[]> penguins; //For every player, the list of his penguins
Map<Player, Integer> score; //Score of every player
Player humanPlayer;
public GameState()
{
fish = new long[3];
can_play = new HashMap<>();
can_play.put(Player.Red, true);
can_play.put(Player.Blue, true);
penguins = new HashMap<>();
penguins.put(Player.Red, new int[4]);
penguins.put(Player.Blue, new int[4]);
score = new HashMap<>();
score.put(Player.Red, 0);
score.put(Player.Blue, 0);
}
public void update(String jsonString)
{
//System.out.println(jsonString);
JSONObject json = new JSONObject(jsonString);
can_play.put(Player.Red, json.getJSONObject("can_play").getBoolean("red"));
can_play.put(Player.Blue, json.getJSONObject("can_play").getBoolean("blue"));
current_player = Player.valueOf(json.getString("current_player"));
fish[0] = json.getJSONObject("bitboards").getLong("onefish");
fish[1] = json.getJSONObject("bitboards").getLong("twofish");
fish[2] = json.getJSONObject("bitboards").getLong("threefish");
score.put(Player.Red, json.getJSONObject("score").getInt("red"));
score.put(Player.Blue, json.getJSONObject("score").getInt("blue"));
JSONArray jsonPeng = json.getJSONObject("penguins").getJSONArray("red");
for (int i = 0; i < jsonPeng.length(); i++)
penguins.get(Player.Red)[i] = jsonPeng.getInt(i);
jsonPeng = json.getJSONObject("penguins").getJSONArray("blue");
for (int i = 0; i < jsonPeng.length(); i++)
penguins.get(Player.Blue)[i] = jsonPeng.getInt(i);
}
/**
* Set the amount of fish on one tile
*
* @param tileNb the tile on which we want to set the amount of fish
* @param fishNb The amount of fish (0-3)
*/
public void setFish(int tileNb, int fishNb)
{
for (int i = 0; i < 3; i++)
if (i == fishNb - 1)
fish[i] |= (long) 1 << tileNb;
else
fish[i] &= ~((long) 1 << tileNb);
}
/**
* Reset fish bitboards
*/
public void clearFish()
{
for (int i = 0; i < fish.length; i++)
fish[i] = 0;
}
/**
* @param tileNb The number of the tile we want to know the value of
* @return The number of fish on the tile, or 0 if empty
*/
public int getNbFish(int tileNb)
{
if (((fish[0] >>> tileNb) & 1) == 1)
return 1;
else if (((fish[1] >>> tileNb) & 1) == 1)
return 2;
else if (((fish[2] >>> tileNb) & 1) == 1)
return 3;
return 0;
}
/**
* @param player The player for which we are searching the penguin position
* @param i The number of the penguin for this player
* @return The position (0-59) of the penguin on the board
*/
public int getPenguinPos(Player player, int i)
{ return (penguins.get(player)[i] & 63); }
/**
* @param player The penguin's owner
* @param i The number of the penguin for this player
* @return the total moves that this penguin can do
*/
public int getPenguinTotalMoves(Player player, int i)
{ return ((penguins.get(player)[i] >>> 6) & 63); }
/**
* Get maximum number of tiles that a penguin can do in one specific direction
*/
public int getNbMoves(Player player, int penguinNumber, Direction direction)
{
int shift = 0;
switch (direction)
{
case A:
shift = 12;
break;
case B:
shift = 15;
break;
case C:
shift = 18;
break;
case D:
shift = 21;
break;
case E:
shift = 24;
break;
case F:
shift = 27;
break;
}
return ((penguins.get(player)[penguinNumber] >>> shift) & 7);
}
/**
* "I want to move penguin 1 in direction B for 5 steps"
* "This is you move number 10 out of all your possible moves"
* Note: moves start at 0. Steps start at 1 for this function
*/
public int getPlayerMoveNumber(Player player, int penguinNumber, Direction direction, int nbSteps)
{
int[] peng = penguins.get(player);
int total = 0;
for (int i = 0; i < penguinNumber; i++) //We add the moves of all preceding penguins
total += getPenguinTotalMoves(player, i);
for (Direction d : Direction.values())
{
if (d.equals(direction))
return total + nbSteps - 1;
else
total += getNbMoves(player, penguinNumber, d);
}
return -1;
}
/**
* @return The number of the penguin of this player that is on tile number tileNumber. Or -1 if there isn't any (note: there could be a penguin of the other player)
*/
public int getPenguinOnTile(Player player, int tileNumber)
{
for (int i = 0; i < penguins.get(player).length; i++)
if (getPenguinPos(player, i) == tileNumber)
return i;
return -1;
}
public void setPenguin(Player player, int penguinNumber, int penguin)
{
penguins.get(player)[penguinNumber] = penguin;
}
/**
* @return The list of all tiles that could be reached by moving penguin penguinNb of player player
*/
public List<Integer> getReachableTiles(Player player, int penguinNb)
{
List<Integer> result = new ArrayList<>();
int startingTile = getPenguinPos(player, penguinNb);
for (int i = 1; i <= getNbMoves(player, penguinNb, Direction.A); i++)
result.add(startingTile + i * 7);
for (int i = 1; i <= getNbMoves(player, penguinNb, Direction.B); i++)
result.add(startingTile - i);
for (int i = 1; i <= getNbMoves(player, penguinNb, Direction.C); i++)
result.add(startingTile - i * 8);
for (int i = 1; i <= getNbMoves(player, penguinNb, Direction.D); i++)
result.add(startingTile - i * 7);
for (int i = 1; i <= getNbMoves(player, penguinNb, Direction.E); i++)
result.add(startingTile + i);
for (int i = 1; i <= getNbMoves(player, penguinNb, Direction.F); i++)
result.add(startingTile + i * 8);
return result;
}
/**
* @return The state as needed for initialisation of the game (just bitboards, penguins, and score)
*/
public String toGameInputJSON()
{
JSONObject result = new JSONObject();
JSONObject jsonBitboards = new JSONObject();
jsonBitboards.put("onefish", fish[0]);
jsonBitboards.put("twofish", fish[1]);
jsonBitboards.put("threefish", fish[2]);
result.put("bitboards", jsonBitboards);
result.put("current_player", Player.Red);
JSONObject jsonPenguins = new JSONObject();
jsonPenguins.put("red", penguins.get(Player.Red));
jsonPenguins.put("blue", penguins.get(Player.Blue));
result.put("penguins", jsonPenguins);
JSONObject jsonScore = new JSONObject();
jsonScore.put("red", score.get(Player.Red));
jsonScore.put("blue", score.get(Player.Blue));
result.put("score", jsonScore);
return result.toString();
}
public boolean getCanPlay(Player player) { return can_play.get(player); }
public Player getCurrent_player()
{
return current_player;
}
public int getScore(Player player)
{
return score.get(player);
}
public Player getHumanPlayer()
{
return humanPlayer;
}
public void setHumanPlayer(Player humanPlayer)
{
this.humanPlayer = humanPlayer;
}
public void setScore(Player player, int value)
{
score.put(player, value);
}
public enum Direction
{
A, B, C, D, E, F;
}
}
package model;
public class Move
{
GameState.Direction direction;
int steps;
public Move(GameState.Direction direction, int steps)
{
this.direction = direction;
this.steps = steps;
}
@Override
public String toString()
{
return "(" + direction + ", " + steps + ")";
}
public GameState.Direction getDirection() { return direction; }
public void setDirection(GameState.Direction direction) { this.direction = direction; }
public int getSteps() { return steps; }
public void setSteps(int steps) { this.steps = steps; }
}
package model;
public enum Player
{
Red, Blue
}
package model;
public class Tile
{
private int number; //model.Tile number (bottom right = 0, top left = 59, ascending from right to left)
private int nbFish; //Number of fish on the tile
private PenguinPresence penguinPresence; //If there is a penguin on this tile
public Tile(int number, int nbFish, PenguinPresence penguinPresence)
{
this.number = number;
this.nbFish = nbFish;
this.penguinPresence = penguinPresence;
}
public Tile(int number, PenguinPresence penguinPresence)
{
this(number, 0, penguinPresence);
}
public int getNumber() { return number; }
public int getNbFish() { return nbFish; }
public void setNbFish(int nbFish) { this.nbFish = nbFish; }
public PenguinPresence getPenguinPresence() { return penguinPresence; }
public void setPenguinPresence(PenguinPresence penguinPresence) { this.penguinPresence = penguinPresence; }
public enum PenguinPresence
{
NO_PENGUIN, RED_PENGUIN, BLUE_PENGUIN;
}
}
File added
File added
package view;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import model.Move;
import model.Tile;
/**
* Class that makes the association between a tile from the model and its representation
*/
public class TileView
{
private Polygon fxTile;
private Tile modelTile;
private Label fishLabel;
private boolean selected, highlighted;
private Move highlightMove = null; // How do we get here from the selected tile (used when highlighting)
public TileView(Polygon fxTile, Label fishLabel, Tile modelTile)
{
this.fxTile = fxTile;
this.modelTile = modelTile;
this.fishLabel = fishLabel;
}
/**
* Tell the view to update according to the model
*/
public void update()
{
//TODO: Better tile representation
Color fillColor = null;
//FISH NUMBER
if(modelTile.getNbFish() == 0)
{
fishLabel.setText("");
fillColor = Color.WHITE;
}
else
{
fishLabel.setText(Integer.toString(modelTile.getNbFish()));
fillColor = Color.LIGHTBLUE;
}
//PENGUIN
if (modelTile.getPenguinPresence().equals(Tile.PenguinPresence.RED_PENGUIN))
fillColor = Color.RED;
else if (modelTile.getPenguinPresence().equals(Tile.PenguinPresence.BLUE_PENGUIN))
fillColor = Color.BLUE;
//SELECTION/HIGHLIGHT
if (selected)
fillColor = fillColor.deriveColor(0, 0.5, 1, 1);
if (highlighted)
fillColor = fillColor.deriveColor(0, 1, 0.5, 1);
fxTile.setFill(fillColor);
}
public Tile getModelTile() { return modelTile; }
public boolean isSelected() { return selected; }
public Polygon getFxTile() { return fxTile; }
public Label getFishLabel() { return fishLabel; }
public void setSelected(boolean selected) { this.selected = selected; }
public boolean isHighlighted() { return highlighted; }
public void setHighlighted(boolean highlighted) { this.highlighted = highlighted; }
public Move getHighlightMove() { return highlightMove; }
public void setHighlightMove(Move highlightMove) { this.highlightMove = highlightMove; }
}
This diff is collapsed.
Files under AI/src, with the exception of AI/src/game/penguin.hpp and AI/src/game/penguin.cpp have been written by Pascal Garcia
Copyright (C) 2016 Pascal Garcia
Every other file of the project, including AI/src/game/penguin.hhp and .cpp, have been written by Francesco Bariatti, Adrien Gasté, Mikael Le, Romain Lebouc.
Copyright (C) 2016 Francesco Bariatti < francesco.bariatti@insa-rennes.fr >, Adrien Gasté < adrien.gaste@insa-rennes.fr >, Mikael Le < mikael.le@insa-rennes.fr >, Romain Lebouc < romain.lebouc@insa-rennes.fr >
This project is licensed under the MIT License:
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Except as contained in this notice, the name(s) of (the) Author shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Software without
prior written authorization from (the)Author.
CC=g++
BIN=bin
INCLUDE=-I src/game -I src/util -I src/monte_carlo -I src/mcts -I src/minmax -I src/json
CFLAGS=-g -O3 -ffast-math -fopenmp -c -Wall -std=c++11 $(INCLUDE)
# Makefile for the Penguin game project.
# This project is composed of two modules: AI(Artificial Intelligence), that manage the game, and GUI that allows you to play it
# To compile the AI run 'make ai'; it is written in C++.
# To compile the GUI run 'make gui'; it is written in java and javafx.
# To compile everything run 'make all' or 'make'.
# To run the GUI (that will launch the AI) run 'make run'.
# Run 'make clean' to delete compiled files and programs. 'make cleanai' and 'make cleangui' also exist
# COMPILERS AND PROGRAMS
# Compiler for the AI: g++ >= 4.9
CC = g++
# JAVA ENVIRONMENT FOR THE GUI
# Java compiler >= 1.8 (usually javac)
JAVAC := javac
# Java jar utility (usually jar)
JAR := jar
# Java application launcher >= 1.8 (usually java)
JAVA := java
all: ai gui
clean: cleanai cleangui
# ============================================ AI (C++) =============================================
#Root directory for the AI module
AIRoot = AI
# Output directory
AIOutputDir = $(AIRoot)/bin
# Name of the executable
AIExecutable = $(AIOutputDir)/penguin
# Source files directory
AISRC = $(AIRoot)/src
# Directories with .h files
AIInclude=-I $(AISRC)/game -I $(AISRC)/util -I $(AISRC)/monte_carlo -I $(AISRC)/mcts -I $(AISRC)/json
# Cpp source files
AISourceFile = omp_util.cpp fast_log.cpp display_node.cpp penguin.cpp test_two_players_game.cpp monte_carlo.cpp \
test_fast_log.cpp statistics.cpp node.cpp allocator.cpp test_allocator.cpp openings.cpp mcts_two_players.cpp \
test_mcts_two_players.cpp bits.cpp test_bits.cpp main.cpp
# Directories in which make will search for files
vpath %.cpp $(AISRC)/game $(AISRC)/main $(AISRC)/util $(AISRC)/monte_carlo $(AISRC)/mcts $(AISRC)/gdl
# Flags passed to the compiler
CFLAGS=-g -O3 -ffast-math -fopenmp -Wall -std=c++11
# Flags passed to the linker
LDFLAGS=-fopenmp -std=c++11 #-lprofiler -Wl,-no_pie
SOURCES=omp_util.cpp fast_log.cpp display_node.cpp penguin.cpp connect4.cpp morpion.cpp test_two_players_game.cpp test_connect4.cpp monte_carlo.cpp test_monte_carlo.cpp test_fast_log.cpp\
statistics.cpp node.cpp allocator.cpp test_allocator.cpp openings.cpp mcts_two_players.cpp test_mcts_two_players.cpp test_minmax.cpp\
jsoncpp.cpp bits.cpp test_bits.cpp main.cpp
OBJECTS=$(addprefix $(BIN)/, $(SOURCES:.cpp=.o))
EXECUTABLE=$(BIN)/theturk
vpath %.cpp src/game:src/main:src/util:src/monte_carlo:src/mcts:src/gdl:src/minmax:src/json
all: $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
-include $(BIN)/$(OBJECTS:.o=.d)
$(BIN)/%.o: %.cpp
$(CC) -c $(CFLAGS) $< -o $(BIN)/$*.o
$(CC) -MM $(CFLAGS) $< > $(BIN)/$*.d
@mv -f $(BIN)/$*.d $(BIN)/$*.d.tmp
@sed -e 's|.*:|$(BIN)/$*.o:|' < $(BIN)/$*.d.tmp > $(BIN)/$*.d
@sed -e 's/.*://' -e 's/\\$$//' < $(BIN)/$*.d.tmp | fmt -1 | \
sed -e 's/^ *//' -e 's/$$/:/' >> $(BIN)/$*.d
@rm -f $(BIN)/$*.d.tmp
clean:
rm -f $(BIN)/*.o $(BIN)/*.d $(EXECUTABLE)
# Generating .o files' names
AIOobjects=$(addprefix $(AIOutputDir)/, $(AISourceFile:.cpp=.o))
# ==== RULES ====
# Main rule for creating the executable
ai: $(AIOutputDir) $(AIExecutable)
$(AIOutputDir):
mkdir -p $(AIOutputDir)
$(AIExecutable): $(AIOobjects)
$(CC) $(LDFLAGS) $(AIOobjects) -o $@
# Creates .o files with automatic generation of dependencies (.d) files
$(AIOutputDir)/%.o: %.cpp
$(CC) -c $(CFLAGS) $(AIInclude) -MD -MP $< -o $@
# Include the dependency files into the makefile
-include $(AIOobjects:.o=.d)
# Deletes .o, .d and the executable
cleanai:
rm -f $(AIOutputDir)/*.o $(AIOutputDir)/*.d $(AIExecutable)
# ============================================ GUI (Java) =============================================
GUIRoot := GUI
# Output root directory
GUIOutDir := $(GUIRoot)/out
# Jar output file name
GUIJarFile := $(GUIOutDir)/penguin.jar
# Compiled classes output directory
GUIOutClassDir := $(GUIOutDir)/classes
# Sources root directory: all your sources will be under this directory (packages and classes)
GUISrcDir := $(GUIRoot)/src
# Packages that will be compiled and included in the jar: every class in these packages will be compiled, if you have classes in a package and in a subpackage as well you must specify both. Example: main package package/subpackage
GUIPackages := main model view controller
# Additional classes tha will be compiled and included in the jar: useful for packages that contains classes that you don't want to include. Otherwise, just use the Packages variable. Specify the path inside the source root WITH EXTENSION. Example: main/Main.java package/subpackage/MyClass.java
GUIAdditionalClasses :=
# Entry point (main class) for jar file. In the form package/subpackage/Class
GUIJAREntryPoint := main.Main
# Additional resources that will be copied in jar file. In the form -C path/to/the/parent/folder resource/path (resource/path will also be the path of the resourc inside the jar)
GUIJARResources := -C $(GUISrcDir) resource/ -C $(GUISrcDir) view/view.fxml
# Classpath option for compilation or execution: colon (:) separated list, must prepend -classpath flag. Example: -classpath Library.jar:Library/Include
GUIClassPath := -classpath $(GUIRoot)/json-20160212.jar
# Compiler additional flags
GUIJAVACFlags :=
# JAR additional flags
GUIJARFlags :=
# Run arguments
GUIRunArgs := $(AIExecutable)
# ========== AUTOMATICALLY GENERATED VARIABLES ==========
# Searching for java files inside the $(Packages): for every package we search for java files and later we strip the leading source folder that is not necessary
GUIClassesSources := $(patsubst $(GUISrcDir)/%, %, $(foreach package, $(GUIPackages), $(wildcard $(GUISrcDir)/$(package)/*.java)))
GUIClassesSources := $(GUIClassesSources) $(GUIAdditionalClasses)
# We tell make to search for java sources inside the source directory
vpath %.java $(GUISrcDir)
# ========== RULES ==========
gui: $(GUIJarFile)
# You need all compiled version of classes to make jar file
$(GUIJarFile): $(addprefix $(GUIOutClassDir)/, $(GUIClassesSources:.java=.class)) $(GUIOutDir)
$(JAR) cvfe $(GUIJarFile) $(GUIJAREntryPoint) -C $(GUIOutClassDir) ./ $(GUIJARResources) $(GUIJARFlags)
$(GUIOutDir):
mkdir -p $(GUIOutDir)
# To compile a class you need the source code and the output folder
$(GUIOutClassDir)/%.class: $(GUISrcDir)/%.java $(GUIOutClassDir)
$(JAVAC) -sourcepath $(GUISrcDir) -d $(GUIOutClassDir) $(GUIClassPath) $(GUIJAVACFlags) $<
$(GUIOutClassDir):
mkdir -p $(GUIOutClassDir)
run:
@$(JAVA) $(GUIClassPath):$(GUIJarFile) main.Main $(GUIRunArgs)
guicleanjar:
-rm -f $(GUIJarFile)
guicleanclass:
-rm -f $(addprefix $(GUIOutClassDir)/, $(GUIClassesSources:.java=.class))
cleangui: guicleanjar guicleanclass
# 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)
#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;
}
}