Shell输出成语列表用于填字游戏生成坐标

Published: Tags: SHELL

之前说到根据成语列表生成填字游戏的坐标,这次来说下成语列表需要如何得到,代码:

#!/bin/bash
# idiom_solitaire_sampler.sh by jtwo

idiom_number=${1:-5}  #默认成语个数
topic_number=${2:-10} #默认出题次数

SQL='mysql -uroot -h127.0.0.1 -D crossword -Ne'; export MYSQL_PWD=123456 #隐藏警告

LSR() { s="${1}"; echo "$[$s-1],${s},$[$s+1]"; } #左右索引 Left Self Right
RC() { echo "${1}" |tr ',' '\n' |shuf |head -1; } #随机选择逗号分隔列表 Random Choice
POP_RC() { echo "${1}" |tr ',' '\n' |grep -Ev $(echo "${2}" |tr ',' '|') |shuf |head -1; } #先POP再RC
RELK() { k="${1}"; v="${2}"; for x in {1..4}; do [[ "$x" -eq "$k" ]] && echo -n "$v" || echo -n "_"; done; } #匹配模式 Regex Like
PI() { $SQL "SELECT idiom FROM paraphrase WHERE idiom LIKE '${1}' AND idiom NOT IN ('${idiom_list}') ORDER BY RAND() LIMIT 1"; } #Pick Idiom

topic_finish=0 #记录成功出题数量(初值)
while [[ $topic_finish -lt $topic_number ]]; do
    debug_line="========================================"
    idiom_1st=$(PI "____") #首先随机生成一个成语
    idiom_list=${idiom_1st} #存放所有抽中成语的列表

    pos_1st_tail=$(RC 1,2,3,4) #随机选出成语中某字的索引
    pos_2nd_head=$(RC 1,2,3,4) #第二个成语的交叉点的位置

    case $pos_1st_tail in #获取第二个成语的尾部坐标
        1|4 ) pos_2nd_tail=$(POP_RC "1,2,3,4" "$pos_2nd_head") ;;
        2|3 ) pos_2nd_tail=$(POP_RC "1,2,3,4" $(LSR "$pos_2nd_head")) ;;
    esac

    pos_2nd_diff=$[$pos_2nd_tail-$pos_2nd_head] #是否紧邻(绝对值)、是否回拐(负数)
    if [[ ${pos_2nd_diff#-} -eq 1 ]]; then #头尾紧邻,前后成语头尾相接
        pos_3rd_head=$(echo 1,4 |tr ',' '\n' |grep -Ev "2|3|$pos_1st_tail")
    else
        pos_3rd_head=$(RC 1,2,3,4) #如果头尾不是紧邻,那就可以任选位置
    fi

    IFTF=1 #是否首次生成本题 Is First Time Flag
    idiom_finish=0 #记录成语个数(算法至少四个成语)
    while [[ $idiom_finish -le $idiom_number ]]; do
        case $pos_2nd_tail in #获取第三个成语的尾部坐标
            1|4 ) pos_3rd_tail=$(POP_RC "1,2,3,4" "$pos_3rd_head") ;;
            2|3 ) pos_3rd_tail=$(POP_RC "1,2,3,4" $(LSR "$pos_3rd_head")) ;;
        esac

        pos_3rd_diff=$[$pos_3rd_tail-$pos_3rd_head] #是否紧邻(绝对值)、是否回拐(负数)

        #【开始,特殊逻辑】第四个成语,根据回拐情况限制索引
        if [[ ${pos_3rd_diff} -lt 0 ]]; then #成语方向左拐(默认方向:右)
            pos_1st_diff=$[$pos_1st_tail-1]
        else
            pos_1st_diff=$[4-$pos_1st_tail]
        fi

        if [[ ${pos_1st_diff} -gt ${pos_3rd_diff#-} ]]; then
            pos_1st_diff=${pos_3rd_diff#-} #以最小间隔为边长
        fi

        if [[ ${pos_2nd_diff} -lt 0 ]]; then #成语方向上拐(默认方向:下)
            pos_4th_head_lv=$[ (${pos_3rd_diff#-}+${pos_2nd_diff#-}-${pos_1st_diff}-6) / (-1) ]
            [[ $pos_4th_head_lv -lt 1 ]] && pos_4th_head_lv=1 #限制坐标,可能为0(Limit Value)
            pos_4th_head_lv=$(printf "%s," $(seq $[$pos_4th_head_lv] 4) |sed 's/,\+$//g')
        else
            pos_4th_head_lv=$[ ${pos_3rd_diff#-}+${pos_2nd_diff#-}-${pos_1st_diff}-1 ]
            [[ $pos_4th_head_lv -gt 4 ]] && pos_4th_head_lv=4 #限制坐标,可能为5(Limit Value)
            pos_4th_head_lv=$(printf "%s," $(seq 1 $[$pos_4th_head_lv]) |sed 's/,\+$//g')
        fi
        #【结束,特殊逻辑】第四个成语,根据回拐情况限制索引

        if [[ ${pos_3rd_diff#-} -eq 1 ]]; then #头尾紧邻,前后成语头尾相接(1或4)
            pos_4th_head=$(echo $pos_4th_head_lv |tr ',' '\n' |grep -Ev "2|3|$pos_2nd_tail")
        else
            pos_4th_head=$(RC $pos_4th_head_lv) #如果头尾不是紧邻,那就可以任选位置
        fi

        if [[ $IFTF -eq 1 ]]; then #首次运行才需要处理"第1、2、3个"成语,并记录到输出列表
            word_1st_tail=${idiom_1st:$[${pos_1st_tail}-1]:1} #取出第一个成语的交接点的字,从零索引减一

            idiom_2nd=$(PI $(RELK $pos_2nd_head $word_1st_tail))
            [[ -z "$idiom_2nd" ]] && break || idiom_list="${idiom_list}','${idiom_2nd}"
            [[ $idiom_number -eq 2 ]] && echo $idiom_list |tr -d "'" && ((topic_finish++)) && break
            word_2nd_tail=${idiom_2nd:$[${pos_2nd_tail}-1]:1}

            idiom_3rd=$(PI $(RELK $pos_3rd_head $word_2nd_tail))
            [[ -z "$idiom_3rd" ]] && break || idiom_list="${idiom_list}','${idiom_3rd}"
            [[ $idiom_number -eq 3 ]] && echo $idiom_list |tr -d "'" && ((topic_finish++)) && break
            word_3rd_tail=${idiom_3rd:$[${pos_3rd_tail}-1]:1}
        fi

        idiom_4th=$(PI $(RELK $pos_4th_head $word_3rd_tail)); [[ -z "$idiom_4th" ]] && break
        idiom_list="${idiom_list}','${idiom_4th}"

        idiom_finish=$[$(echo $idiom_list |grep -Eo , |wc -l) + 1] #接龙的长度
        [[ $idiom_finish -eq $idiom_number ]] && echo $idiom_list |tr -d "'" && ((topic_finish++)) && break

        if [[ $idiom_number -ge 5 ]]; then
            idiom_1st=$idiom_2nd #更新冗余数据
            idiom_2nd=$idiom_3rd
            idiom_3rd=$idiom_4th

            pos_1st_tail=$pos_2nd_tail #交替坐标
            pos_2nd_head=$pos_3rd_head
            pos_2nd_tail=$pos_3rd_tail
            pos_3rd_head=$pos_4th_head

            pos_2nd_diff=$[$pos_2nd_tail-$pos_2nd_head] #因为在循环之外所以需要更新
            word_3rd_tail=${idiom_3rd:$[${pos_3rd_tail}-1]:1} #需要接上"第四个"成语
            IFTF=0 #不是首次运行,后续只需处理"第四个"成语(任意非1值均可,仅作判断)
        fi
    done
done

执行命令:

echo -e '3 3\n4 4\n5 5' |while read x y; do bash idiom_solitaire_sampler.sh $x $y; done

输出效果:

杀身报国,感恩图报,感旧之哀
议论风生,风栉雨沐,础润而雨
桀犬吠尧,蜀犬吠日,日月经天
负薪之忧,牛之一毛,颠毛种种,谬种流传
冰炭不投,投鞭断流,流金铄石,金貂换酒
子为父隐,言为心声,声动梁尘,动辄得咎
黑白混淆,论黄数黑,笃而论之,笃近举远
窥间伺隙,管窥蠡测,持蠡测海,扶颠持危,两瞽相扶
贪夫徇财,夫子自道,天道好还,天命有归,海阔天高
髀肉复生,慢易生忧,忧心如焚,如日方中,外方内圆
贵古贱今,年逾古稀,年高德劭,爱人以德,以古为镜
怨天怨地,以怨报德,酒后无德,后生小子,福过灾生