import com.fasterxml.jackson.databind.
ObjectMapper;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
public class BacBoTracker extends Application {
private static final int HISTORY_SIZE = 20;
private static List<String> history = new ArrayList<>();
private static ObservableList<String> historyObservable =
FXCollections.observableArrayList();
private static WebDriver driver;
private static WebElement roadmapElement;
private static AtomicBoolean running = new AtomicBoolean(true);
private static Label predictionLabel;
private static Label statsLabel;
private static PieChart pieChart;
private static final double[][] transitionMatrix = new double[3][3];
private static final String[] states = {"Jogador", "Banca", "Empate"};
private static final String CSV_FILE = "bacbo_history.csv";
private static final String CONFIG_FILE = "config.properties";
private static final String STATE_FILE = "bacbo_state.json";
private static final String GAMES_CONFIG = "games.json";
private static Properties config = new Properties();
private static long lastResultTime = 0;
private static long averageInterval = 5000;
private static String[] resultSelectors = {".roadmap .result:last-child",
".latest-result", ".game-outcome"};
private static Map<String, GameConfig> gameConfigs;
// Configuração do jogo
private static class GameConfig {
String url;
String resultSelector;
}
@Override
public void start(Stage primaryStage) {
// Aviso de conformidade
Alert complianceAlert = new Alert(Alert.AlertType.CONFIRMATION);
complianceAlert.setContentText("O uso de scraping pode violar os termos da
Elephant Bet. Você confirma que verificou as permissões?");
complianceAlert.showAndWait().ifPresent(response -> {
if (response != ButtonType.OK) System.exit(0);
});
// Carregar configurações
loadConfig();
loadGameConfigs();
loadState();
// Configurar interface gráfica
primaryStage.setTitle("Rastreador de Bac Bo - Elephant Bet Angola");
// Tabela para histórico
TableView<String> historyTable = new TableView<>();
TableColumn<String, String> resultColumn = new TableColumn<>("Resultado");
resultColumn.setCellValueFactory(cellData -> new
javafx.beans.property.SimpleStringProperty(cellData.getValue()));
historyTable.getColumns().add(resultColumn);
historyTable.setItems(historyObservable);
historyTable.setPrefHeight(200);
// Gráfico de pizza
pieChart = new PieChart();
updatePieChart();
pieChart.setPrefHeight(200);
// Rótulos
predictionLabel = new Label("Previsão para a próxima rodada:
Indeterminado");
predictionLabel.setStyle("-fx-font-size: 16; -fx-font-weight: bold;");
statsLabel = new Label("Estatísticas: Aguardando dados...");
statsLabel.setStyle("-fx-font-size: 14;");
// Botão de configurações
Button configButton = new Button("Configurações");
configButton.setOnAction(e -> openConfigWindow());
// Botão para encerrar
Button stopButton = new Button("Encerrar Programa");
stopButton.setOnAction(e -> {
saveState();
running.set(false);
primaryStage.close();
});
// Toggle para tema
ToggleButton themeToggle = new ToggleButton("Tema Escuro");
themeToggle.setOnAction(e -> {
String style = themeToggle.isSelected() ? "-fx-background-color: #333;
-fx-text-fill: white;" : "-fx-background-color: white; -fx-text-fill: black;";
primaryStage.getScene().getRoot().setStyle(style);
});
// Layout
VBox layout = new VBox(10, historyTable, pieChart, predictionLabel,
statsLabel, configButton, themeToggle, stopButton);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout, 500, 600);
primaryStage.setScene(scene);
primaryStage.show();
// Iniciar rastreamento
new Thread(this::startTracking).start();
}
private void startTracking() {
reconnect();
String lastResult = null;
while (running.get()) {
try {
String result = scrapeLatestResult();
if (result != null && !result.isEmpty() && !
result.equals(lastResult)) {
addToHistory(result);
updateTransitionMatrix(lastResult, result);
saveToCsv(result);
updateInterval();
Platform.runLater(() -> {
historyObservable.clear();
historyObservable.addAll(history);
predictionLabel.setText("Previsão para a próxima rodada: "
+ predictNext());
updatePieChart();
updateStatistics();
checkForNotifications(lastResult, result);
});
lastResult = result;
}
Thread.sleep(averageInterval);
} catch (Exception e) {
System.out.println("Erro ao capturar resultado: " +
e.getMessage());
reconnect();
}
}
if (driver != null) driver.quit();
}
private void reconnect() {
if (driver != null) driver.quit();
try {
System.setProperty("webdriver.chrome.driver",
config.getProperty("chromedriver.path", "C:/WebDrivers/chromedriver.exe"));
driver = new ChromeDriver();
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
driver.get("https://m.elephantbet.co.ao/pt");
try {
WebElement loginButton =
wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector(config.getPropert
y("login.button.selector", "a.login-btn"))));
loginButton.click();
WebElement usernameField =
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(config.getProperty("
username.field.id", "username-input"))));
WebElement passwordField =
driver.findElement(By.id(config.getProperty("password.field.id", "password-
input")));
WebElement submitButton =
driver.findElement(By.cssSelector(config.getProperty("submit.button.selector",
"button.login-submit")));
usernameField.sendKeys(decrypt(config.getProperty("username")));
passwordField.sendKeys(decrypt(config.getProperty("password")));
submitButton.click();
wait.until(ExpectedConditions.urlContains("/pt"));
} catch (Exception e) {
System.out.println("Login não necessário ou falhou: " +
e.getMessage());
}
GameConfig bacBoConfig = gameConfigs.getOrDefault("BacBo", new
GameConfig());
bacBoConfig.url =
"https://m.elephantbet.co.ao/pt/casino/game-view/420012128/bac-bo";
driver.get(bacBoConfig.url);
wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector(".game-
container")));
roadmapElement = null;
} catch (Exception e) {
System.out.println("Erro ao reconectar: " + e.getMessage());
}
}
private String scrapeLatestResult() {
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
try {
if (roadmapElement == null || isStale(roadmapElement)) {
roadmapElement =
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".roadmap")
));
}
for (String selector : resultSelectors) {
try {
WebElement resultElement =
roadmapElement.findElement(By.cssSelector(selector));
String resultText = resultElement.getText().toLowerCase();
if (resultText.contains("player") ||
resultText.contains("jogador") || resultText.contains("p")) return "Jogador";
if (resultText.contains("banker") ||
resultText.contains("banca") || resultText.contains("b")) return "Banca";
if (resultText.contains("tie") || resultText.contains("empate")
|| resultText.contains("t")) return "Empate";
} catch (Exception e) {
// Tentar próximo seletor
}
}
} catch (Exception e) {
roadmapElement = null;
}
return null;
}
private boolean isStale(WebElement element) {
try {
element.isDisplayed();
return false;
} catch (StaleElementReferenceException e) {
return true;
}
}
private void addToHistory(String result) {
if (history.size() >= HISTORY_SIZE) history.remove(0);
history.add(result);
}
private void initCsv() {
try (FileWriter writer = new FileWriter(CSV_FILE)) {
writer.write("Timestamp,Resultado\n");
} catch (IOException e) {
System.out.println("Erro ao inicializar CSV: " + e.getMessage());
}
}
private void saveToCsv(String result) {
try (FileWriter writer = new FileWriter(CSV_FILE, true)) {
writer.write(LocalDateTime.now() + "," + result + "\n");
} catch (IOException e) {
System.out.println("Erro ao salvar no CSV: " + e.getMessage());
}
}
private void updateTransitionMatrix(String lastResult, String currentResult) {
if (lastResult == null) return;
int fromIndex = getStateIndex(lastResult);
int toIndex = getStateIndex(currentResult);
transitionMatrix[fromIndex][toIndex]++;
double sum = Arrays.stream(transitionMatrix[fromIndex]).sum();
if (sum > 0) {
for (int i = 0; i < 3; i++) {
transitionMatrix[fromIndex][i] /= sum;
}
}
}
private int getStateIndex(String state) {
for (int i = 0; i < states.length; i++) {
if (states[i].equals(state)) return i;
}
return -1;
}
private String predictNext() {
if (history.isEmpty()) return "Indeterminado";
String lastResult = history.get(history.size() - 1);
int currentState = getStateIndex(lastResult);
if (currentState == -1) return "Indeterminado";
// Simulação de ML: peso por frequência recente
Map<String, Long> recentCounts = history.subList(Math.max(0, history.size()
- 5), history.size())
.stream().collect(Collectors.groupingBy(r -> r,
Collectors.counting()));
double mlWeight = 0.3;
double[] combinedProbs = new double[3];
for (int i = 0; i < 3; i++) {
combinedProbs[i] = (1 - mlWeight) * transitionMatrix[currentState][i] +
mlWeight * (recentCounts.getOrDefault(states[i], 0L) / 5.0);
}
int nextState = 0;
double maxProb = combinedProbs[0];
for (int i = 1; i < 3; i++) {
if (combinedProbs[i] > maxProb) {
maxProb = combinedProbs[i];
nextState = i;
}
}
return states[nextState];
}
private void updatePieChart() {
long jogador = history.stream().filter(r -> r.equals("Jogador")).count();
long banca = history.stream().filter(r -> r.equals("Banca")).count();
long empate = history.stream().filter(r -> r.equals("Empate")).count();
ObservableList<PieChart.Data> pieData = FXCollections.observableArrayList(
new PieChart.Data("Jogador", jogador),
new PieChart.Data("Banca", banca),
new PieChart.Data("Empate", empate)
);
Platform.runLater(() -> pieChart.setData(pieData));
}
private void updateStatistics() {
long total = history.size();
if (total == 0) return;
long jogador = history.stream().filter(r -> r.equals("Jogador")).count();
long banca = history.stream().filter(r -> r.equals("Banca")).count();
long empate = history.stream().filter(r -> r.equals("Empate")).count();
String longestStreak = findLongestStreak();
Platform.runLater(() -> statsLabel.setText(String.format(
"Estatísticas: Jogador: %.2f%% | Banca: %.2f%% | Empate: %.2f%% |
Sequência mais longa: %s",
jogador * 100.0 / total, banca * 100.0 / total, empate * 100.0 / total,
longestStreak
)));
}
private String findLongestStreak() {
if (history.isEmpty()) return "Nenhuma";
String current = history.get(0);
int currentCount = 1;
int maxCount = 1;
String maxStreak = current;
for (int i = 1; i < history.size(); i++) {
if (history.get(i).equals(current)) {
currentCount++;
if (currentCount > maxCount) {
maxCount = currentCount;
maxStreak = current;
}
} else {
current = history.get(i);
currentCount = 1;
}
}
return maxStreak + " (" + maxCount + ")";
}
private void checkForNotifications(String lastResult, String currentResult) {
if (history.size() >= 3 && history.subList(history.size() - 3,
history.size()).stream().allMatch(r -> r.equals(currentResult))) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setContentText("Sequência de 3 vitórias de " + currentResult
+ " detectada!");
alert.showAndWait();
});
}
}
private void updateInterval() {
long currentTime = System.currentTimeMillis();
if (lastResultTime > 0) {
long interval = currentTime - lastResultTime;
averageInterval = (long) (averageInterval * 0.9 + interval * 0.1);
}
lastResultTime = currentTime;
}
private void loadConfig() {
try (FileInputStream fis = new FileInputStream(CONFIG_FILE)) {
config.load(fis);
} catch (IOException e) {
// Configuração padrão
config.setProperty("chromedriver.path",
"C:/WebDrivers/chromedriver.exe");
config.setProperty("login.button.selector", "a.login-btn");
config.setProperty("username.field.id", "username-input");
config.setProperty("password.field.id", "password-input");
config.setProperty("submit.button.selector", "button.login-submit");
}
}
private void saveConfig(String chromedriverPath, String username, String
password, String loginButton, String usernameField, String passwordField, String
submitButton) {
config.setProperty("chromedriver.path", chromedriverPath);
config.setProperty("username", encrypt(username));
config.setProperty("password", encrypt(password));
config.setProperty("login.button.selector", loginButton);
config.setProperty("username.field.id", usernameField);
config.setProperty("password.field.id", passwordField);
config.setProperty("submit.button.selector", submitButton);
try (FileOutputStream fos = new FileOutputStream(CONFIG_FILE)) {
config.store(fos, "Configurações do BacBoTracker");
} catch (IOException e) {
System.out.println("Erro ao salvar configuração: " + e.getMessage());
}
}
private String encrypt(String data) {
try {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec key = new SecretKeySpec("MySecretKey12345".getBytes(),
"AES"); // Ajuste a chave
cipher.init(Cipher.ENCRYPT_MODE, key);
return
Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
} catch (Exception e) {
System.out.println("Erro ao criptografar: " + e.getMessage());
return data;
}
}
private String decrypt(String data) {
try {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec key = new SecretKeySpec("MySecretKey12345".getBytes(),
"AES"); // Ajuste a chave
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
} catch (Exception e) {
System.out.println("Erro ao descriptografar: " + e.getMessage());
return data;
}
}
private void loadGameConfigs() {
try {
ObjectMapper mapper = new ObjectMapper();
gameConfigs = mapper.readValue(new File(GAMES_CONFIG), new
TypeReference<Map<String, GameConfig>>() {});
} catch (IOException e) {
gameConfigs = new HashMap<>();
GameConfig bacBo = new GameConfig();
bacBo.url =
"https://m.elephantbet.co.ao/pt/casino/game-view/420012128/bac-bo";
bacBo.resultSelector = ".roadmap .result:last-child";
gameConfigs.put("BacBo", bacBo);
try {
new ObjectMapper().writeValue(new File(GAMES_CONFIG), gameConfigs);
} catch (IOException ex) {
System.out.println("Erro ao criar games.json: " + ex.getMessage());
}
}
}
private void openConfigWindow() {
Stage configStage = new Stage();
configStage.setTitle("Configurações");
TextField chromedriverPathField = new
TextField(config.getProperty("chromedriver.path"));
TextField usernameField = new
TextField(decrypt(config.getProperty("username", "")));
PasswordField passwordField = new PasswordField();
passwordField.setText(decrypt(config.getProperty("password", "")));
TextField loginButtonField = new
TextField(config.getProperty("login.button.selector"));
TextField usernameFieldId = new
TextField(config.getProperty("username.field.id"));
TextField passwordFieldId = new
TextField(config.getProperty("password.field.id"));
TextField submitButtonField = new
TextField(config.getProperty("submit.button.selector"));
Button saveButton = new Button("Salvar");
saveButton.setOnAction(e -> {
saveConfig(
chromedriverPathField.getText(), usernameField.getText(),
passwordField.getText(),
loginButtonField.getText(), usernameFieldId.getText(),
passwordFieldId.getText(),
submitButtonField.getText()
);
configStage.close();
});
VBox configLayout = new VBox(10,
new Label("Caminho do ChromeDriver:"), chromedriverPathField,
new Label("Usuário:"), usernameField,
new Label("Senha:"), passwordField,
new Label("Seletor do Botão de Login:"), loginButtonField,
new Label("ID do Campo de Usuário:"), usernameFieldId,
new Label("ID do Campo de Senha:"), passwordFieldId,
new Label("Seletor do Botão de Submit:"), submitButtonField,
saveButton
);
configLayout.setPadding(new Insets(10));
Scene configScene = new Scene(configLayout, 400, 500);
configStage.setScene(configScene);
configStage.show();
}
private void saveState() {
try {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> state = Map.of("history", history,
"transitionMatrix", transitionMatrix);
mapper.writeValue(new File(STATE_FILE), state);
} catch (IOException e) {
System.out.println("Erro ao salvar estado: " + e.getMessage());
}
}
private void loadState() {
try {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> state = mapper.readValue(new File(STATE_FILE),
Map.class);
history.addAll((List<String>) state.get("history"));
List<List<Double>> matrix = (List<List<Double>>)
state.get("transitionMatrix");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
transitionMatrix[i][j] = matrix.get(i).get(j);
}
}
Platform.runLater(() -> {
historyObservable.addAll(history);
updatePieChart();
updateStatistics();
});
} catch (IOException e) {
System.out.println("Estado não encontrado, iniciando novo.");
initCsv();
}
}
public static void main(String[] args) {
launch(args);
}
}