#include #include #include #include #include // 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; } } fputs("OK\n", stdout); 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 un euristica (approssimata) per A* // Euristica=stima della distanza (in numero di esagoni ignorando i land_cost) tra due esagoni // 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))); } void 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){ fputs("KO\n", stdout); fflush(stdout); return; } // Per ogni esagono in griglia, controllo se dista meno del raggio e, nel caso, gli calbio il costo int distanza; int cost_old_air_route; for(int i=0; icols; i++){ for (int j=0; jrows; j++){ distanza=hexagons_distance(x,y,i,j); if (distanzagrid[i][j].land_cost=map->grid[i][j].land_cost+(float) floor(cost * fmax(0,(float) (radius-distanza)/radius)); // 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); } /* // VECCHIA FUNZIONE void change_cost(HexMap* map, int x, int y, int cost, int radius){ // FUNZIONAMENTO: // Inizialmente faccio il controllo su x, y e radius. // Genero una tabella di valori booleani in cui mi salvo quali esagoni sono già stati modificati. // Creo una coda in cui metto l'esagono sorgente e ne modifico il costo. Poi aggiungo i 6 esagoni vicini (si differenzia se la riga è pari o dispari). // Vado a scorrere la tabella per ogni nodo che c'è al suo interno (i primi che incontro sono i 6 vicini del sorgente) e per ogni nodo aggiorno il valore // e aggiungo i vicini alla coda (se sono all'interno del raggio che voglio e se non sono già stati modificati prima (lo controllo con la tabella booleana)). if(!is_valid(map, x, y) || radius<=0){ fputs("KO\n", stdout); fflush(stdout); return; } bool** visited = (bool**) malloc(map -> cols * sizeof(bool*)); // Genero la tabella booleana for (int i=0; i< map -> cols; i++){ visited[i]= (bool*) malloc(map -> rows * sizeof(bool)); for (int j=0; j< map -> rows; j++){ visited[i][j]=false; } } QueueNode* queue = (QueueNode*) malloc(map -> cols * map -> rows * sizeof(QueueNode)); // Creo la coda di dimensione giusta int head = 0; // head è la testa della coda (dove estraggo), tail è il culo della coda (dove inserisco) int tail = 0; // Inserisco il primo esagono (quello su cui ho chiamato la funzione) all'interno della coda e lo marco nella tabella booleana queue[tail] = (QueueNode){x, y, 0}; visited[queue[tail].x][queue[tail].y]=true; tail = tail + 1; // 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} }; while (head < tail){ // Fino a quando ho oggetti in coda, faccio sta merda QueueNode current_node = queue[head]; // Prendo il primo oggetto nella coda head=head+1; if (current_node.dist < radius){ // Se la sua distanza dalla sorgente è minore di quella del raggio, gli aggiorno il costo e guardo i vicini map -> grid[current_node.x][current_node.y].land_cost = map -> grid[current_node.x][current_node.y].land_cost + (float) floor(cost * fmax(0,(float) (radius-current_node.dist)/radius)); if (map -> grid[current_node.x][current_node.y].land_cost>100){ // Faccio il controllo sul costo (può essere compreso tra 0 e 100) map -> grid[current_node.x][current_node.y].land_cost=100; } if (map -> grid[current_node.x][current_node.y].land_cost<0){ map -> grid[current_node.x][current_node.y].land_cost=0; } // Scegli le direzioni in base alla parità della riga const int (*dirs)[2] = (current_node.y % 2 == 0) ? dir_even : dir_odd; int new_x; int new_y; // Esplora i 6 vicini for (int i = 0; i < 6; i++) { new_x = current_node.x + dirs[i][0]; new_y = current_node.y + dirs[i][1]; // Controllo limiti mappa e se li rispetta, lo aggiungo in coda if (is_valid(map, new_x, new_y) && !visited[new_x][new_y]) { visited[new_x][new_y] = true; queue[tail] = (QueueNode){new_x, new_y, current_node.dist + 1}; tail = tail + 1; } } } } // Dealloco dalla memoria la tabella booleana for (int i = 0; i < map->cols; i++) { free(visited[i]); } free(visited); // Dealloco la coda free(queue); fputs("OK\n", stdout); fflush(stdout); } */ void 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; } 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; } 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; } } // 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); // 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; int approx_distance_from_end; 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[] = { 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 approx:%d]-> ", current -> x, current -> y, current -> distance_from_start, current -> approx_distance_from_end); 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, int approx_distance_from_end){ // 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 int index = calculate_hash(x, y, ht -> size); // Calcola l'index con la hash function 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->approx_distance_from_end = approx_distance_from_end; 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; } HashNode* search_element (HashTable* ht, int x, int y){ // Cerca un nodo dalla hash table 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; } return current; } // END // BEGIN PATH-FINDING (A*) // BEGIN STRUTTURE typedef struct { int x,y; } QueueNode_PathFinding; // END void enqueue(QueueNode_PathFinding* queue, HashTable* ht, int x, int y, int head, int tail){ HashNode* hn = search_element(ht, x, y); int node_total_cost = hn->distance_from_start + hn->approx_distance_from_end; // Trovo il costo totale del nodo da inserire HashNode* temp_node = NULL; for (int i=head; idistance_from_start + temp_node->approx_distance_from_end; // Trovo il costo totale del nodo i-esimo nella coda if(node_total_cost < temp_cost){ // Se il nodo da inserire costa meno del nodo i-esimo della coda for (int j=tail; j>=i; j--){ // Sposto tutti i nodi successivi avanti di uno queue[j+1]=queue[j]; } queue[i]=(QueueNode_PathFinding) {x, y}; // 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 } int travel_cost(HexMap* map, HashTable* ht, int xp, int yp, int xd, int yd){ // FUNZIONAMENTO: // È un algoritmo A* in cui tengo sempre una coda ORDINATA che hain testa le coordinate x,y del nodo con minore costo totale, mentre in coda quello con maggior costo totale. // Il costo totale è dato dalla distanza dal nodo sorgente + euristica (distanza approssimativa in linea d'aria dalla fine). // // 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; } QueueNode_PathFinding* queue = malloc(map->rows * map->cols * sizeof(QueueNode_PathFinding)*1000); // Inizializzo la coda, head e tail int head=0; int tail=0; queue[0] = (QueueNode_PathFinding) {xp, yp}; // Inserisco il nodo sorgente in coda tail++; insert_or_update_element(ht, xp, yp, 0, hexagons_distance(xp,yp,xd,yd)); // 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) HashNode* current_ht; // Inizializzo current_ht, ossia il 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 distance_from_start; free(queue); // Dealloco la coda clear_hash_table(ht); // Pulisco la hash table return (ris); } 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} }; // Scegli le direzioni 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 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->distance_from_start+map->grid[current.x][current.y].land_cost, hexagons_distance(new_x, new_y, xd, yd))){ // Se inserendolo ottengo true enqueue(queue, ht, new_x, new_y, 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; idistance_from_start+air_routes[i].cost, hexagons_distance(new_x, new_y, xd, yd))){ // Se inserendolo ottengo true enqueue(queue, ht, new_x, new_y, head, tail); // Lo aggiungo alla coda tail++; } } } } } free(queue); // Dealloco la coda clear_hash_table(ht); // Pulisco la hash table return -1; } // END int main(){ char testo[30]; int inp_uno, inp_due, inp_tre, inp_quattro; HexMap map; HashTable* ht = NULL; int already_initialized=0; while (1){ // LEGGO L'INPUT E SE RAGGIUNGO L'EOF FACCIO UNA BREAK int tmp_input = scanf("%s %d %d %d %d",testo, &inp_uno, &inp_due, &inp_tre, &inp_quattro); if (tmp_input==-1){ break; } if (strcmp(testo, "init")==0){ if (already_initialized==1){ destroy_map(&map); } init_map(&map, inp_uno, inp_due); already_initialized=1; if (ht!=NULL){ destroy_hash_table(&ht); } ht = create_hash_table(inp_uno * inp_due); } if (strcmp(testo, "print")==0){ print_map(&map); } if (strcmp(testo, "change_cost")==0){ change_cost(&map, inp_uno, inp_due, inp_tre, inp_quattro); clear_hash_table(ht); } if (strcmp(testo, "toggle_air_route")==0){ toggle_air_route(&map, inp_uno, inp_due, inp_tre, inp_quattro); } if (strcmp(testo, "travel_cost")==0){ int costo = travel_cost(&map, ht, inp_uno, inp_due, inp_tre, inp_quattro); fprintf(stdout, "%d\n", costo); fflush(stdout); } } }