From: VGoncalo Date: Sun, 15 Mar 2026 22:18:31 +0000 (+0000) Subject: final project of Applied Artificial Inteligence Course X-Git-Url: https://vgcfreebox.myrthtech.pt/gitweb/ue-aai-hiddenarkgame.git/commitdiff_plain/refs/heads/main?ds=inline final project of Applied Artificial Inteligence Course --- d12f0acd1c6752c37b6da44e64c635aaf1ea3730 diff --git a/HiddenArkGame.py b/HiddenArkGame.py new file mode 100644 index 0000000..bf7e711 --- /dev/null +++ b/HiddenArkGame.py @@ -0,0 +1,276 @@ +import tkinter as tk +import sys +from tkinter import ttk, messagebox +import random +import numpy as np +from scipy.spatial.distance import cityblock + +# Configurações do tabuleiro (tamanho (X,Y), orçamento inicial, Custo dos sensores) +if len(sys.argv) > 1: + ROWS = int(sys.argv[1]) + COLS = int(sys.argv[2]) +else: + ROWS,COLS = 5,5 + + +class TreasureGame: + def __init__(self, root): + self.root = root + self.root.title("Caçadores da Arca Perdida") + + # Inicialização de variáveis de estado + self.perfect_mode = False + self.INITIAL_BUDGET = 100 + self.SENSOR_COSTS = {"GPR": 5, "MAG": 3, "VIS": 1, "DIG": 0} + + self.budget = self.INITIAL_BUDGET + self.ark_loc = (random.randint(0, ROWS - 1), random.randint(0, COLS - 1)) + #self.ark_loc = (1,1) + self.probs = np.full((ROWS, COLS), 1.0 / (ROWS * COLS)) + self.used_sensors = set() + self.cell_history = {} + self.show_treasure_debug = False + # Tabelas de Probabilidade Normais + self.SENSOR_TABLES = { + "GPR": { + "STRONG": [0.75, 0.10, 0.05, 0.02], + "MODERATE": [0.15, 0.70, 0.15, 0.08], + "WEAK": [0.07, 0.15, 0.65, 0.15], + "NONE": [0.03, 0.05, 0.15, 0.75] + }, + "MAG": { + "HIGH": [0.70, 0.15, 0.10], + "MEDIUM": [0.20, 0.65, 0.25], + "LOW": [0.10, 0.20, 0.65] + }, + "VIS": { + "SUSPICIOUS": [0.60, 0.20], + "NORMAL": [0.40, 0.80] + } + } + # Tabelas de Probabilidade Perfeitas + self.SENSOR_TABLES_PERFECT = { + "GPR": { + "STRONG": [1, 0, 0, 0], + "MODERATE": [0, 1, 0, 0], + "WEAK": [0, 0, 1, 0], + "NONE": [0, 0, 0, 1], + }, + "MAG": { + "HIGH": [1, 0, 0], + "MEDIUM": [0, 1, 0], + "LOW": [0, 0, 1] + }, + "VIS": { + "SUSPICIOUS": [1, 0], + "NORMAL": [0, 1] + } + } + + + # Estilos dos botões + self.style = ttk.Style() + self.style.configure("Perfect.TButton", foreground="green", font=("Arial", 9, "bold")) + self.style.configure("Normal.TButton", foreground="black") + + self.setup_ui() + + # Define os sensores utilizados + @property + def current_sensor_table(self): + return self.SENSOR_TABLES_PERFECT if self.perfect_mode else self.SENSOR_TABLES + + def setup_ui(self): + # Tabuleiro INICIO + self.main_frame = ttk.Frame(self.root, padding="10") + self.main_frame.grid(row=0, column=0, sticky="nsew") + + self.buttons = {} + for r in range(ROWS): + for c in range(COLS): + btn = tk.Button(self.main_frame, text="", width=15, height=5, + font=("Arial", 8), relief="groove", + command=lambda row=r, col=c: self.cell_click(row, col)) + btn.grid(row=r, column=c, padx=2, pady=2, sticky="nsew") + self.buttons[(r, c)] = btn + # Tabuleiro FIM + # Menu Lateral INICIO + control_panel = ttk.Frame(self.root, padding="10") + control_panel.grid(row=0, column=1, sticky="ns") + + # Texto Orçamento + self.budget_var = tk.StringVar(value=f"Orçamento: {self.budget}") + ttk.Label(control_panel, textvariable=self.budget_var, font=("Arial", 12, "bold")).pack(pady=5) + + # RadioButton Sensores + self.sensor_var = tk.StringVar(value="GPR") + sf = ttk.LabelFrame(control_panel, text=" Selecione o Sensor ", padding="5") + sf.pack(fill='x', pady=5) + for s, cost in self.SENSOR_COSTS.items(): + ttk.Radiobutton(sf, text=f"{s} ({cost})", variable=self.sensor_var, value=s).pack(anchor='w') + + # Botão Revelar tesouro + self.reveal_btn = ttk.Button(control_panel, text="Revelar Tesouro: OFF", command=self.toggle_treasure, + style="Normal.TButton") + self.reveal_btn.pack(fill='x', pady=10) + + # Botão Sensores Perfeitos + self.perfect_btn = ttk.Button(control_panel, text="Sensores Perfeitos: OFF", command=self.toggle_perfect_mode, + style="Normal.TButton") + self.perfect_btn.pack(fill='x', pady=5) + + # Painel Histórico + ttk.Label(control_panel, text="Histórico:").pack(anchor='w', pady=(10, 0)) + self.history_log = tk.Text(control_panel, width=30, height=12, state='disabled', font=("Consolas", 9)) + self.history_log.pack(pady=5) + + # Botão Reiniciar + ttk.Button(control_panel, text="Reiniciar Jogo", command=self.reset_game).pack(fill='x', pady=2) + + # Botão Sair + ttk.Button(control_panel, text="Sair", command=self.root.destroy).pack(fill='x', pady=5) + + # Menu lateral FIM + self.update_grid_display() + + # Activa/Desactiva sensores perfeitos + def toggle_perfect_mode(self): + self.perfect_mode = not self.perfect_mode + if self.perfect_mode: + self.perfect_btn.config(text="Sensores Perfeitos: ON", style="Perfect.TButton") + else: + self.perfect_btn.config(text="Sensores Perfeitos: OFF", style="Normal.TButton") + + self.history_log.config(state='normal') + self.history_log.insert(tk.END, f"> Modo Sensores Perfeitos: {'ON' if self.perfect_mode else 'OFF'}\n") + self.history_log.see(tk.END) + self.history_log.config(state='disabled') + + # Activa/Desactiva localização do tesouro + def toggle_treasure(self): + + self.show_treasure_debug = not self.show_treasure_debug + if self.show_treasure_debug: + self.reveal_btn.config(text="Revelar Tesouro: ON", style="Perfect.TButton") + else: + self.reveal_btn.config(text="Revelar Tesouro: OFF", style="Normal.TButton") + + self.history_log.config(state='normal') + self.history_log.insert(tk.END, f"> Modo Tesouro Visivel: {'ON' if self.show_treasure_debug else 'OFF'}\n") + self.history_log.see(tk.END) + self.history_log.config(state='disabled') + + self.update_grid_display() + + # Actualiza o tabuleiro + def update_grid_display(self): + for (r, c), btn in self.buttons.items(): + treasure_header = "!!! TESOURO !!!\n" if (self.show_treasure_debug and (r, c) == self.ark_loc) else "" + p_text = f"{self.probs[r, c]:.1%}" + history_text = self.cell_history.get((r, c), "") + if history_text: + history_text = "\n" + history_text + + btn.config(text=f"{treasure_header}{p_text}{history_text}") + + # Cor de fundo baseada na probabilidade + intensity = int(255 - (self.probs[r, c] * 350)) + intensity = max(min(intensity, 255), 180) + color = f"#{255:02x}{intensity:02x}{intensity:02x}" + + if self.show_treasure_debug and (r, c) == self.ark_loc: + color = "#FFD700" # Dourado + + btn.config(bg=color) + + # Click das localizações do mapa + def cell_click(self, r, c): + sensor = self.sensor_var.get() + cost = self.SENSOR_COSTS[sensor] + + # Verifica o se sensor já foi usado na localização + if sensor != "DIG" and (r, c, sensor) in self.used_sensors: + messagebox.showwarning("Aviso", "Este sensor já foi usado aqui!") + return + + # Verifica se há orçamento + if self.budget < cost: + messagebox.showwarning("Aviso", "Orçamento insuficiente!") + return + + # Se o jogador utilizou "DIG" verifica se ganhou ou perdeu + if sensor == "DIG": + if (r, c) == self.ark_loc: + messagebox.showinfo("Vitória", f"Encontrou o tesouro!\nPontuação final: {self.budget}") + else: + messagebox.showerror("Derrota", f"O tesouro não está nesta localização.\nPerdeu o jogo!") + self.root.destroy() + return + + # Executa o sensor, com base na tabela utilizada + dist = cityblock((r, c), self.ark_loc) + active_table = self.current_sensor_table[sensor] + + # Determinar leitura baseada nas probabilidades + outcomes = list(active_table.keys()) + max_idx = len(next(iter(active_table.values()))) - 1 + idx = min(dist, max_idx) + weights = [active_table[out][idx] for out in outcomes] + print(weights) + reading = random.choices(outcomes, weights=weights, k=1)[0] + + + # Actualiza orçamento + self.used_sensors.add((r, c, sensor)) + self.budget -= cost + self.budget_var.set(f"Orçamento: {self.budget}") + + # Atualiza o resultado na localização + entry = f"{sensor}:{reading}" + + # Actualiza o histórico + self.cell_history[(r, c)] = f"{self.cell_history.get((r, c), '')}\n{entry}".strip() + self.history_log.config(state='normal') + self.history_log.insert(tk.END, f"{sensor} em ({r},{c}): {reading}\n") + self.history_log.see(tk.END) + self.history_log.config(state='disabled') + + self.update_probabilities(sensor, reading, (r, c)) + self.update_grid_display() + + # actualiza as probabilidades no tabuleiro + def update_probabilities(self, sensor, reading, click_loc): + new_probs = np.zeros((ROWS, COLS)) + table_row = self.current_sensor_table[sensor][reading] + max_idx = len(table_row) - 1 + + for r in range(ROWS): + for c in range(COLS): + d = cityblock((r, c), click_loc) + prob_evidence = table_row[min(d, max_idx)] + new_probs[r, c] = self.probs[r, c] * prob_evidence + + total = np.sum(new_probs) + if total > 0: + self.probs = new_probs / total + + # Reinicia o jogo + def reset_game(self): + self.budget = self.INITIAL_BUDGET + self.budget_var.set(f"Orçamento: {self.budget}") + self.ark_loc = (random.randint(0, ROWS - 1), random.randint(0, COLS - 1)) + self.probs = np.full((ROWS, COLS), 1.0 / (ROWS * COLS)) + self.used_sensors = set() + self.cell_history = {} + + self.history_log.config(state='normal') + self.history_log.delete('1.0', tk.END) + self.history_log.config(state='disabled') + + self.update_grid_display() + + +if __name__ == "__main__": + root = tk.Tk() + game = TreasureGame(root) + root.mainloop() diff --git "a/Relat\303\263rioAAI-57098&70323.pdf" "b/Relat\303\263rioAAI-57098&70323.pdf" new file mode 100644 index 0000000..a48a137 Binary files /dev/null and "b/Relat\303\263rioAAI-57098&70323.pdf" differ diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..f1d4f59 --- /dev/null +++ b/readme.txt @@ -0,0 +1,8 @@ +Hidden Ark Game + +This application is a videogame that expects inputs from the user's mouse. The user can selects different inference instruments to improve it's chance to find an hidden treasure ark. + +In order to properly execute the application the following libraries must be available in the system +or virtual environment: tkinter, numpy, scikit. The default game uses a 5x5 grid of areas to infer, but this can be changed by passing the arguments on start like: .\HiddenArkGame.py 9 9 + +The class TreasureGame is responsible to define the game variables and functions available in the UI, the method cell_click is executed on each user click on a tkinter.button, triggering other actions like update_probabilities which calculates the probabilities for each area and refreshes the ui, with the method update_grid_display, which is also responsible for colloring the buttons and provide an heat-map to the user. \ No newline at end of file