Created
June 29, 2025 03:51
-
-
Save langningchen/e75d7c0947af98cb0a930770f3a14029 to your computer and use it in GitHub Desktop.
Judge
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env fish | |
# ===== 配置区域 ===== | |
# 测试配置 | |
set TEST_ROUNDS 1000 # 每个题目运行的测试次数 | |
# 编译配置 | |
set COMPILE_FLAGS "-std=c++17" "-O2" "-Wall" "-Wextra" # 编译选项 | |
set COMPILE_TIMEOUT 30 # 编译超时时间(秒) | |
set ENABLE_DEBUG_INFO false # 是否启用调试信息 (-g) | |
set ENABLE_SANITIZER false # 是否启用地址检查器 (-fsanitize=address,undefined) | |
# 运行时配置 | |
set SOLUTION_TIMEOUT 1 # 解决方案运行超时时间(秒) | |
set BRUTEFORCE_TIMEOUT 10 # 暴力解法运行超时时间(秒) | |
set GENERATOR_TIMEOUT 5 # 数据生成器运行超时时间(秒) | |
# 输出配置 | |
set SHOW_PROGRESS true # 是否显示进度信息 | |
set SHOW_ROUND_INFO true # 是否显示轮次信息 | |
set ANIMATION_SPEED 0.1 # 动画刷新间隔(秒) | |
set PROGRESS_BAR_MIN_WIDTH 15 # 进度条最小宽度(字符数) | |
set PROGRESS_BAR_MAX_WIDTH 50 # 进度条最大宽度(字符数) | |
# 文件配置 | |
set INPUT_FILE_EXT ".in" # 输入文件扩展名 | |
set OUTPUT_FILE_EXT ".out" # 输出文件扩展名 | |
set ANSWER_FILE_EXT ".ans" # 答案文件扩展名 | |
set CLEANUP_FILES true # 是否自动清理临时文件 | |
# 错误数据保存配置 | |
set SAVE_ERROR_DATA true # 是否保存错误的测试数据 | |
set ERROR_DATA_DIR "error_data" # 错误数据保存目录 | |
set MAX_ERROR_CASES 10 # 每个问题最多保存多少个错误用例 | |
# 颜色配置 | |
set COLOR_AC "\033[32m" | |
set COLOR_SE "\033[30m" | |
set COLOR_RE "\033[34m" | |
set COLOR_CE "\033[33m" | |
set COLOR_TLE "\033[35m" | |
set COLOR_WA "\033[31m" | |
set COLOR_PE "\033[95m" | |
set COLOR_PC "\033[96m" | |
set COLOR_RESET "\033[0m" | |
set COLOR_PROCESSING "\033[36m" | |
set COLOR_RUNNING "\033[93m" | |
set BOLD "\033[1m" | |
set UNDERLINE "\033[4m" | |
set ITALIC "\033[3m" | |
# 全局变量 | |
set -g TEMP_FILES # 用于记录需要清理的临时文件 | |
# 清理函数 | |
function cleanup_temp_files | |
if test $CLEANUP_FILES = true | |
for file in $TEMP_FILES | |
if test -f "$file" | |
rm -f "$file" 2>/dev/null | |
end | |
end | |
end | |
set -e TEMP_FILES | |
end | |
# 信号处理函数 | |
function handle_interrupt | |
echo -e "\n\033[31m[INTERRUPTED]\033[0m Cleaning up..." | |
cleanup_temp_files | |
exit 130 | |
end | |
# 设置信号处理器 | |
trap handle_interrupt SIGINT SIGTERM | |
# 创建错误数据目录 | |
function create_error_data_dir | |
set -l problem $argv[1] | |
if test $SAVE_ERROR_DATA = true | |
set -l error_dir "$ERROR_DATA_DIR/$problem" | |
if not test -d "$error_dir" | |
mkdir -p "$error_dir" 2>/dev/null | |
end | |
echo $error_dir | |
end | |
end | |
# 保存错误测试数据 | |
function save_error_data | |
set -l problem $argv[1] | |
set -l round $argv[2] | |
set -l error_status $argv[3] | |
set -l input_file $argv[4] | |
set -l output_file $argv[5] | |
set -l answer_file $argv[6] | |
if test $SAVE_ERROR_DATA != true | |
return | |
end | |
set -l error_dir (create_error_data_dir $problem) | |
if test -z "$error_dir" | |
return | |
end | |
# 计算当前错误用例数量 | |
set -l case_count (ls "$error_dir" 2>/dev/null | grep "^case_" | wc -l) | |
# 检查是否已经保存了足够多的错误用例 | |
if test $case_count -ge $MAX_ERROR_CASES | |
return | |
end | |
set -l case_num (math $case_count + 1) | |
# 创建用例目录 | |
set -l case_dir "$error_dir/case_$case_num" | |
mkdir -p "$case_dir" 2>/dev/null | |
# 保存输入文件 | |
if test -f "$input_file" | |
cp "$input_file" "$case_dir/input.txt" | |
end | |
# 保存输出文件(如果存在) | |
if test -f "$output_file" | |
cp "$output_file" "$case_dir/output.txt" | |
end | |
# 保存期望答案(如果存在) | |
if test -f "$answer_file" | |
cp "$answer_file" "$case_dir/expected.txt" | |
end | |
# 创建状态文件 | |
echo "Round: $round" > "$case_dir/info.txt" | |
echo "Status: $error_status" >> "$case_dir/info.txt" | |
echo "Timestamp: "(date) >> "$case_dir/info.txt" | |
# 如果是WA,添加diff信息 | |
if test "$error_status" = "WA"; and test -f "$output_file"; and test -f "$answer_file" | |
echo "=== Diff ===" >> "$case_dir/info.txt" | |
diff "$answer_file" "$output_file" >> "$case_dir/info.txt" 2>/dev/null; or echo "Files differ significantly" >> "$case_dir/info.txt" | |
end | |
end | |
# 根据配置构建编译命令 | |
function build_compile_command | |
set -l flags $COMPILE_FLAGS | |
if test $ENABLE_DEBUG_INFO = true | |
set flags $flags "-g" | |
end | |
if test $ENABLE_SANITIZER = true | |
set flags $flags "-fsanitize=address,undefined" | |
end | |
# 直接返回数组,不使用 echo | |
printf '%s\n' $flags | |
end | |
# 获取动态进度条宽度 | |
function get_progress_bar_width | |
# 获取终端宽度 | |
set -l term_width (tput cols 2>/dev/null; or echo 80) | |
# 计算可用空间:终端宽度 - 问题名 - 状态文本 - 百分比 - 其他字符 | |
# 预留大约 60 个字符给其他内容 | |
set -l available_width (math "$term_width - 60") | |
# 限制在最小和最大宽度之间 | |
if test $available_width -lt $PROGRESS_BAR_MIN_WIDTH | |
echo $PROGRESS_BAR_MIN_WIDTH | |
else if test $available_width -gt $PROGRESS_BAR_MAX_WIDTH | |
echo $PROGRESS_BAR_MAX_WIDTH | |
else | |
echo $available_width | |
end | |
end | |
# 生成进度条 | |
function generate_progress_bar | |
set -l current $argv[1] | |
set -l total $argv[2] | |
set -l width (get_progress_bar_width) | |
set -l percentage (math "round($current * 100 / $total)") | |
# 计算精确的填充位置(8个级别) | |
set -l exact_fill (math "$current * $width * 8 / $total") | |
set -l full_blocks (math "floor($exact_fill / 8)") | |
set -l remainder (math "floor($exact_fill % 8)") | |
# 八分之一矩形字符 | |
set -l chars " " "▏" "▎" "▍" "▌" "▋" "▊" "▉" "█" | |
set -l bar "|" | |
set -l used_blocks $full_blocks # 初始化已使用块数 | |
# 添加完全填充的块 | |
for i in (seq 1 $full_blocks) | |
set bar "$bar█" | |
end | |
# 添加部分填充的块(如果有余数且没有超出宽度) | |
if test $remainder -gt 0; and test $full_blocks -lt $width | |
set bar "$bar"(echo $chars[(math $remainder + 1)]) | |
set used_blocks (math $full_blocks + 1) | |
end | |
# 添加空白块(确保总长度等于width) | |
set -l empty_blocks (math "$width - $used_blocks") | |
if test $empty_blocks -gt 0 | |
for i in (seq 1 $empty_blocks) | |
set bar "$bar " | |
end | |
end | |
set bar "$bar| $percentage%" | |
echo $bar | |
end | |
function print_status | |
set -l problem $argv[1] | |
set -l status_text $argv[2] | |
set -l color $argv[3] | |
if test $SHOW_PROGRESS = true | |
echo -n -e "\r\033[K$BOLD$problem$COLOR_RESET $color$UNDERLINE$status_text$COLOR_RESET" | |
end | |
end | |
function print_status_with_progress | |
set -l problem $argv[1] | |
set -l progress_bar $argv[2] | |
set -l round_info $argv[3] | |
set -l status_text $argv[4] | |
set -l color $argv[5] | |
if test $SHOW_PROGRESS = true | |
echo -n -e "\r\033[K$BOLD$problem$COLOR_RESET $progress_bar $round_info: $color$UNDERLINE$status_text$COLOR_RESET" | |
end | |
end | |
function print_result | |
set -l problem $argv[1] | |
set -l result $argv[2] | |
set -l message $argv[3] | |
set -l color | |
switch $result | |
case "AC" | |
set color $COLOR_AC | |
case "SE" | |
set color $COLOR_SE | |
case "RE" | |
set color $COLOR_RE | |
case "CE" | |
set color $COLOR_CE | |
case "TLE" | |
set color $COLOR_TLE | |
case "WA" | |
set color $COLOR_WA | |
case "PE" | |
set color $COLOR_PE | |
case "PC" | |
set color $COLOR_PC | |
end | |
echo -e "\r\033[K$BOLD$problem$COLOR_RESET $color$result$COLOR_RESET $UNDERLINE$message$COLOR_RESET" | |
end | |
function compile_file | |
set -l source $argv[1] | |
set -l output $argv[2] | |
set -l problem $argv[3] | |
print_status $problem "Compiling $(basename $source)..." $COLOR_PROCESSING | |
# 构建编译命令 | |
set -l compile_flags (build_compile_command) | |
# 创建临时错误文件 | |
set -l error_file "/tmp/judge_compile_error_$problem.log" | |
set -g TEMP_FILES $TEMP_FILES $error_file | |
# 启动后台编译进程 | |
timeout $COMPILE_TIMEOUT"s" g++ -o $output $source $compile_flags 2>$error_file & | |
set -l compile_pid $last_pid | |
# 盲文点动画 | |
set -l frames "⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏" | |
set -l frame_count 1 | |
while kill -0 $compile_pid 2>/dev/null | |
print_status $problem "Compiling $(basename $source)... "(echo $frames[$frame_count]) $COLOR_PROCESSING | |
sleep $ANIMATION_SPEED | |
set frame_count (math $frame_count % 10 + 1) | |
end | |
# 等待进程结束并获取退出状态 | |
wait $compile_pid | |
# 如果没有输出文件,也就是编译失败,显示错误信息 | |
if not test -f $output | |
if test -s $error_file | |
echo -e "\r\033[K$ITALIC$COLOR_CE""[Compile $source Error]$COLOR_RESET" | |
head -n 5 $error_file 2>/dev/null | sed 's/^/ /' | |
end | |
return 1 | |
end | |
return 0 | |
end | |
# 格式化统计结果 | |
function format_test_results | |
set -l ac_count $argv[1] | |
set -l wa_count $argv[2] | |
set -l pe_count $argv[3] | |
set -l tle_count $argv[4] | |
set -l re_count $argv[5] | |
set -l se_count $argv[6] | |
set -l total $argv[7] | |
set -l result_summary "AC: $ac_count/$total" | |
if test $wa_count -gt 0 | |
set result_summary "$result_summary, WA: $wa_count" | |
end | |
if test $pe_count -gt 0 | |
set result_summary "$result_summary, PE: $pe_count" | |
end | |
if test $tle_count -gt 0 | |
set result_summary "$result_summary, TLE: $tle_count" | |
end | |
if test $re_count -gt 0 | |
set result_summary "$result_summary, RE: $re_count" | |
end | |
if test $se_count -gt 0 | |
set result_summary "$result_summary, SE: $se_count" | |
end | |
echo $result_summary | |
end | |
# 判断测试结果状态 | |
function determine_result_status | |
set -l ac_count $argv[1] | |
set -l wa_count $argv[2] | |
set -l pe_count $argv[3] | |
set -l tle_count $argv[4] | |
set -l re_count $argv[5] | |
set -l se_count $argv[6] | |
set -l total $argv[7] | |
if test $ac_count -eq $total | |
echo "AC" | |
else if test $se_count -gt 0 | |
echo "SE" | |
else if test $ac_count -gt 0 | |
echo "PC" # 部分正确 | |
else | |
echo "WA" # 全部错误 | |
end | |
end | |
function run_with_timeout | |
set -l cmd $argv[1] | |
set -l timeout_sec $argv[2] | |
set -l input_file $argv[3] | |
set -l output_file $argv[4] | |
if test -n "$input_file" | |
timeout $timeout_sec"s" $cmd < $input_file > $output_file 2>/dev/null | |
else | |
timeout $timeout_sec"s" $cmd > $output_file 2>/dev/null | |
end | |
set -l exit_code $status | |
if test $exit_code -eq 124 | |
return 124 # timeout | |
else if test $exit_code -ne 0 | |
return 1 # runtime error | |
else | |
return 0 # success | |
end | |
end | |
function judge_problem | |
set -l problem $argv[1] | |
if not test -f "$problem.cpp" | |
return | |
end | |
# 统计多轮测试结果 | |
set -l ac_count 0 | |
set -l wa_count 0 | |
set -l pe_count 0 | |
set -l tle_count 0 | |
set -l re_count 0 | |
set -l se_count 0 | |
set -l ce_count 0 | |
print_status $problem "Checking files..." $COLOR_PROCESSING | |
# 检查并编译生成器 | |
if test -f "$problem.gen.cpp" | |
if not compile_file "$problem.gen.cpp" "$problem.gen" $problem | |
print_result $problem "SE" "Failed to compile generator" | |
cleanup_temp_files | |
return | |
end | |
set -g TEMP_FILES $TEMP_FILES "$problem.gen" | |
else | |
print_result $problem "SE" "Generator not found" | |
cleanup_temp_files | |
return | |
end | |
# 检查并编译暴力解法 | |
if test -f "$problem.bf.cpp" | |
if not compile_file "$problem.bf.cpp" "$problem.bf" $problem | |
print_result $problem "SE" "Failed to compile brute force solution" | |
cleanup_temp_files | |
return | |
end | |
set -g TEMP_FILES $TEMP_FILES "$problem.bf" | |
else | |
print_result $problem "SE" "Brute force solution not found" | |
cleanup_temp_files | |
return | |
end | |
# 编译主解决方案 | |
if not compile_file "$problem.cpp" "$problem" $problem | |
print_result $problem "CE" "" | |
cleanup_temp_files | |
return | |
end | |
set -g TEMP_FILES $TEMP_FILES "$problem" | |
# 进行多轮测试 | |
for round in (seq 1 $TEST_ROUNDS) | |
set -l progress_bar (generate_progress_bar $round $TEST_ROUNDS) | |
# 生成测试数据 | |
if test $SHOW_ROUND_INFO = true | |
print_status_with_progress $problem "$progress_bar" "Round $round/$TEST_ROUNDS" "Generating input..." $COLOR_RUNNING | |
else | |
print_status_with_progress $problem "$progress_bar" "($round/$TEST_ROUNDS)" "Testing..." $COLOR_RUNNING | |
end | |
set -l input_file "$problem$INPUT_FILE_EXT" | |
set -g TEMP_FILES $TEMP_FILES $input_file | |
if not run_with_timeout "./$problem.gen" $GENERATOR_TIMEOUT "" $input_file | |
set se_count (math $se_count + 1) | |
save_error_data $problem $round "SE" $input_file "" "" | |
continue | |
end | |
# 运行解决方案 | |
if test $SHOW_ROUND_INFO = true | |
print_status_with_progress $problem "$progress_bar" "Round $round/$TEST_ROUNDS" "Running solution..." $COLOR_RUNNING | |
end | |
set -l output_file "$problem$OUTPUT_FILE_EXT" | |
set -g TEMP_FILES $TEMP_FILES $output_file | |
run_with_timeout "./$problem" $SOLUTION_TIMEOUT $input_file $output_file | |
set -l exit_code $status | |
if test $exit_code -eq 124 | |
set tle_count (math $tle_count + 1) | |
save_error_data $problem $round "TLE" $input_file $output_file "" | |
continue | |
else if test $exit_code -ne 0 | |
set re_count (math $re_count + 1) | |
save_error_data $problem $round "RE" $input_file $output_file "" | |
continue | |
end | |
# 运行暴力解法生成答案 | |
if test $SHOW_ROUND_INFO = true | |
print_status_with_progress $problem "$progress_bar" "Round $round/$TEST_ROUNDS" "Running brute force..." $COLOR_RUNNING | |
end | |
set -l answer_file "$problem$ANSWER_FILE_EXT" | |
set -g TEMP_FILES $TEMP_FILES $answer_file | |
if not run_with_timeout "./$problem.bf" $BRUTEFORCE_TIMEOUT $input_file $answer_file | |
set se_count (math $se_count + 1) | |
save_error_data $problem $round "SE" $input_file $output_file "" | |
continue | |
end | |
# 比较输出 | |
if test $SHOW_ROUND_INFO = true | |
print_status_with_progress $problem "$progress_bar" "Round $round/$TEST_ROUNDS" "Comparing outputs..." $COLOR_PROCESSING | |
end | |
if not test -f $answer_file | |
set se_count (math $se_count + 1) | |
save_error_data $problem $round "SE" $input_file $output_file "" | |
continue | |
end | |
if not test -f $output_file | |
set wa_count (math $wa_count + 1) | |
save_error_data $problem $round "WA" $input_file "" $answer_file | |
continue | |
end | |
set -l content1 (cat $output_file | string trim) | |
set -l content2 (cat $answer_file | string trim) | |
if test "$content1" = "$content2" | |
set ac_count (math $ac_count + 1) | |
continue | |
end | |
# 尝试忽略空白字符差异 | |
set -l normalized1 (echo $content1 | tr -d '[:space:]') | |
set -l normalized2 (echo $content2 | tr -d '[:space:]') | |
if test "$normalized1" = "$normalized2" | |
set pe_count (math $pe_count + 1) | |
save_error_data $problem $round "PE" $input_file $output_file $answer_file | |
continue | |
end | |
set wa_count (math $wa_count + 1) | |
save_error_data $problem $round "WA" $input_file $output_file $answer_file | |
end | |
# 输出最终结果 | |
set -l result_status (determine_result_status $ac_count $wa_count $pe_count $tle_count $re_count $se_count $TEST_ROUNDS) | |
set -l result_summary (format_test_results $ac_count $wa_count $pe_count $tle_count $re_count $se_count $TEST_ROUNDS) | |
if test $result_status = "AC" | |
print_result $problem "AC" "All $TEST_ROUNDS tests passed" | |
else if test $result_status = "CE" | |
print_result $problem "CE" "" | |
else | |
set -l error_info "" | |
if test $SAVE_ERROR_DATA = true | |
set -l error_dir "$ERROR_DATA_DIR/$problem" | |
if test -d "$error_dir" | |
set -l saved_cases (ls "$error_dir" 2>/dev/null | grep "^case_" | wc -l) | |
if test $saved_cases -gt 0 | |
set error_info " (Saved $saved_cases error cases to $error_dir)" | |
end | |
end | |
end | |
print_result $problem $result_status "$result_summary$error_info" | |
end | |
# 清理当前问题的临时文件 | |
cleanup_temp_files | |
end | |
# 主程序入口 | |
if test (count $argv) -eq 0 | |
for cpp_file in *.cpp | |
set -l basename (basename $cpp_file .cpp) | |
if not string match -q "*.gen" $basename; and not string match -q "*.bf" $basename | |
judge_problem $basename | |
end | |
end | |
else | |
for problem in $argv | |
judge_problem $problem | |
end | |
end | |
# 最终清理 | |
cleanup_temp_files |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment