#include #include #include #include #include #define CACHE_SIZE 1024 #define CACHE_MAX_OCCUPIED 4096 // BEGIN INIZIALIZZAZIONE DELLA MAPPA // BEGIN STRUTTURE // Struttura per rappresentare una rotta aerea typedef struct { int dest_x, dest_y; // Coordinate destinazione int cost; // Costo della rotta } AirRoute; // Struttura per rappresentare un esagono typedef struct { int land_cost; // Costo di uscita via terra (0 = bloccato) AirRoute air_routes[5]; // Rotte aeree uscenti int air_route_count; // Numero di rotte aeree } Hexagon; // Struttura per la mappa typedef struct { int rows, cols; // Dimensioni della matrice Hexagon** grid; // Matrice } HexMap; // Struttura per la coda in change_cost typedef struct { int x; int y; int dist; } QueueNode; // END void init_map(HexMap* map, int cols, int rows){ // FUNZIONAMENTO: // Gli passo una struttura HexMap e gli inserisco il numero di colonne e righe. // La matrice è formata da un "array" di puntatori alle teste delle colonne. Per farlo alloco dinamicamente lo spazio per contenere tutti // i puntatori agli inizi delle colonne. // Vado poi a scorrere ogni elemento della matrice ed a inizializarli con i valori land_cost=1 e air_route_count=0. map -> rows = rows; // Inserisce le dimensioni negli attributi dell'oggetto' map -> cols = cols; // Genero la matrice di esagoni map -> grid = (Hexagon**) malloc(cols * sizeof(Hexagon*)); // Alloco lo spazio del primo livello della matrice for (int i=0; i cols; i++){ map -> grid[i] = (Hexagon*) malloc(rows * sizeof(Hexagon)); // Alloco lo spazio del secondo livello della matrice for (int j=0; j rows; j++){ map -> grid[i][j].land_cost=1; // Inizializzo i valori degli esagoni map->grid[i][j].air_route_count = 0; } } fprintf(stdout,"OK\n"); fflush(stdout); } void destroy_map(HexMap* map){ // Va a deallocare la mappa for (int i = 0; i < map -> cols && map -> grid[i]; i++) { free(map->grid[i]); } free(map->grid); map->grid = NULL; map->rows = map->cols = 0; } void print_map(HexMap* map){ printf("\n"); for (int i=map->rows-1; i>=0; i--){ // Per ogni riga (se pari la offsetta per ottenere forma esagonale) if(i%2==1) printf(" "); for (int j=0; j cols; j++){ // Per ogni colonna printf("%d ",map -> grid[j][i].land_cost); // Stampa il costo dell'esagono' } printf("\n"); } } bool is_valid(HexMap* map, int x, int y){ if(x<0 || x>map->cols-1 || y<0 || y>map->rows-1){ return false; } else { return true; } } int hexagons_distance(int x_1, int y_1, int x_2, int y_2){ // Viene usato per calcolare la distanza tra due esagoni (tramite coordinate cubiche) // Converte (x, y) in coordinate cubiche (cx, cy, cz) int cx1 = x_1 - (y_1 - (y_1 & 1)) / 2; int cz1 = y_1; int cy1 = -cx1 - cz1; int cx2 = x_2 - (y_2 - (y_2 & 1)) / 2; int cz2 = y_2; int cy2 = -cx2 - cz2; return fmax(fabs(cx1 - cx2), fmax(fabs(cy1 - cy2), fabs(cz1 - cz2))); // Ritorno la distanza tra i due esagoni } bool change_cost(HexMap* map, int x, int y, int cost, int radius){ // Controllo che l'esagono sia valido if(!is_valid(map, x, y) || radius<=0 || cost < -10 || cost > 10){ fputs("KO\n", stdout); fflush(stdout); return false; } // Per ogni esagono in griglia, controllo se dista meno del raggio e, nel caso, gli calbio il costo int dist; int cost_old_air_route; float coeff; for(int i=0; icols; i++){ for (int j=0; jrows; j++){ dist=hexagons_distance(x,y,i,j); if (dist0){ // Calcolo del coefficiente (fidati) coeff=((float)(radius - dist) / (float)radius); } else { coeff=0; } map->grid[i][j].land_cost=map->grid[i][j].land_cost+(int)floor(cost * coeff); // Aggiorna il costo if (map -> grid[i][j].land_cost>100){ // Faccio il controllo sul costo (può essere compreso tra 0 e 100) map -> grid[i][j].land_cost=100; } if (map -> grid[i][j].land_cost<0){ map -> grid[i][j].land_cost=0; } // Aggiorno i costi delle rotte aeree for (int counter=0; counter grid[i][j].air_route_count;counter++){ cost_old_air_route=0; for (int n=0; ngrid[i][j].air_routes[n].cost; } map->grid[i][j].air_routes[counter].cost=(int)((cost_old_air_route+map->grid[i][j].land_cost)/(counter+1)); } } } } fputs("OK\n", stdout); fflush(stdout); return true; } bool toggle_air_route(HexMap* map, int x_1, int y_1, int x_2, int y_2){ // FUNZIONAMENTO: // Analizzo l'array delle rotte aeree dell'esagono di partenza (x_1, y_1) e, se la rotta è già presente la elimino (rimuovo dall'array); se invece non è presente // la aggiungo (inserisco nell'array) // Controllo che gli esagoni siano validi if(!is_valid(map, x_1,y_1) || !is_valid(map, x_2,y_2)){ fputs("KO\n", stdout); fflush(stdout); return false; } Hexagon* StartingHexagon = &(map -> grid[x_1][y_1]); // Memorizzo il puntatore all'esagono di partenza // Controllo di non avere più di 5 rotte aeree (uso il 4 per come è costruito il codice) if (StartingHexagon -> air_route_count>4){ fputs("KO\n", stdout); fflush(stdout); return false; } for (int i=0; i< StartingHexagon -> air_route_count; i++){ // Per ogni elemento all'interno della lista di rotte aeree dell'esagono di partenza if ((StartingHexagon -> air_routes[i].dest_x == x_2)&&(StartingHexagon -> air_routes[i].dest_y == y_2)){ // Se la rotta è già presente for (int j=i; j air_route_count; j++){ // Lo elimino shiftando gli elementi successivi a sx (così da non avere buchi nell'array) StartingHexagon -> air_routes[j]=StartingHexagon -> air_routes[j+1]; } StartingHexagon -> air_route_count = StartingHexagon -> air_route_count - 1; // Diminuisco il contatore di rotte aeree fputs("OK\n", stdout); fflush(stdout); return true; } } // Se sono arrivato qua, significa che la rotta non era presente nell'array e quindi la aggiungo // Calcolo il costo della rotta aerea int cost; if (StartingHexagon -> air_route_count==0){ cost = (int) floor(StartingHexagon -> land_cost / ((StartingHexagon -> air_route_count)+1)); } else { int sum=0; for (int i=0; i< StartingHexagon -> air_route_count; i++){ sum = sum + (int) StartingHexagon -> air_routes[i].cost; } cost = (int) floor((int) (sum+(StartingHexagon -> land_cost)) / ((StartingHexagon -> air_route_count)+1)); } StartingHexagon -> air_routes[StartingHexagon -> air_route_count] = (AirRoute) {x_2, y_2, cost}; // Aggiungo la tratta all'array StartingHexagon -> air_route_count = StartingHexagon -> air_route_count + 1; // Aumento il contatore del numero di tratte fputs("OK\n", stdout); fflush(stdout); return true; // DEBUGGING /* * printf("Tratta aggiunta\n"); * printf("La lista di tratte aeree partendo da %d %d:\n", x_1, y_1); * for (int i=0; i< StartingHexagon -> air_route_count; i++){ * printf("x:%d y:%d costo:%d\n", StartingHexagon -> air_routes[i].dest_x, StartingHexagon -> air_routes[i].dest_y, StartingHexagon -> air_routes[i].cost); } printf("\n\n"); */ } // END // BEGIN HASH-TABLE // BEGIN STRUTTURE typedef struct HashNode HashNode; struct HashNode{ int x,y; int distance_from_start; HashNode* next; }; typedef struct{ int size; HashNode** buckets; } HashTable; // END HashTable* create_hash_table(int size){ // Riceve la dimensione della tabella esagonale e crea una hash table di dimensioni adeguate // Trovo la dimensione minima che deve avere la tabella (fattore di carico massimo 1.5) int min_size = (int)ceil(size / 1.5); // Numeri primi buoni per dimensioni di hash table static const int primes[] = { 7, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611 }; // Trova il primo numero primo >= min_size che sarà la dimensione della tabella hash int hash_length=0; for (size_t i = 0; i < 23; i++) { if (primes[i] >= min_size) { hash_length = primes[i]; break; } } // Allco la struttura principale della hash table e inizializzo i suoi attributi HashTable* ht = (HashTable*)malloc(sizeof(HashTable)); ht->size = hash_length; // Alloco l'array di bucket (li inizializzo tutti a NULL) ht->buckets = (HashNode**) malloc(hash_length * sizeof(HashNode*)); for (int i = 0; i < hash_length; i++) { ht->buckets[i] = NULL; } return ht; } void clear_hash_table(HashTable* ht) { // Ripulisce la tabella dagli HashNode per poterla riutilizzare // Scorri tutti i bucket for (int i = 0; i < ht->size; i++) { HashNode* current = ht->buckets[i]; // Libera tutti i nodi nella catena while (current != NULL) { HashNode* next = current->next; free(current); current = next; } // Reimposta il bucket a NULL ht->buckets[i] = NULL; } } void destroy_hash_table(HashTable** ht) { // Distrugge completamente l'hash table // Ripulisce tutti i nodi nella tabella clear_hash_table(*ht); // Pulisce l'array dei bucket, la struttura principale e infine imposta a null il puntatore alla hash table free((*ht)->buckets); free(*ht); *ht = NULL; } void print_hash_table (HashTable* ht){ for (int i=0; i<(ht->size); i++){ HashNode* current = ht->buckets[i]; printf("Index[%d]: ", i); while (current != NULL) { printf("[x:%d y:%d dist:%d]-> ", current -> x, current -> y, current -> distance_from_start); current = current->next; } printf("\n"); } } int calculate_hash(int x, int y, int size){ // Hash function const int p1 = 7919; // Primo grande const int p2 = 1237; // Altro primo grande return ((x * p1) ^ (y * p2)) % size; } bool insert_or_update_element (HashTable* ht, int x, int y, int distance_from_start){ // Cerca un nodo dalla hash table, se non lo trova lo aggiunge, mentre se lo trova aggiorna (se necessario) la distance_from_start // Ritorna true se il nodo è stato aggiunto o aggiornato, false altrimenti int index = calculate_hash(x, y, ht -> size); // Calcolo l'hash HashNode* current = ht -> buckets[index]; //Scorro la lista fino a quando trovo il nodo o la finisco while (current != NULL && !(current -> x == x && current -> y == y)){ current = current -> next; } if (current == NULL){ // Se non c'è il nodo, lo inserisco HashNode* hn = malloc(sizeof(HashNode)); // Crea il nodo con i parametri che sono stati passati alla funzione (l'attributo next lo definisco nell'inserimento in testa) hn->x = x; hn->y = y; hn->distance_from_start = distance_from_start; hn -> next = ht -> buckets[index]; // Inserisco in testa alla lista ht -> buckets[index] = hn; return true; } if (current -> distance_from_start > distance_from_start){ // Se c'è già, se posso lo aggiorno current -> distance_from_start = distance_from_start; return true; } return false; } int search_element (HashTable* ht, int x, int y){ // Cerca un nodo dalla hash table e ne restituisce il costo int index = calculate_hash(x, y, ht -> size); // Calcolo l'hash HashNode* current = ht -> buckets[index]; //Scorro la lista fino a quando trovo il nodo o la finisco while (current != NULL && !(current -> x == x && current -> y == y)){ current = current -> next; } if(current==NULL){ // Se il nodo non è presente, restituisce -1 return -1; } return current->distance_from_start; } // END // BEGIN CACHE // BEGIN STRUTTURE typedef struct CACHE_HashNode CACHE_HashNode; struct CACHE_HashNode{ int xp,yp; // Coordinate di partenza int xd,yd; // Coordinate di arrivo int travel_cost; // Costo da xp,yp a xd,yd int LRU; // Valore per la gestione del caching CACHE_HashNode* next; // Puntatore al prossimo elemento }; typedef struct{ int size; // Dimensione della hash table (numero di bucket) int occupied; // Quantità di nodi inseriti in cache CACHE_HashNode** buckets; // Puntatore alla testa dei bucket } CACHE_HashTable; // END CACHE_HashTable* create_cache(int size){ // Riceve la dimensione con cui creare la tabella // Numeri primi buoni per dimensioni di hash table static const int primes[] = { 7, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611 }; // Trova il primo numero primo >= min_size che sarà la dimensione della tabella hash int hash_length=0; for (size_t i = 0; i < 23; i++) { if (primes[i] >= size) { hash_length = primes[i]; break; } } // Allco la struttura principale della hash table e inizializzo i suoi attributi CACHE_HashTable* ht = (CACHE_HashTable*)malloc(sizeof(CACHE_HashTable)); ht->size = hash_length; ht->occupied=0; // Alloco l'array di bucket (li inizializzo tutti a NULL) ht->buckets = (CACHE_HashNode**) malloc(hash_length * sizeof(CACHE_HashNode*)); for (int i = 0; i < hash_length; i++) { ht->buckets[i] = NULL; } return ht; } void clear_cache(CACHE_HashTable* ht) { // Ripulisce la tabella dai CACHE_HashNode per poterla riutilizzare // Scorri tutti i bucket for (int i = 0; i < ht->size; i++) { CACHE_HashNode* current = ht->buckets[i]; // Libera tutti i nodi nella catena while (current != NULL) { CACHE_HashNode* next = current->next; free(current); current = next; } // Reimposta il bucket a NULL ht->buckets[i] = NULL; } // Imposta il numero di nodi presenti a 0 ht->occupied=0; } void destroy_cache(CACHE_HashTable** ht) { // Distrugge completamente l'hash table // Ripulisce tutti i nodi nella tabella clear_cache(*ht); // Pulisce l'array dei bucket, la struttura principale e infine imposta a null il puntatore alla hash table free((*ht)->buckets); free(*ht); *ht = NULL; } void print_cache (CACHE_HashTable* ht){ for (int i=0; i<(ht->size); i++){ CACHE_HashNode* current = ht->buckets[i]; printf("Index[%d]: ", i); while (current != NULL) { printf("[xp:%d yp:%d xd:%d yd:%d cost:%d LRU:%d]-> ", current->xp, current->yp, current->xd, current->yd, current->travel_cost, current->LRU); current = current->next; } printf("\n"); } } int calculate_hash_cache(int xp, int yp, int xd, int yd, int size) { // Hash function con 4 input const int p1 = 7919; // primo per xp const int p2 = 1237; // primo per yp const int p3 = 1047; // primo per xd const int p4 = 1548; // primo per yd return ((xp * p1) ^ (yp * p2) ^ (xd * p3) ^ (yd * p4)) % size; } void delete_element_cache(CACHE_HashTable* ht){ // Cerco il nodo con LRU minore in tutta la cache e lo elimino CACHE_HashNode* prev=NULL; CACHE_HashNode* min=NULL; CACHE_HashNode* min_prev=NULL; int min_LRU=1000; int min_index=0; // Scorri tutti i bucket for (int i = 0; i < ht->size; i++) { CACHE_HashNode* current = ht->buckets[i]; prev = NULL; while (current != NULL) { // Per ogni nodo nella catena if (current->LRU < min_LRU){ // Se il nodo ha LRU minore di min_LRU, diventa il nuovo min min_LRU=current->LRU; min=current; min_prev=prev; min_index=i; } prev = current; current = current->next; } } if (min_prev==NULL){ ht->buckets[min_index] = min->next; } else { min_prev->next=min->next; } free(min); } bool insert_element_cache (CACHE_HashTable* ht, int xp, int yp, int xd, int yd, int travel_cost){ // Cerca un nodo dalla hash table, se non lo trova lo aggiunge, mentre se lo trova aggiorna (LRU permettendo) // Ritorna true se il nodo è stato aggiunto o aggiornato, false altrimenti int index = calculate_hash_cache(xp, yp, xd, yd, ht -> size); // Calcolo l'hash CACHE_HashNode* current = ht->buckets[index]; //Scorro la lista fino a quando trovo il nodo o la finisco while (current!=NULL && !(current->xp==xp && current->yp==yp && current->xd==xd && current->yd==yd)){ current = current->next; } if (current==NULL){ // Se non c'è il nodo, lo inserisco // Se la cache ha troppi elementi, ne elimino uno tramite la politica di LRU if (ht->occupied>=CACHE_MAX_OCCUPIED){ delete_element_cache(ht); } CACHE_HashNode* hn = malloc(sizeof(CACHE_HashNode)); // Crea il nodo con i parametri che sono stati passati alla funzione (l'attributo next lo definisco nell'inserimento in testa) hn->xp = xp; hn->yp = yp; hn->xd = xd; hn->yd = yd; hn->travel_cost=travel_cost; ht->occupied=ht->occupied+1; hn->next = ht->buckets[index]; // Inserisco in testa alla lista ht->buckets[index] = hn; return true; } return false; } int search_element_cache (CACHE_HashTable* ht, int xp, int yp, int xd, int yd){ // Cerca un nodo contenente il tragitto da xp,yp a xd,yd dalla hash table e ne restituisce il costo int index = calculate_hash_cache(xp, yp, xd, yd, ht -> size); // Calcolo l'hash CACHE_HashNode* current = ht -> buckets[index]; //Scorro la lista fino a quando trovo il nodo o la finisco while (current != NULL && !(current->xp==xp && current->yp==yp && current->xd==xd && current->yd==yd)){ current = current -> next; } if(current==NULL){ // Se il nodo non è presente, restituisce -2 return -2; } current->LRU=current->LRU+1; // Se il nodo è stato cercato, gli aumento l'LRU return current->travel_cost; } // END // BEGIN PATH-FINDING (Dijkstra) // BEGIN STRUTTURE typedef struct { int x,y; int distance_from_start; } QueueNode_PathFinding; // END void enqueue(QueueNode_PathFinding* queue, HashTable* ht, int x, int y, int node_cost, int head, int tail){ // Controllo il costo del nodo x,y in hash table e se il valore che trovo è minore di quello che gli passo, non aggiungo in coda (non vale la pena aggiungere il percorso con un costo maggiore) int hn_cost = search_element(ht, x, y); if (node_cost >= hn_cost){ return; } int temp_cost; for (int i=head; i=i; j--){ // Sposto tutti i nodi successivi avanti di uno queue[j+1]=queue[j]; } queue[i]=(QueueNode_PathFinding) {x, y, node_cost}; // Inserisco il nodo nella coda in posizione i return; } } queue[tail]=(QueueNode_PathFinding) {x, y}; // Se non è stato aggiunto in mezzo alla coda, lo devo aggiungere alla fine return; } int travel_cost(HexMap* map, HashTable* ht, int xp, int yp, int xd, int yd){ // FUNZIONAMENTO: // È un algoritmo Dijkstra in cui tengo sempre una coda ORDINATA che ha in testa le coordinate x,y del nodo con minore costo, mentre in coda quello con maggior costo. // Il costo è dato dalla distanza dal nodo sorgente // // Inizializzo la coda e ci inserisco il nodo sorgente; una volta fatto questo inserisco i 6 nodi vicini nella hashtable e poi nella coda. // // NOTA: la funzione di inserimento dell'hash table inserisce il costo solamente se è minore di quello già presente e riotrna true solo se il nodo viene aggiunto o aggiornato // (se il costo è maggiore di quello precedente, ritorna false). // // NOTA: la funzione di inserimento in coda inserisce i nodi già ordinandoli in base al loro costo. if (!is_valid(map,xp,yp) || !is_valid(map,xd,yd)){ return -1; } // TODO realloc dinamica della coda int queue_dimension = map->rows * map->cols * sizeof(QueueNode_PathFinding)*100; // Inizializzo la coda con dimensione 100 x del numero di nodi, head e tail QueueNode_PathFinding* queue = malloc(queue_dimension); int head=0; int tail=0; queue[0] = (QueueNode_PathFinding) {xp, yp, 0}; // Inserisco il nodo sorgente in coda tail++; insert_or_update_element(ht, xp, yp, 0); // Inserisco il nodo sorgente in hash table QueueNode_PathFinding current; // Inizializzo current, ossia il primo nodo della coda su cui lavoro (quello con costo minore dato che la coda è ordinata) int current_cost; // Il costo di current in coda int current_ht_cost; // Inizializzo current_ht_cost, ossia il costo del nodo nell'hash table corrispondente a current int new_x; // new_x e new_y vengono usate per immagazzinare i 6 nodi vicini int new_y; int air_route_count; // In queste due variabili vado a inserire gli attributi delle rotte aeree del nodo corrente AirRoute* air_routes; while (head current_ht_cost){ continue; } // ISPEZIONI I VICINI if(map->grid[current.x][current.y].land_cost!=0){ // Se current è transitabile // ISPEZIONI I 6 VICINI // Creo i due percorsi da ispezionare in base alla riga (pari o dispari) const int dir_even[6][2] = { {+1, 0}, { 0, -1}, {+1, -1}, {-1, 0}, {+1, +1}, { 0, +1} }; const int dir_odd[6][2] = { {+1, 0}, {-1, -1}, { 0, -1}, {-1, 0}, { 0, +1}, {-1, +1} }; // Scelgo la direzione in base alla parità della riga const int (*dirs)[2] = (current.y % 2 == 1) ? dir_even : dir_odd; // Guardo le coordinate dei 6 nodi vicini (per ogni iterazione, il nodo vicino avrà coordinate new_x, new_y) for (int i = 0; i < 6; i++) { new_x = current.x + dirs[i][0]; new_y = current.y + dirs[i][1]; if(is_valid(map, new_x, new_y)){ // Se il nodo è nella mappa if (insert_or_update_element(ht, new_x, new_y, current_ht_cost+map->grid[current.x][current.y].land_cost)){ // Se inserendolo ottengo true enqueue(queue, ht, new_x, new_y, current_cost, head, tail); // Lo aggiungo alla coda tail++; } } } // ISPEZIONI LE ROTTE AEREE USCENTI DA CURRENT air_route_count=map->grid[current.x][current.y].air_route_count; air_routes=map->grid[current.x][current.y].air_routes; for (int i=0; i