# 2023 程式設計 考試抄襲分析
## 如何抓抄襲?
我們採用史丹佛大學開發的 MOSS(https://theory.stanford.edu/~aiken/moss/),這是一套用來專門分析程式碼抄襲的工具。此工具會檢測同學之間的程式碼相似程度,且此工具在更換程式順序、變數名稱下仍可以正確檢測。
然而,此套工具也明確說明「僅供參考」,因此仍需要由助教進行二次確認。但相比一個個程式點開來檢查,節省下非常多的時間。
## 抄襲檢測流程
1. 將學生的程式碼皆上傳至 MOSS
2. 若該次考試有提供範例程式,將此範例程式設為排除項目上傳至 MOSS
3. MOSS 產生結果網頁,如下圖:
<!--  -->

在結果報告中,會顯示不同色塊,代表相似性高的片段


4. 由助教群討論檢視 MOSS 較前面的結果,若助教群多數認為有抄襲嫌疑,將抄襲的檢測結果與抄襲判斷交給老師做最後決定,如下圖:
<!--  -->

## 抄襲情形
### 程設一期中考 共 2 組
- 有發現有一組人抄襲網路上相同的程式碼
- 一組程式邏輯非常相似
### 程設一期末考 共 6 組
- 在計算分數與踩地雷等兩題,發現多組程式邏輯相似,變數命名也相似的程式碼。但因為題目較容易,程式碼行數短,故沒有認定為嚴重抄襲


### 程設二期中考 共 0 組
### 程設二期末考 共 1 組
- 在核心函式上完全相同,但最後兩位同學在該題獲得分數差異甚大,分別為 5 分與 25 分

## 現今大型語言模型之解題能力
隨著大型語言模型(LLM)變得普及,我們預期會有越來越多的同學使用 LLM 輔助進行解題,其中 LLM 對於複雜題目的直接解題能力,將直接影響到 LLM 對於輔助行為的涉入程度(僅從語法補全、函數實作,乃至於直接依照指令完成整份程式)。
此次,助教使用目前最廣泛被一般大眾使用的 LLM 服務 OpenAI ChatGPT (GPT-4o) 嘗試在不對 LLM 生成之程式碼進行修改的情況下,以自然語言指令的方式引導其完成程設二期末考之其中一題。



此題涉及:
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 進行修正,其依然不能直接完成程式。