Skip to content

Instantly share code, notes, and snippets.

@langningchen
Created June 29, 2025 03:51
Show Gist options
  • Save langningchen/e75d7c0947af98cb0a930770f3a14029 to your computer and use it in GitHub Desktop.
Save langningchen/e75d7c0947af98cb0a930770f3a14029 to your computer and use it in GitHub Desktop.
Judge
#!/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