# 2023 程式設計 考試抄襲分析 ## 如何抓抄襲? 我們採用史丹佛大學開發的 MOSS(https://theory.stanford.edu/~aiken/moss/),這是一套用來專門分析程式碼抄襲的工具。此工具會檢測同學之間的程式碼相似程度,且此工具在更換程式順序、變數名稱下仍可以正確檢測。 然而,此套工具也明確說明「僅供參考」,因此仍需要由助教進行二次確認。但相比一個個程式點開來檢查,節省下非常多的時間。 ## 抄襲檢測流程 1. 將學生的程式碼皆上傳至 MOSS 2. 若該次考試有提供範例程式,將此範例程式設為排除項目上傳至 MOSS 3. MOSS 產生結果網頁,如下圖: <!-- ![image](https://hackmd.io/_uploads/BJTzDgnU0.png) --> ![螢幕擷取畫面 2024-06-28 164049](https://hackmd.io/_uploads/SJQS9rgP0.png) 在結果報告中,會顯示不同色塊,代表相似性高的片段 ![螢幕擷取畫面 2024-07-01 232928](https://hackmd.io/_uploads/B1ICiSgv0.png) ![image](https://hackmd.io/_uploads/Hy6NnrxvR.png) 4. 由助教群討論檢視 MOSS 較前面的結果,若助教群多數認為有抄襲嫌疑,將抄襲的檢測結果與抄襲判斷交給老師做最後決定,如下圖: <!-- ![image](https://hackmd.io/_uploads/B1odcl3IR.png) --> ![螢幕擷取畫面 2024-06-28 165455](https://hackmd.io/_uploads/S1KYqSewR.png) ## 抄襲情形 ### 程設一期中考 共 2 組 - 有發現有一組人抄襲網路上相同的程式碼 - 一組程式邏輯非常相似 ### 程設一期末考 共 6 組 - 在計算分數與踩地雷等兩題,發現多組程式邏輯相似,變數命名也相似的程式碼。但因為題目較容易,程式碼行數短,故沒有認定為嚴重抄襲 ![image](https://hackmd.io/_uploads/rJvujNgwR.png) ![image](https://hackmd.io/_uploads/Byvco4ePC.png) ### 程設二期中考 共 0 組 ### 程設二期末考 共 1 組 - 在核心函式上完全相同,但最後兩位同學在該題獲得分數差異甚大,分別為 5 分與 25 分 ![image](https://hackmd.io/_uploads/HJc4MrgDR.png) ## 現今大型語言模型之解題能力 隨著大型語言模型(LLM)變得普及,我們預期會有越來越多的同學使用 LLM 輔助進行解題,其中 LLM 對於複雜題目的直接解題能力,將直接影響到 LLM 對於輔助行為的涉入程度(僅從語法補全、函數實作,乃至於直接依照指令完成整份程式)。 此次,助教使用目前最廣泛被一般大眾使用的 LLM 服務 OpenAI ChatGPT (GPT-4o) 嘗試在不對 LLM 生成之程式碼進行修改的情況下,以自然語言指令的方式引導其完成程設二期末考之其中一題。 ![截圖 2024-07-01 晚上11.16.51](https://hackmd.io/_uploads/ryMOdrePR.png) ![截圖 2024-07-01 晚上11.17.39](https://hackmd.io/_uploads/HkgiurxvC.png) ![截圖 2024-07-01 晚上11.18.00](https://hackmd.io/_uploads/S1Lh_Hxv0.png) 此題涉及: 1. 利用 libcurl 抓取網頁資訊 2. 解析出網頁中的所需資訊並進行統計 3. 依統計資訊產生一張 BMP 圓餅圖 雖然 ChatGPT 具有直接瀏覽網頁的功能,但因該網頁大小過大(超過 1.5MB),會遇到 LLM 詞元(token)上限問題,因此對於網頁的特徵工程由人類進行,再指示 LLM 編寫程式尋找特定字詞當作起始位置再解析特定詞語。 完整過程:https://chatgpt.com/share/aaa2b756-d6ad-462f-a87f-865bf2ae371e 解題的順序由人類決定從「2. 解析出網頁中的所需資訊並進行統計」開始: > Write a C program, by giving a HTML file, find the line which starts with var resultList =, and then find all "SCORE":"N" where N is a one-digit number (0, 1, 2, ...), and output the count of each N. :::spoiler Code 1 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #define MAX_LINE_LENGTH 1024 int main() { FILE *file; char line[MAX_LINE_LENGTH]; int score_count[10] = {0}; file = fopen("input.html", "r"); if (file == NULL) { perror("Error opening file"); return EXIT_FAILURE; } // Find the line starting with "var resultList =" while (fgets(line, MAX_LINE_LENGTH, file) != NULL) { if (strncmp(line, "var resultList =", 16) == 0) { break; } } if (feof(file)) { fprintf(stderr, "Error: Line starting with 'var resultList =' not found\n"); fclose(file); return EXIT_FAILURE; } // Find all occurrences of "SCORE":"N" where N is a one-digit number char *pos = line; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } fclose(file); // Output the count of each score for (int i = 0; i <= 9; i++) { printf("SCORE %d: %d\n", i, score_count[i]); } return EXIT_SUCCESS; } ``` ::: LLM 完整地給出可以編譯並執行的 C 原始碼,但統計資訊不完整。原因是因為 LLM 不知道檔案大小的緣故,所以給出的 buffer size 不夠大,於是指示其增加大小。 > MAX_LINE_LENGTH should be larger (maybe 8MB?) :::spoiler Code 2 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB int main() { FILE *file; char *line; int score_count[10] = {0}; line = (char *)malloc(MAX_LINE_LENGTH * sizeof(char)); if (line == NULL) { perror("Error allocating memory"); return EXIT_FAILURE; } file = fopen("input.html", "r"); if (file == NULL) { perror("Error opening file"); free(line); return EXIT_FAILURE; } // Find the line starting with "var resultList =" while (fgets(line, MAX_LINE_LENGTH, file) != NULL) { if (strncmp(line, "var resultList =", 16) == 0) { break; } } if (feof(file)) { fprintf(stderr, "Error: Line starting with 'var resultList =' not found\n"); fclose(file); free(line); return EXIT_FAILURE; } // Find all occurrences of "SCORE":"N" where N is a one-digit number char *pos = line; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } fclose(file); free(line); // Output the count of each score for (int i = 0; i <= 9; i++) { printf("SCORE %d: %d\n", i, score_count[i]); } return EXIT_SUCCESS; } ``` ::: LLM 也適當的調整了相應的位置。 接著開始讓 LLM 畫 BMP: > Ok, then make it plot a bmp pie chart too :::spoiler Code 3 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB #define WIDTH 800 #define HEIGHT 800 #define CENTER_X (WIDTH / 2) #define CENTER_Y (HEIGHT / 2) #define RADIUS (WIDTH / 3) typedef struct { unsigned char b; unsigned char g; unsigned char r; } Pixel; typedef struct { unsigned int width; unsigned int height; Pixel *pixels; } Bitmap; void save_bmp(const char *filename, Bitmap *bmp) { FILE *f; unsigned char bmpfileheader[14] = { 'B', 'M', // BMP signature 0, 0, 0, 0, // File size 0, 0, // Reserved 0, 0, // Reserved 54, 0, 0, 0 // Data offset }; unsigned char bmpinfoheader[40] = { 40, 0, 0, 0, // Info header size 0, 0, 0, 0, // Width 0, 0, 0, 0, // Height 1, 0, // Planes 24, 0, // Bits per pixel 0, 0, 0, 0, // Compression 0, 0, 0, 0, // Image size (can be 0 for BI_RGB) 0, 0, 0, 0, // X pixels per meter 0, 0, 0, 0, // Y pixels per meter 0, 0, 0, 0, // Total colors 0, 0, 0, 0 // Important colors }; unsigned int filesize = 54 + 3 * bmp->width * bmp->height; bmpfileheader[2] = (unsigned char)(filesize); bmpfileheader[3] = (unsigned char)(filesize >> 8); bmpfileheader[4] = (unsigned char)(filesize >> 16); bmpfileheader[5] = (unsigned char)(filesize >> 24); bmpinfoheader[4] = (unsigned char)(bmp->width); bmpinfoheader[5] = (unsigned char)(bmp->width >> 8); bmpinfoheader[6] = (unsigned char)(bmp->width >> 16); bmpinfoheader[7] = (unsigned char)(bmp->width >> 24); bmpinfoheader[8] = (unsigned char)(bmp->height); bmpinfoheader[9] = (unsigned char)(bmp->height >> 8); bmpinfoheader[10] = (unsigned char)(bmp->height >> 16); bmpinfoheader[11] = (unsigned char)(bmp->height >> 24); f = fopen(filename, "wb"); fwrite(bmpfileheader, 1, 14, f); fwrite(bmpinfoheader, 1, 40, f); for (int i = 0; i < bmp->height; i++) { fwrite(bmp->pixels + (bmp->width * (bmp->height - i - 1)), 3, bmp->width, f); } fclose(f); } void draw_pie_segment(Bitmap *bmp, double start_angle, double end_angle, Pixel color) { for (int y = 0; y < bmp->height; y++) { for (int x = 0; x < bmp->width; x++) { int dx = x - CENTER_X; int dy = y - CENTER_Y; double distance = sqrt(dx * dx + dy * dy); if (distance <= RADIUS) { double angle = atan2(dy, dx) * 180 / M_PI; if (angle < 0) angle += 360; if (angle >= start_angle && angle <= end_angle) { bmp->pixels[y * bmp->width + x] = color; } } } } } int main() { FILE *file; char *line; int score_count[10] = {0}; line = (char *)malloc(MAX_LINE_LENGTH * sizeof(char)); if (line == NULL) { perror("Error allocating memory"); return EXIT_FAILURE; } file = fopen("input.html", "r"); if (file == NULL) { perror("Error opening file"); free(line); return EXIT_FAILURE; } // Find the line starting with "var resultList =" while (fgets(line, MAX_LINE_LENGTH, file) != NULL) { if (strncmp(line, "var resultList =", 16) == 0) { break; } } if (feof(file)) { fprintf(stderr, "Error: Line starting with 'var resultList =' not found\n"); fclose(file); free(line); return EXIT_FAILURE; } // Find all occurrences of "SCORE":"N" where N is a one-digit number char *pos = line; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } fclose(file); free(line); // Create a bitmap for the pie chart Bitmap bmp; bmp.width = WIDTH; bmp.height = HEIGHT; bmp.pixels = (Pixel *)malloc(WIDTH * HEIGHT * sizeof(Pixel)); if (bmp.pixels == NULL) { perror("Error allocating memory for bitmap"); return EXIT_FAILURE; } // Fill the bitmap with white color for (int i = 0; i < WIDTH * HEIGHT; i++) { bmp.pixels[i].r = 255; bmp.pixels[i].g = 255; bmp.pixels[i].b = 255; } // Colors for the pie segments Pixel colors[10] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 0, 0}, {0, 128, 0}, {0, 0, 128}, {128, 128, 0} }; // Calculate total scores int total_scores = 0; for (int i = 0; i <= 9; i++) { total_scores += score_count[i]; } // Draw the pie chart segments double start_angle = 0.0; for (int i = 0; i <= 9; i++) { if (score_count[i] > 0) { double end_angle = start_angle + (score_count[i] / (double)total_scores) * 360.0; draw_pie_segment(&bmp, start_angle, end_angle, colors[i]); start_angle = end_angle; } } // Save the bitmap to a file save_bmp("pie_chart.bmp", &bmp); // Free allocated memory free(bmp.pixels); // Output the count of each score for (int i = 0; i <= 9; i++) { printf("SCORE %d: %d\n", i, score_count[i]); } return EXIT_SUCCESS; } ``` ::: 該程式確實可以輸出具有正確標頭與圓餅圖的 BMP,但其繪圖方向與起始角度與題目要求不同。 於是我們對其指示圓餅圖的方向與起始位置: > Let the pie chart draw counterclockwise starting from 90 degrees. :::spoiler Code 4 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB #define WIDTH 800 #define HEIGHT 800 #define CENTER_X (WIDTH / 2) #define CENTER_Y (HEIGHT / 2) #define RADIUS (WIDTH / 3) typedef struct { unsigned char b; unsigned char g; unsigned char r; } Pixel; typedef struct { unsigned int width; unsigned int height; Pixel *pixels; } Bitmap; void save_bmp(const char *filename, Bitmap *bmp) { FILE *f; unsigned char bmpfileheader[14] = { 'B', 'M', // BMP signature 0, 0, 0, 0, // File size 0, 0, // Reserved 0, 0, // Reserved 54, 0, 0, 0 // Data offset }; unsigned char bmpinfoheader[40] = { 40, 0, 0, 0, // Info header size 0, 0, 0, 0, // Width 0, 0, 0, 0, // Height 1, 0, // Planes 24, 0, // Bits per pixel 0, 0, 0, 0, // Compression 0, 0, 0, 0, // Image size (can be 0 for BI_RGB) 0, 0, 0, 0, // X pixels per meter 0, 0, 0, 0, // Y pixels per meter 0, 0, 0, 0, // Total colors 0, 0, 0, 0 // Important colors }; unsigned int filesize = 54 + 3 * bmp->width * bmp->height; bmpfileheader[2] = (unsigned char)(filesize); bmpfileheader[3] = (unsigned char)(filesize >> 8); bmpfileheader[4] = (unsigned char)(filesize >> 16); bmpfileheader[5] = (unsigned char)(filesize >> 24); bmpinfoheader[4] = (unsigned char)(bmp->width); bmpinfoheader[5] = (unsigned char)(bmp->width >> 8); bmpinfoheader[6] = (unsigned char)(bmp->width >> 16); bmpinfoheader[7] = (unsigned char)(bmp->width >> 24); bmpinfoheader[8] = (unsigned char)(bmp->height); bmpinfoheader[9] = (unsigned char)(bmp->height >> 8); bmpinfoheader[10] = (unsigned char)(bmp->height >> 16); bmpinfoheader[11] = (unsigned char)(bmp->height >> 24); f = fopen(filename, "wb"); fwrite(bmpfileheader, 1, 14, f); fwrite(bmpinfoheader, 1, 40, f); for (int i = 0; i < bmp->height; i++) { fwrite(bmp->pixels + (bmp->width * (bmp->height - i - 1)), 3, bmp->width, f); } fclose(f); } void draw_pie_segment(Bitmap *bmp, double start_angle, double end_angle, Pixel color) { for (int y = 0; y < bmp->height; y++) { for (int x = 0; x < bmp->width; x++) { int dx = x - CENTER_X; int dy = y - CENTER_Y; double distance = sqrt(dx * dx + dy * dy); if (distance <= RADIUS) { double angle = atan2(dy, dx) * 180 / M_PI + 90; if (angle < 0) angle += 360; if (angle >= start_angle && angle <= end_angle) { bmp->pixels[y * bmp->width + x] = color; } } } } } int main() { FILE *file; char *line; int score_count[10] = {0}; line = (char *)malloc(MAX_LINE_LENGTH * sizeof(char)); if (line == NULL) { perror("Error allocating memory"); return EXIT_FAILURE; } file = fopen("input.html", "r"); if (file == NULL) { perror("Error opening file"); free(line); return EXIT_FAILURE; } // Find the line starting with "var resultList =" while (fgets(line, MAX_LINE_LENGTH, file) != NULL) { if (strncmp(line, "var resultList =", 16) == 0) { break; } } if (feof(file)) { fprintf(stderr, "Error: Line starting with 'var resultList =' not found\n"); fclose(file); free(line); return EXIT_FAILURE; } // Find all occurrences of "SCORE":"N" where N is a one-digit number char *pos = line; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } fclose(file); free(line); // Create a bitmap for the pie chart Bitmap bmp; bmp.width = WIDTH; bmp.height = HEIGHT; bmp.pixels = (Pixel *)malloc(WIDTH * HEIGHT * sizeof(Pixel)); if (bmp.pixels == NULL) { perror("Error allocating memory for bitmap"); return EXIT_FAILURE; } // Fill the bitmap with white color for (int i = 0; i < WIDTH * HEIGHT; i++) { bmp.pixels[i].r = 255; bmp.pixels[i].g = 255; bmp.pixels[i].b = 255; } // Colors for the pie segments Pixel colors[10] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 0, 0}, {0, 128, 0}, {0, 0, 128}, {128, 128, 0} }; // Calculate total scores int total_scores = 0; for (int i = 0; i <= 9; i++) { total_scores += score_count[i]; } // Draw the pie chart segments double start_angle = 0.0; for (int i = 0; i <= 9; i++) { if (score_count[i] > 0) { double end_angle = start_angle + (score_count[i] / (double)total_scores) * 360.0; draw_pie_segment(&bmp, start_angle, end_angle, colors[i]); start_angle = end_angle; } } // Save the bitmap to a file save_bmp("pie_chart.bmp", &bmp); // Free allocated memory free(bmp.pixels); // Output the count of each score for (int i = 0; i <= 9; i++) { printf("SCORE %d: %d\n", i, score_count[i]); } return EXIT_SUCCESS; } ``` ::: 到此之前都非常順利,LLM 成功把起始位置改成 90 度,但我們在此嘗試多次都無法使其正確地將順時針修正成逆時針,LLM 會寫出產生錯誤圖片的程式碼。 所以我們直接指示修改一行中兩個字元: > It looks like it's now starting at 90 degrees, but still clockwise, not counterclockwise as expected. Consider changing double angle = atan2(dy, dx) * 180 / M_PI + 90 to double angle = -atan2(dy, dx) * 180 / M_PI - 90 :::spoiler Code 5 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB #define WIDTH 800 #define HEIGHT 800 #define CENTER_X (WIDTH / 2) #define CENTER_Y (HEIGHT / 2) #define RADIUS (WIDTH / 3) typedef struct { unsigned char b; unsigned char g; unsigned char r; } Pixel; typedef struct { unsigned int width; unsigned int height; Pixel *pixels; } Bitmap; void save_bmp(const char *filename, Bitmap *bmp) { FILE *f; unsigned char bmpfileheader[14] = { 'B', 'M', // BMP signature 0, 0, 0, 0, // File size 0, 0, // Reserved 0, 0, // Reserved 54, 0, 0, 0 // Data offset }; unsigned char bmpinfoheader[40] = { 40, 0, 0, 0, // Info header size 0, 0, 0, 0, // Width 0, 0, 0, 0, // Height 1, 0, // Planes 24, 0, // Bits per pixel 0, 0, 0, 0, // Compression 0, 0, 0, 0, // Image size (can be 0 for BI_RGB) 0, 0, 0, 0, // X pixels per meter 0, 0, 0, 0, // Y pixels per meter 0, 0, 0, 0, // Total colors 0, 0, 0, 0 // Important colors }; unsigned int filesize = 54 + 3 * bmp->width * bmp->height; bmpfileheader[2] = (unsigned char)(filesize); bmpfileheader[3] = (unsigned char)(filesize >> 8); bmpfileheader[4] = (unsigned char)(filesize >> 16); bmpfileheader[5] = (unsigned char)(filesize >> 24); bmpinfoheader[4] = (unsigned char)(bmp->width); bmpinfoheader[5] = (unsigned char)(bmp->width >> 8); bmpinfoheader[6] = (unsigned char)(bmp->width >> 16); bmpinfoheader[7] = (unsigned char)(bmp->width >> 24); bmpinfoheader[8] = (unsigned char)(bmp->height); bmpinfoheader[9] = (unsigned char)(bmp->height >> 8); bmpinfoheader[10] = (unsigned char)(bmp->height >> 16); bmpinfoheader[11] = (unsigned char)(bmp->height >> 24); f = fopen(filename, "wb"); fwrite(bmpfileheader, 1, 14, f); fwrite(bmpinfoheader, 1, 40, f); for (int i = 0; i < bmp->height; i++) { fwrite(bmp->pixels + (bmp->width * (bmp->height - i - 1)), 3, bmp->width, f); } fclose(f); } void draw_pie_segment(Bitmap *bmp, double start_angle, double end_angle, Pixel color) { for (int y = 0; y < bmp->height; y++) { for (int x = 0; x < bmp->width; x++) { int dx = x - CENTER_X; int dy = y - CENTER_Y; double distance = sqrt(dx * dx + dy * dy); if (distance <= RADIUS) { double angle = -atan2(dy, dx) * 180 / M_PI - 90; if (angle < 0) angle += 360; if (angle >= start_angle && angle <= end_angle) { bmp->pixels[y * bmp->width + x] = color; } } } } } int main() { FILE *file; char *line; int score_count[10] = {0}; line = (char *)malloc(MAX_LINE_LENGTH * sizeof(char)); if (line == NULL) { perror("Error allocating memory"); return EXIT_FAILURE; } file = fopen("input.html", "r"); if (file == NULL) { perror("Error opening file"); free(line); return EXIT_FAILURE; } // Find the line starting with "var resultList =" while (fgets(line, MAX_LINE_LENGTH, file) != NULL) { if (strncmp(line, "var resultList =", 16) == 0) { break; } } if (feof(file)) { fprintf(stderr, "Error: Line starting with 'var resultList =' not found\n"); fclose(file); free(line); return EXIT_FAILURE; } // Find all occurrences of "SCORE":"N" where N is a one-digit number char *pos = line; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } fclose(file); free(line); // Create a bitmap for the pie chart Bitmap bmp; bmp.width = WIDTH; bmp.height = HEIGHT; bmp.pixels = (Pixel *)malloc(WIDTH * HEIGHT * sizeof(Pixel)); if (bmp.pixels == NULL) { perror("Error allocating memory for bitmap"); return EXIT_FAILURE; } // Fill the bitmap with white color for (int i = 0; i < WIDTH * HEIGHT; i++) { bmp.pixels[i].r = 255; bmp.pixels[i].g = 255; bmp.pixels[i].b = 255; } // Colors for the pie segments Pixel colors[10] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 0, 0}, {0, 128, 0}, {0, 0, 128}, {128, 128, 0} }; // Calculate total scores int total_scores = 0; for (int i = 0; i <= 9; i++) { total_scores += score_count[i]; } // Draw the pie chart segments double start_angle = 90.0; for (int i = 0; i <= 9; i++) { if (score_count[i] > 0) { double end_angle = start_angle + (score_count[i] / (double)total_scores) * 360.0; draw_pie_segment(&bmp, start_angle, end_angle, colors[i]); start_angle = end_angle; } } // Save the bitmap to a file save_bmp("pie_chart.bmp", &bmp); // Free allocated memory free(bmp.pixels); // Output the count of each score for (int i = 0; i <= 9; i++) { printf("SCORE %d: %d\n", i, score_count[i]); } return EXIT_SUCCESS; } ``` ::: LLM 矯枉過正,雖然修正了剛剛的錯誤,但也把正確的部分改成錯的了。 讓我們提醒它一下: > Why change double start_angle = 0.0 to double start_angle = 90.0? :::spoiler Code 6 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB #define WIDTH 800 #define HEIGHT 800 #define CENTER_X (WIDTH / 2) #define CENTER_Y (HEIGHT / 2) #define RADIUS (WIDTH / 3) typedef struct { unsigned char b; unsigned char g; unsigned char r; } Pixel; typedef struct { unsigned int width; unsigned int height; Pixel *pixels; } Bitmap; void save_bmp(const char *filename, Bitmap *bmp) { FILE *f; unsigned char bmpfileheader[14] = { 'B', 'M', // BMP signature 0, 0, 0, 0, // File size 0, 0, // Reserved 0, 0, // Reserved 54, 0, 0, 0 // Data offset }; unsigned char bmpinfoheader[40] = { 40, 0, 0, 0, // Info header size 0, 0, 0, 0, // Width 0, 0, 0, 0, // Height 1, 0, // Planes 24, 0, // Bits per pixel 0, 0, 0, 0, // Compression 0, 0, 0, 0, // Image size (can be 0 for BI_RGB) 0, 0, 0, 0, // X pixels per meter 0, 0, 0, 0, // Y pixels per meter 0, 0, 0, 0, // Total colors 0, 0, 0, 0 // Important colors }; unsigned int filesize = 54 + 3 * bmp->width * bmp->height; bmpfileheader[2] = (unsigned char)(filesize); bmpfileheader[3] = (unsigned char)(filesize >> 8); bmpfileheader[4] = (unsigned char)(filesize >> 16); bmpfileheader[5] = (unsigned char)(filesize >> 24); bmpinfoheader[4] = (unsigned char)(bmp->width); bmpinfoheader[5] = (unsigned char)(bmp->width >> 8); bmpinfoheader[6] = (unsigned char)(bmp->width >> 16); bmpinfoheader[7] = (unsigned char)(bmp->width >> 24); bmpinfoheader[8] = (unsigned char)(bmp->height); bmpinfoheader[9] = (unsigned char)(bmp->height >> 8); bmpinfoheader[10] = (unsigned char)(bmp->height >> 16); bmpinfoheader[11] = (unsigned char)(bmp->height >> 24); f = fopen(filename, "wb"); fwrite(bmpfileheader, 1, 14, f); fwrite(bmpinfoheader, 1, 40, f); for (int i = 0; i < bmp->height; i++) { fwrite(bmp->pixels + (bmp->width * (bmp->height - i - 1)), 3, bmp->width, f); } fclose(f); } void draw_pie_segment(Bitmap *bmp, double start_angle, double end_angle, Pixel color) { for (int y = 0; y < bmp->height; y++) { for (int x = 0; x < bmp->width; x++) { int dx = x - CENTER_X; int dy = y - CENTER_Y; double distance = sqrt(dx * dx + dy * dy); if (distance <= RADIUS) { double angle = -atan2(dy, dx) * 180 / M_PI - 90; if (angle < 0) angle += 360; if (angle >= start_angle && angle <= end_angle) { bmp->pixels[y * bmp->width + x] = color; } } } } } int main() { FILE *file; char *line; int score_count[10] = {0}; line = (char *)malloc(MAX_LINE_LENGTH * sizeof(char)); if (line == NULL) { perror("Error allocating memory"); return EXIT_FAILURE; } file = fopen("input.html", "r"); if (file == NULL) { perror("Error opening file"); free(line); return EXIT_FAILURE; } // Find the line starting with "var resultList =" while (fgets(line, MAX_LINE_LENGTH, file) != NULL) { if (strncmp(line, "var resultList =", 16) == 0) { break; } } if (feof(file)) { fprintf(stderr, "Error: Line starting with 'var resultList =' not found\n"); fclose(file); free(line); return EXIT_FAILURE; } // Find all occurrences of "SCORE":"N" where N is a one-digit number char *pos = line; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } fclose(file); free(line); // Create a bitmap for the pie chart Bitmap bmp; bmp.width = WIDTH; bmp.height = HEIGHT; bmp.pixels = (Pixel *)malloc(WIDTH * HEIGHT * sizeof(Pixel)); if (bmp.pixels == NULL) { perror("Error allocating memory for bitmap"); return EXIT_FAILURE; } // Fill the bitmap with white color for (int i = 0; i < WIDTH * HEIGHT; i++) { bmp.pixels[i].r = 255; bmp.pixels[i].g = 255; bmp.pixels[i].b = 255; } // Colors for the pie segments Pixel colors[10] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 0, 0}, {0, 128, 0}, {0, 0, 128}, {128, 128, 0} }; // Calculate total scores int total_scores = 0; for (int i = 0; i <= 9; i++) { total_scores += score_count[i]; } // Draw the pie chart segments double start_angle = 0.0; for (int i = 0; i <= 9; i++) { if (score_count[i] > 0) { double end_angle = start_angle + (score_count[i] / (double)total_scores) * 360.0; draw_pie_segment(&bmp, start_angle, end_angle, colors[i]); start_angle = end_angle; } } // Save the bitmap to a file save_bmp("pie_chart.bmp", &bmp); // Free allocated memory free(bmp.pixels); // Output the count of each score for (int i = 0; i <= 9; i++) { printf("SCORE %d: %d\n", i, score_count[i]); } return EXIT_SUCCESS; } ``` ::: 現在的程式碼確實可以產生正確的 BMP 了,接著我們將 CLI 的範例給它,讓它實作出相同的輸出與輸入格式,並完成 curl 抓取資料的部分: > Great. Then try to make this interface: > $ ./fin02 --help > Usage: fin02 date > -h, --help Display this information and exit. > $ ./fin02 2024-04-23 > 0: 1183 (44.34%) > 1: 612 (22.94%) > 2: 386 (14.47%) > 3: 238 (8.92%) > 4: 109 (4.09%) > 5: 53 (1.99%) > 6: 42 (1.57%) > 7: 45 (1.69%) > $ ls > output.bmp > > By using libcurl to fetch the data from https://cpe.cse.nsysu.edu.tw/cpe/scoreboard/<date> :::spoiler Code 7 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #include <curl/curl.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB #define WIDTH 800 #define HEIGHT 800 #define CENTER_X (WIDTH / 2) #define CENTER_Y (HEIGHT / 2) #define RADIUS (WIDTH / 3) typedef struct { unsigned char b; unsigned char g; unsigned char r; } Pixel; typedef struct { unsigned int width; unsigned int height; Pixel *pixels; } Bitmap; typedef struct { char *data; size_t size; } MemoryBlock; void save_bmp(const char *filename, Bitmap *bmp) { FILE *f; unsigned char bmpfileheader[14] = { 'B', 'M', // BMP signature 0, 0, 0, 0, // File size 0, 0, // Reserved 0, 0, // Reserved 54, 0, 0, 0 // Data offset }; unsigned char bmpinfoheader[40] = { 40, 0, 0, 0, // Info header size 0, 0, 0, 0, // Width 0, 0, 0, 0, // Height 1, 0, // Planes 24, 0, // Bits per pixel 0, 0, 0, 0, // Compression 0, 0, 0, 0, // Image size (can be 0 for BI_RGB) 0, 0, 0, 0, // X pixels per meter 0, 0, 0, 0, // Y pixels per meter 0, 0, 0, 0, // Total colors 0, 0, 0, 0 // Important colors }; unsigned int filesize = 54 + 3 * bmp->width * bmp->height; bmpfileheader[2] = (unsigned char)(filesize); bmpfileheader[3] = (unsigned char)(filesize >> 8); bmpfileheader[4] = (unsigned char)(filesize >> 16); bmpfileheader[5] = (unsigned char)(filesize >> 24); bmpinfoheader[4] = (unsigned char)(bmp->width); bmpinfoheader[5] = (unsigned char)(bmp->width >> 8); bmpinfoheader[6] = (unsigned char)(bmp->width >> 16); bmpinfoheader[7] = (unsigned char)(bmp->width >> 24); bmpinfoheader[8] = (unsigned char)(bmp->height); bmpinfoheader[9] = (unsigned char)(bmp->height >> 8); bmpinfoheader[10] = (unsigned char)(bmp->height >> 16); bmpinfoheader[11] = (unsigned char)(bmp->height >> 24); f = fopen(filename, "wb"); fwrite(bmpfileheader, 1, 14, f); fwrite(bmpinfoheader, 1, 40, f); for (int i = 0; i < bmp->height; i++) { fwrite(bmp->pixels + (bmp->width * (bmp->height - i - 1)), 3, bmp->width, f); } fclose(f); } void draw_pie_segment(Bitmap *bmp, double start_angle, double end_angle, Pixel color) { for (int y = 0; y < bmp->height; y++) { for (int x = 0; x < bmp->width; x++) { int dx = x - CENTER_X; int dy = y - CENTER_Y; double distance = sqrt(dx * dx + dy * dy); if (distance <= RADIUS) { double angle = -atan2(dy, dx) * 180 / M_PI - 90; if (angle < 0) angle += 360; if (angle >= start_angle && angle <= end_angle) { bmp->pixels[y * bmp->width + x] = color; } } } } } size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; MemoryBlock *mem = (MemoryBlock *)userp; char *ptr = realloc(mem->data, mem->size + realsize + 1); if(ptr == NULL) { // out of memory return 0; } mem->data = ptr; memcpy(&(mem->data[mem->size]), contents, realsize); mem->size += realsize; mem->data[mem->size] = 0; return realsize; } int fetch_data(const char *url, MemoryBlock *mem) { CURL *curl_handle; CURLcode res; mem->data = malloc(1); // will be grown as needed by realloc mem->size = 0; // no data at this point curl_global_init(CURL_GLOBAL_ALL); curl_handle = curl_easy_init(); curl_easy_setopt(curl_handle, CURLOPT_URL, url); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)mem); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); res = curl_easy_perform(curl_handle); if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); return 1; } curl_easy_cleanup(curl_handle); curl_global_cleanup(); return 0; } void parse_and_count_scores(const char *data, int score_count[10]) { const char *pos = data; while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 9) { score_count[score]++; } } pos++; } } void print_usage() { printf("Usage: fin02 date\n"); printf("-h, --help Display this information and exit.\n"); } int main(int argc, char *argv[]) { if (argc != 2) { print_usage(); return EXIT_FAILURE; } if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { print_usage(); return EXIT_SUCCESS; } char url[256]; snprintf(url, sizeof(url), "https://cpe.cse.nsysu.edu.tw/cpe/scoreboard/%s", argv[1]); MemoryBlock mem; if (fetch_data(url, &mem) != 0) { fprintf(stderr, "Failed to fetch data from %s\n", url); return EXIT_FAILURE; } int score_count[10] = {0}; parse_and_count_scores(mem.data, score_count); free(mem.data); // Calculate total scores int total_scores = 0; for (int i = 0; i <= 9; i++) { total_scores += score_count[i]; } // Print the score counts and percentages for (int i = 0; i <= 9; i++) { if (total_scores > 0) { printf("%d: %d (%.2f%%)\n", i, score_count[i], (score_count[i] / (double)total_scores) * 100); } else { printf("%d: %d (0.00%%)\n", i, score_count[i]); } } // Create a bitmap for the pie chart Bitmap bmp; bmp.width = WIDTH; bmp.height = HEIGHT; bmp.pixels = (Pixel *)malloc(WIDTH * HEIGHT * sizeof(Pixel)); if (bmp.pixels == NULL) { perror("Error allocating memory for bitmap"); return EXIT_FAILURE; } // Fill the bitmap with white color for (int i = 0; i < WIDTH * HEIGHT; i++) { bmp.pixels[i].r = 255; bmp.pixels[i].g = 255; bmp.pixels[i].b = 255; } // Colors for the pie segments Pixel colors[10] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 0, 0}, {0, 128, 0}, {0, 0, 128}, {128, 128, 0} }; // Draw the pie chart segments double start_angle = 0.0; for (int i = 0; i <= 9; i++) { if (score_count[i] > 0) { double end_angle = start_angle + (score_count[i] / (double)total_scores) * 360.0; draw_pie_segment(&bmp, start_angle, end_angle, colors[i]); start_angle = end_angle; } } // Save the bitmap to a file save_bmp("output.bmp", &bmp); // Free allocated memory free(bmp.pixels); return EXIT_SUCCESS; } ``` ::: 至此,程式已經的大致完成。但我們可以發現其已經開始忘記一開始的一些資料處理方式,我們提供錯誤的輸入並提醒它: > ./fin02 2024-04-23 > 0: 2362 (44.27%) > 1: 1228 (23.01%) > 2: 772 (14.47%) > 3: 476 (8.92%) > 4: 218 (4.09%) > 5: 106 (1.99%) > 6: 84 (1.57%) > 7: 90 (1.69%) > 8: 0 (0.00%) > 9: 0 (0.00%) > > It looks like you are calculating the data twice, only the line starting with var resultList =. Also, don't count 8 and 9. :::spoiler Code 8 ```c= #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <math.h> #include <curl/curl.h> #define MAX_LINE_LENGTH 8 * 1024 * 1024 // 8MB #define WIDTH 800 #define HEIGHT 800 #define CENTER_X (WIDTH / 2) #define CENTER_Y (HEIGHT / 2) #define RADIUS (WIDTH / 3) typedef struct { unsigned char b; unsigned char g; unsigned char r; } Pixel; typedef struct { unsigned int width; unsigned int height; Pixel *pixels; } Bitmap; typedef struct { char *data; size_t size; } MemoryBlock; void save_bmp(const char *filename, Bitmap *bmp) { FILE *f; unsigned char bmpfileheader[14] = { 'B', 'M', // BMP signature 0, 0, 0, 0, // File size 0, 0, // Reserved 0, 0, // Reserved 54, 0, 0, 0 // Data offset }; unsigned char bmpinfoheader[40] = { 40, 0, 0, 0, // Info header size 0, 0, 0, 0, // Width 0, 0, 0, 0, // Height 1, 0, // Planes 24, 0, // Bits per pixel 0, 0, 0, 0, // Compression 0, 0, 0, 0, // Image size (can be 0 for BI_RGB) 0, 0, 0, 0, // X pixels per meter 0, 0, 0, 0, // Y pixels per meter 0, 0, 0, 0, // Total colors 0, 0, 0, 0 // Important colors }; unsigned int filesize = 54 + 3 * bmp->width * bmp->height; bmpfileheader[2] = (unsigned char)(filesize); bmpfileheader[3] = (unsigned char)(filesize >> 8); bmpfileheader[4] = (unsigned char)(filesize >> 16); bmpfileheader[5] = (unsigned char)(filesize >> 24); bmpinfoheader[4] = (unsigned char)(bmp->width); bmpinfoheader[5] = (unsigned char)(bmp->width >> 8); bmpinfoheader[6] = (unsigned char)(bmp->width >> 16); bmpinfoheader[7] = (unsigned char)(bmp->width >> 24); bmpinfoheader[8] = (unsigned char)(bmp->height); bmpinfoheader[9] = (unsigned char)(bmp->height >> 8); bmpinfoheader[10] = (unsigned char)(bmp->height >> 16); bmpinfoheader[11] = (unsigned char)(bmp->height >> 24); f = fopen(filename, "wb"); fwrite(bmpfileheader, 1, 14, f); fwrite(bmpinfoheader, 1, 40, f); for (int i = 0; i < bmp->height; i++) { fwrite(bmp->pixels + (bmp->width * (bmp->height - i - 1)), 3, bmp->width, f); } fclose(f); } void draw_pie_segment(Bitmap *bmp, double start_angle, double end_angle, Pixel color) { for (int y = 0; y < bmp->height; y++) { for (int x = 0; x < bmp->width; x++) { int dx = x - CENTER_X; int dy = y - CENTER_Y; double distance = sqrt(dx * dx + dy * dy); if (distance <= RADIUS) { double angle = -atan2(dy, dx) * 180 / M_PI - 90; if (angle < 0) angle += 360; if (angle >= start_angle && angle <= end_angle) { bmp->pixels[y * bmp->width + x] = color; } } } } } size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; MemoryBlock *mem = (MemoryBlock *)userp; char *ptr = realloc(mem->data, mem->size + realsize + 1); if(ptr == NULL) { // out of memory return 0; } mem->data = ptr; memcpy(&(mem->data[mem->size]), contents, realsize); mem->size += realsize; mem->data[mem->size] = 0; return realsize; } int fetch_data(const char *url, MemoryBlock *mem) { CURL *curl_handle; CURLcode res; mem->data = malloc(1); // will be grown as needed by realloc mem->size = 0; // no data at this point curl_global_init(CURL_GLOBAL_ALL); curl_handle = curl_easy_init(); curl_easy_setopt(curl_handle, CURLOPT_URL, url); curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)mem); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); res = curl_easy_perform(curl_handle); if(res != CURLE_OK) { fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); return 1; } curl_easy_cleanup(curl_handle); curl_global_cleanup(); return 0; } void parse_and_count_scores(const char *data, int score_count[8]) { const char *pos = data; pos = strstr(pos, "var resultList ="); if (pos == NULL) { fprintf(stderr, "Error: 'var resultList =' not found in data\n"); return; } // Move to the next line pos = strchr(pos, '\n'); if (pos == NULL) { return; } // Find all occurrences of "SCORE":"N" where N is a one-digit number (0-7) while ((pos = strstr(pos, "\"SCORE\":\"")) != NULL) { pos += 9; // Move past "\"SCORE\":\"" if (isdigit(*pos)) { int score = *pos - '0'; if (score >= 0 && score <= 7) { score_count[score]++; } } pos++; } } void print_usage() { printf("Usage: fin02 date\n"); printf("-h, --help Display this information and exit.\n"); } int main(int argc, char *argv[]) { if (argc != 2) { print_usage(); return EXIT_FAILURE; } if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { print_usage(); return EXIT_SUCCESS; } char url[256]; snprintf(url, sizeof(url), "https://cpe.cse.nsysu.edu.tw/cpe/scoreboard/%s", argv[1]); MemoryBlock mem; if (fetch_data(url, &mem) != 0) { fprintf(stderr, "Failed to fetch data from %s\n", url); return EXIT_FAILURE; } int score_count[8] = {0}; parse_and_count_scores(mem.data, score_count); free(mem.data); // Calculate total scores int total_scores = 0; for (int i = 0; i <= 7; i++) { total_scores += score_count[i]; } // Print the score counts and percentages for (int i = 0; i <= 7; i++) { if (total_scores > 0) { printf("%d: %d (%.2f%%)\n", i, score_count[i], (score_count[i] / (double)total_scores) * 100); } else { printf("%d: %d (0.00%%)\n", i, score_count[i]); } } // Create a bitmap for the pie chart Bitmap bmp; bmp.width = WIDTH; bmp.height = HEIGHT; bmp.pixels = (Pixel *)malloc(WIDTH * HEIGHT * sizeof(Pixel)); if (bmp.pixels == NULL) { perror("Error allocating memory for bitmap"); return EXIT_FAILURE; } // Fill the bitmap with white color for (int i = 0; i < WIDTH * HEIGHT; i++) { bmp.pixels[i].r = 255; bmp.pixels[i].g = 255; bmp.pixels[i].b = 255; } // Colors for the pie segments Pixel colors[8] = { {255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 0}, {0, 255, 255}, {255, 0, 255}, {128, 0, 0}, {0, 128, 0} }; // Draw the pie chart segments double start_angle = 0.0; for (int i = 0; i <= 7; i++) { if (score_count[i] > 0) { double end_angle = start_angle + (score_count[i] / (double)total_scores) * 360.0; draw_pie_segment(&bmp, start_angle, end_angle, colors[i]); start_angle = end_angle; } } // Save the bitmap to a file save_bmp("output.bmp", &bmp); // Free allocated memory free(bmp.pixels); return EXIT_SUCCESS; } ``` ::: 程式完成。 整個過程大概花了一小時完成,在圓餅圖修正上面花了一些時間重試。 可以發現需要人類對於程式碼與題目有一定認知才能提醒或指引 LLM 進行修正,其依然不能直接完成程式。