int main(int argc, char const *argv[]) { if (argc != 3 && argc != 4) { TERMINATE("%s\n%s\n%s\n%s\n", "Argumen tidak sesuai", "Cara kompilasi: g++ solver_checker.cpp -o solver_checker", "Cara pemakaian: ./solver_checker <file_input> <file_output> [<file_kamus>]", " Jika <file_kamus> tidak ada, maka akan digunakan \"kamus.txt\"."); } // Membuka berkas masukan dan keluaran FILE* fin = fopen(argv[1], "r"); if (fin == NULL) { TERMINATE("Berkas masukan \"%s\" tidak ditemukan!\n", argv[1]); } FILE* fout = fopen(argv[2], "r"); if (fout == NULL) { fclose(fin); TERMINATE("Berkas keluaran \"%s\" tidak ditemukan!\n", argv[2]); } if (argc == 4) { strcpy(buffer, argv[3]); } else { strcpy(buffer, DEFAULT_DICTIONARY_FILENAME); } FILE* fdict = fopen(buffer, "r"); if (fdict == NULL) { fclose(fin); fclose(fout); TERMINATE("Berkas kamus \"%s\" tidak ditemukan!\n", buffer); } // Membaca seluruh kata pada kamus dan memasukkannya ke set dictionary set<string> dictionary; while (fscanf(fdict, "%s", buffer) == 1) { toUppercaseWord(buffer); // Memastikan format sesuai ASSERT(consistsOfEnglishAlphabets(buffer), "Kata \"%s\" pada kamus tidak valid!\n", buffer); // Memastikan kata tidak muncul dua kali ASSERT(!EXIST(buffer, dictionary), "Kata \"%s\" muncul dua kali pada kamus\n", buffer); dictionary.insert(buffer); } fclose(fdict); // Membaca seluruh kata, koordinat dan arah pada masukan dan memasukkannya ke inputWords vector<CrosswordWord> inputWords; int row, col; while (fscanf(fin, "%s%d%d%s", buffer, &row, &col, bufferDirection) == 4) { toUppercaseWord(buffer); toUppercaseWord(bufferDirection); // Memastikan format sesuai ASSERT(consistsOfEnglishAlphabetsOrDots(buffer), "Kata \"%s\" pada masukan tidak valid!\n", buffer); ASSERT(!(strcmp(bufferDirection, "MENDATAR") && strcmp(bufferDirection, "MENURUN")), "Arah \"%s\" tidak valid! (Seharusnya \"mendatar\" atau \"menurun\")\n", bufferDirection); inputWords.push_back(CrosswordWord(buffer, CrosswordWord::getDirection(bufferDirection), row, col)); } fclose(fin); // Langsung berhenti saat masukan kosong ASSERT(!inputWords.empty(), "Tidak ada kata pada masukan!\n"); // Membaca seluruh kata, koordinat dan arah pada keluaran dan memasukkannya ke outputWords vector<CrosswordWord> outputWords; while (fscanf(fout, "%s%d%d%s", buffer, &row, &col, bufferDirection) == 4) { toUppercaseWord(buffer); toUppercaseWord(bufferDirection); // Memastikan format sesuai ASSERT(consistsOfEnglishAlphabets(buffer), "Kata \"%s\" pada keluaran tidak valid!\n", buffer); ASSERT(!(strcmp(bufferDirection, "MENDATAR") && strcmp(bufferDirection, "MENURUN")), "Arah \"%s\" tidak valid! (Seharusnya \"mendatar\" atau \"menurun\")\n", bufferDirection); outputWords.push_back(CrosswordWord(buffer, CrosswordWord::getDirection(bufferDirection), row, col)); } fclose(fout); // Langsung berhenti saat keluaran kosong ASSERT(!outputWords.empty(), "Tidak ada kata pada keluaran!\n"); // Memasukkan seluruh kata ke dalam grid yang direpresentasikan oleh map sambil mendaftar // semua kontradiksi yang ada pada grid map<Coordinate, char> crosswordGrid; map<Coordinate, CrosswordWord> directionMapping[2]; vector<Coordinate> contradictions; Coordinate topLeft, bottomRight; bool firstCharacter = true; for (vector<CrosswordWord>::iterator crosswordWord = inputWords.begin(); crosswordWord != inputWords.end(); ++crosswordWord) { Coordinate position = crosswordWord->start; // Periksa apakah sudah ada kata pada koordinat dan arah yang sama ASSERT(!EXIST(position, directionMapping[crosswordWord->direction]), "Ada dua kata pada koordinat (%d, %d) %s\n", position.row, position.col, crosswordWord->direction == ACCROSS ? "mendatar" : "menurun"); directionMapping[crosswordWord->direction][position] = *crosswordWord; // Masukkan tiap karakter ke map for (unsigned int i = 0; i < crosswordWord->word.length(); ++i) { char ch = crosswordWord->word[i]; if (EXIST(position, crosswordGrid)) { if (crosswordGrid[position] != ch) { crosswordGrid[position] = '?'; contradictions.push_back(position); } } else { crosswordGrid[position] = ch; } // Memperbarui batas crossword: topLeft dan bottomRight if (firstCharacter) { firstCharacter = false; topLeft = bottomRight = position; } else { if (position.row < topLeft.row) { topLeft.row = position.row; } if (position.col < topLeft.col) { topLeft.col = position.col; } if (position.row > bottomRight.row) { bottomRight.row = position.row; } if (position.col > bottomRight.col) { bottomRight.col = position.col; } } // Memajukan koordinat position sesuai dengan arah kata position.advance(crosswordWord->direction); } } // Cetak grid sebelum diisi printf("Teka-teki silang yang pada masukan:\n\n"); printCrossword(crosswordGrid); // Jika ada kontradiksi, keluarkan daftarnya if (!contradictions.empty()) { string message = "Terdapat kontradiksi pada koordinat"; for (int i = 0; i < contradictions.size(); ++i) { if (i == 0) { sprintf(buffer, " (%d, %d)", contradictions[i].row, contradictions[i].col); } else if (i + 1 < contradictions.size()) { sprintf(buffer, ", (%d, %d)", contradictions[i].row, contradictions[i].col); } else if (contradictions.size() == 2) { sprintf(buffer, " dan (%d, %d)", contradictions[i].row, contradictions[i].col); } else { sprintf(buffer, ", dan (%d, %d)", contradictions[i].row, contradictions[i].col); } message += buffer; } TERMINATE("%s\n", message.c_str()); } // Final check: cari kata baru yang terbentuk, atau cari kata yang tidak valid for (map<Coordinate, char>::iterator startIterator = crosswordGrid.begin(); startIterator != crosswordGrid.end(); ++startIterator) { Coordinate start = startIterator->first; for (int direction = 0; direction < 2; ++direction) { Coordinate position = start; string word; while (EXIST(position, crosswordGrid)) { word.push_back(crosswordGrid[position]); position.advance(direction); } Coordinate previous = start - Coordinate::STEP[direction]; // Periksa kalau pada koordinat dan arah ini ada kata pada map if (EXIST(start, directionMapping[direction])) { string originalWord = directionMapping[direction][start].word; // Kata tidak cocok ASSERT(word == originalWord, "Pada koordinat (%d, %d) %s: Seharusnya \"%s\", tapi ditemukan \"%s\"\n", start.row, start.col, direction == ACCROSS ? "mendatar" : "menurun", originalWord.c_str(), word.c_str()); // Kata diawali oleh huruf lain ASSERT(!EXIST(previous, crosswordGrid), "Ditemukan huruf sebelum kata \"%s\", (%d, %d) %s\n", originalWord.c_str(), start.row, start.col, direction == ACCROSS ? "mendatar" : "menurun"); } else { // Periksa kalau ada kata baru yang terbentuk ASSERT(EXIST(previous, crosswordGrid) || word.length() <= WORD_THRESHOLD, "Ditemukan kata \"%s\" yang tidak ada pada masukan pada (%d, %d) %s\n", word.c_str(), start.row, start.col, direction == ACCROSS ? "mendatar" : "menurun"); } } } // Sekarang, isi teka-teki silang tersebut dengan kata pada keluaran // Tiap kali ada kegagalan, simpan di issues. map<int, string> issues; set<string> usedWords; map<Coordinate, bool> finished[2]; int totalSuccess = 0; for (vector<CrosswordWord>::iterator crosswordWord = outputWords.begin(); crosswordWord != outputWords.end(); ++crosswordWord) { int i = crosswordWord - outputWords.begin(); Coordinate position = crosswordWord->start; // Kata tidak ada pada kamus if (!EXIST(crosswordWord->word, dictionary)) { sprintf(buffer, "Kata \"%s\" tidak ada pada kamus", crosswordWord->word.c_str()); issues[i] = buffer; continue; } // Kata yang sama telah dipakai if (EXIST(crosswordWord->word, usedWords)) { sprintf(buffer, "Kata \"%s\" telah digunakan sebelumnya", crosswordWord->word.c_str()); issues[i] = buffer; // issue ini diperbolehkan untuk lanjut } // Kata tidak ada pada directionMapping if (!EXIST(position, directionMapping[crosswordWord->direction])) { sprintf(buffer, "Tidak ada yang harus diisi pada (%d, %d) %s", position.row, position.col, crosswordWord->direction == ACCROSS ? "mendatar" : "menurun"); issues[i] = buffer; continue; } // Kata sudah diselesaikan sebelumnya if (finished[crosswordWord->direction][position]) { sprintf(buffer, "Kata pada (%d, %d) %s sudah diisi sebelumnya", position.row, position.col, crosswordWord->direction == ACCROSS ? "mendatar" : "menurun"); issues[i] = buffer; continue; } // Mencoba untuk mengisi kata pada posisi dan arah ini CrosswordWord unknown = directionMapping[crosswordWord->direction][position]; bool success = unknown.word.length() == crosswordWord->word.length(); for (int j = 0; j < unknown.word.length() && success; ++j) { if (unknown.word[j] != '.' && unknown.word[j] != crosswordWord->word[j]) { success = false; } } if (!success) { sprintf(buffer, "Kata \"%s\" tidak bisa diiisi pada (%d, %d) %s", crosswordWord->word.c_str(), position.row, position.col, crosswordWord->direction == ACCROSS ? "mendatar" : "menurun" ); issues[i] = buffer; continue; } // Berhasil! finished[crosswordWord->direction][position] = true; totalSuccess++; for (int j = 0; j < unknown.word.length() && success; ++j) { crosswordGrid[position] = crosswordWord->word[j]; position.advance(crosswordWord->direction); } } // Keluarkan hasilnya printf("Teka-teki silang setelah diisi:\n\n"); printCrossword(crosswordGrid); printf("Kata yang berhasil diisi : %d dari %d\n", totalSuccess, (int)inputWords.size()); if (!issues.empty()) { printf("Ditemukan %d masalah pada keluaran :\n", (int)issues.size()); for (map<int, string>::iterator it = issues.begin(); it != issues.end(); ++it) { printf("%3d. %s\n", it->first + 1, it->second.c_str()); } } return 0; }
int main(int argc, char const *argv[]) { if (argc != 3) { TERMINATE("%s\n%s\n%s\n", "Argumen tidak sesuai", "Cara kompilasi: g++ generator_checker.cpp -o generator_checker", "Cara pemakaian: ./generator_checker <file_input> <file_output>"); } // Membuka berkas masukan dan keluaran FILE* fin = fopen(argv[1], "r"); if (fin == NULL) { TERMINATE("Berkas masukan \"%s\" tidak ditemukan!\n", argv[1]); } FILE* fout = fopen(argv[2], "r"); if (fout == NULL) { fclose(fin); TERMINATE("Berkas keluaran \"%s\" tidak ditemukan!\n", argv[2]); } // Membaca seluruh kata pada berkas masukan dan memasukkannya ke inputWords set<string> inputWords; while (fscanf(fin, "%s", buffer) == 1) { toUppercaseWord(buffer); // Memastikan format sesuai ASSERT(consistsOfEnglishAlphabets(buffer), "Kata \"%s\" pada masukan tidak valid!\n", buffer); // Langsung keluar jika menemukan kata yang ada dua kali pada masukan if (EXIST(buffer, inputWords)) { fclose(fin); TERMINATE("Kata %s muncul dua kali pada masukan!\n", buffer); } inputWords.insert(buffer); } fclose(fin); // Membaca seluruh kata, koordinat dan arah pada keluaran dan memasukkannya ke outputWords vector<CrosswordWord> outputWords; set<string> exsistedOutputWords; int row, col; while (fscanf(fout, "%s%d%d%s", buffer, &row, &col, bufferDirection) == 4) { toUppercaseWord(buffer); toUppercaseWord(bufferDirection); // Memastikan format sesuai ASSERT(consistsOfEnglishAlphabets(buffer), "Kata \"%s\" pada keluaran tidak valid!\n", buffer); ASSERT(!(strcmp(bufferDirection, "MENDATAR") && strcmp(bufferDirection, "MENURUN")), "Arah \"%s\" tidak valid! (Seharusnya \"mendatar\" atau \"menurun\")\n", bufferDirection); // Langsung keluar jika menemukan kata yang tidak ada pada masukan, atau muncul dua kali // pada keluaran if (!EXIST(buffer, inputWords)) { fclose(fout); TERMINATE("Kata %s tidak ditemukan pada masukan!\n", buffer); } if (EXIST(buffer, exsistedOutputWords)) { fclose(fout); TERMINATE("Kata %s muncul dua kali pada keluaran!\n", buffer); } outputWords.push_back(CrosswordWord(buffer, CrosswordWord::getDirection(bufferDirection), row, col)); exsistedOutputWords.insert(buffer); } fclose(fout); // Langsung berhenti saat keluaran kosong ASSERT(!outputWords.empty(), "Tidak ada kata pada keluaran!\n"); // Memasukkan seluruh kata ke dalam grid yang direpresentasikan oleh map sambil mendaftar // semua kontradiksi yang ada pada grid map<Coordinate, char> crosswordGrid; map<Coordinate, CrosswordWord> directionMapping[2]; vector<Coordinate> contradictions; for (vector<CrosswordWord>::iterator crosswordWord = outputWords.begin(); crosswordWord != outputWords.end(); ++crosswordWord) { Coordinate position = crosswordWord->start; // Periksa apakah sudah ada kata pada koordinat dan arah yang sama ASSERT(!EXIST(position, directionMapping[crosswordWord->direction]), "Ada dua kata pada koordinat (%d, %d) %s\n", position.row, position.col, crosswordWord->direction == ACCROSS ? "mendatar" : "menurun"); directionMapping[crosswordWord->direction][position] = *crosswordWord; // Masukkan tiap karakter ke map for (unsigned int i = 0; i < crosswordWord->word.length(); ++i) { char ch = crosswordWord->word[i]; if (EXIST(position, crosswordGrid)) { if (crosswordGrid[position] != ch) { crosswordGrid[position] = '?'; contradictions.push_back(position); } } else { crosswordGrid[position] = ch; } // Memajukan koordinat position sesuai dengan arah kata position.advance(crosswordWord->direction); } } // Cetak grid printf("Teka-teki silang yang terbentuk:\n\n"); printCrossword(crosswordGrid); // Jika ada kontradiksi, keluarkan daftarnya if (!contradictions.empty()) { string message = "Terdapat kontradiksi pada koordinat"; for (int i = 0; i < contradictions.size(); ++i) { if (i == 0) { sprintf(buffer, " (%d, %d)", contradictions[i].row, contradictions[i].col); } else if (i + 1 < contradictions.size()) { sprintf(buffer, ", (%d, %d)", contradictions[i].row, contradictions[i].col); } else if (contradictions.size() == 2) { sprintf(buffer, " dan (%d, %d)", contradictions[i].row, contradictions[i].col); } else { sprintf(buffer, ", dan (%d, %d)", contradictions[i].row, contradictions[i].col); } message += buffer; } TERMINATE("%s\n", message.c_str()); } // Final check: cari kata baru yang terbentuk, atau cari kata yang tidak valid for (map<Coordinate, char>::iterator startIterator = crosswordGrid.begin(); startIterator != crosswordGrid.end(); ++startIterator) { Coordinate start = startIterator->first; for (int direction = 0; direction < 2; ++direction) { Coordinate position = start; string word; while (EXIST(position, crosswordGrid)) { word.push_back(crosswordGrid[position]); position.advance(direction); } Coordinate previous = start - Coordinate::STEP[direction]; // Periksa kalau pada koordinat dan arah ini ada kata pada map if (EXIST(start, directionMapping[direction])) { string originalWord = directionMapping[direction][start].word; // Kata tidak cocok ASSERT(word == originalWord, "Pada koordinat (%d, %d) %s: Seharusnya \"%s\", tapi ditemukan \"%s\"\n", start.row, start.col, direction == ACCROSS ? "mendatar" : "menurun", originalWord.c_str(), word.c_str()); // Kata diawali oleh huruf lain ASSERT(!EXIST(previous, crosswordGrid), "Ditemukan huruf sebelum kata \"%s\", (%d, %d) %s\n", originalWord.c_str(), start.row, start.col, direction == ACCROSS ? "mendatar" : "menurun"); } else { // Periksa kalau ada kata baru yang terbentuk ASSERT(EXIST(previous, crosswordGrid) || word.length() <= WORD_THRESHOLD, "Ditemukan kata \"%s\" yang tidak ada pada masukan pada (%d, %d) %s\n", word.c_str(), start.row, start.col, direction == ACCROSS ? "mendatar" : "menurun"); } } } // Setelah semua valid, keluarkan metrik penilaian printf("Teka-teki silang di atas valid!\n\n"); // Keluarkan banyak kata yang berhasil dipakai printf("1. Kata yang terpakai : %d dari %d\n", (int)outputWords.size(), (int)inputWords.size()); // Keluarkan "keterhubungan" dalam bentuk banyak komponen graf yang ada // Teknik flood-fill akan digunakan, dengan menggunakan struktur data stack int numComponent = 0; map<Coordinate, int> visited; for (map<Coordinate, char>::iterator startIterator = crosswordGrid.begin(); startIterator != crosswordGrid.end(); ++startIterator) { if (visited[startIterator->first]) { continue; } vector<Coordinate> stack; stack.push_back(startIterator->first); visited[stack.back()] = 1; while (!stack.empty()) { Coordinate top = stack.back(), temp; stack.pop_back(); temp = top - Coordinate::STEP[0]; if (!visited[temp] && EXIST(temp, crosswordGrid)) { stack.push_back(temp); visited[temp] = 1; } temp = top + Coordinate::STEP[0]; if (!visited[temp] && EXIST(temp, crosswordGrid)) { stack.push_back(temp); visited[temp] = 1; } temp = top - Coordinate::STEP[1]; if (!visited[temp] && EXIST(temp, crosswordGrid)) { stack.push_back(temp); visited[temp] = 1; } temp = top + Coordinate::STEP[1]; if (!visited[temp] && EXIST(temp, crosswordGrid)) { stack.push_back(temp); visited[temp] = 1; } } numComponent++; } printf("2. Komponen terhubung yang terbentuk : %d\n", numComponent); // Keluarkan ukuran teka-teki silang Coordinate topLeft, bottomRight; getCrosswordGridBounds(crosswordGrid, &topLeft, &bottomRight); printf("3. Ukuran teka-teki silang : %d baris x %d kolom = %d kotak\n", bottomRight.row - topLeft.row + 1, bottomRight.col - topLeft.col + 1, (bottomRight.row - topLeft.row + 1) * (bottomRight.col - topLeft.col + 1)); printf(" Banyak kotak yang terpakai : %d kotak\n", (int)crosswordGrid.size()); return 0; }