代码(snake.sh):
- Perl code
#!/usr/bin/ksh############################################################################## 程序名: snake.sh ## 时 间: 2011.03.03 ## 作 者: hiroyukki ## 用 途: 贪食蛇游戏 ############################################################################### 从 ini 文件里获取配置项function getProperty { local INIFILE=$1 local KEY=$2 grep "${KEY}" ${INIFILE} | awk -F= '{print $2}'}# 读取程序配置function getConfig { if [ ! -f "${CFGFILE}" ] then echo "未找到配置文件: ${CFGFILE}" if [ -f "${SINGLEFILE}" ] then rm -f ${SINGLEFILE} fi return -1 fi getProperty ${CFGFILE} "bodyColor" | read color if [ -z "${color}" ] then color=BLACK fi case ${color} in RED) BDCOLOR=31;; GREEN) BDCOLOR=32;; BLACK) BDCOLOR=30;; YELLOW) BDCOLOR=33;; CYAN) BDCOLOR=36;; *) echo "Invalid color: ${color}" return -1 esac getProperty ${CFGFILE} "foodColor" | read color if [ -z "${color}" ] then color=BLACK fi case ${color} in RED) FDCOLOR=31;; GREEN) FDCOLOR=32;; BLACK) FDCOLOR=30;; YELLOW) FDCOLOR=33;; CYAN) FDCOLOR=36;; *) echo "Invalid color: ${color}" return -1 esac return 0}# 移动到目标坐标并输出第三个参数function moveTo { echo "[$1;$2H$3"}# 该函数接受3个参数,第一个是下限,第二个是上限,第三个传1生成奇数function random { # 如果参数少于3个,返回0 if [ $# -lt 3 ] then return 0 fi curSec=`date "+%M%S" | cut -c3-4` curMin=`date "+%M%S" | cut -c1-2` tmp=`expr $2 - $1` sec=`expr ${curMin} \* 60 + ${curSec}` result=`expr ${sec} % ${tmp}` result=`expr ${result} + $1 + 1` # 如果参数3传的是1,则生成奇数 if [ $3 -eq "1" ] then tmp=`expr ${result} % 2` if [ ${tmp} -eq 0 ] then result=`expr ${result} - 1` fi fi echo $result}# 绘制墙壁function drawWall { moveTo 1 1 "■■■■■■■■■■■■■■■■" for i in 2 3 4 5 6 7 8 9 10 11 12 13 14 15 do moveTo ${i} 1 "■ ■" done moveTo 16 1 "■■■■■■■■■■■■■■■■"}# 输出蛇身function drawBody { moveTo $1 $2 "[${BDCOLOR}m■[40m[0m"}# 画食物function drawFood { moveTo $1 $2 "[${FDCOLOR}m■[40m[0m"}# 绘制分数function drawScore { local SCORE=$1 moveTo 18 13 "[41m得分:${SCORE}[40m[0m"}# 绘制死亡通知,这里不用再取SCORE了,能执行至此,前面肯定取过至少一次# 死亡,其实名字起的不对,应该是结束,包含胜利结束和撞墙(撞自己)结束# 传参数1表示死亡,0表示胜利function drawDie { local REASON=$1 if [ "${REASON}" = "0" ] then moveTo 18 6 "[41m您胜利了!您的得分:${SCORE}[40m[0m" return fi moveTo 18 6 "[41m游戏结束,您的得分:${SCORE}[40m[0m"}# 退出时通知子进程退出function onExit { echo "exit" > ${CTLFILE} if [ -f "${TMPFILE}" ] then rm -f ${TMPFILE} > /dev/null fi if [ -f "${DRCTFILE}" ] then rm -f ${DRCTFILE} > /dev/null fi if [ -f "${SINGLEFILE}" ] then rm -f ${SINGLEFILE} > /dev/null fi # 让子进程退出的权宜之计…… # 似乎不需要,虽然子进程是后台,但是他爹在活着,杀他爹时他也死? # 就是说子进程根本来不及清理,因为他没捕获信号,没关系,他爹死前清理就行了 # 事实上,只有用户正常退出(输入Q)后,才会由子进程清理控制文件 echo "等待子进程退出..." sleep 2 if [ -f "${CTLFILE}" ] then rm -f ${CTLFILE} > /dev/null fi clear exit 0}# 吃掉一个食物,吃的时候比较简单,就是把食物标志变成身体标志而已,但要放在其他所有# 身体结点前,因为食物的位置已经是新的头了function eat { grep "score" ${CTLFILE} | awk -F':' '{print $2}' | read SCORE if [ -z "${SCORE}" ] then SCORE=0 fi SCORE=`expr ${SCORE} + 10` grep "food" ${CTLFILE} > ${TMPFILE} grep "body" ${CTLFILE} >> ${TMPFILE} echo "score:${SCORE}" >> ${TMPFILE} sed 's/food/body/g' ${TMPFILE} > ${CTLFILE} nextFood}# 生成下一个食物,没考虑生成到蛇身上的情况,管它呢function nextFood { FOODX=`random 2 15 0` FOODY=`random 2 28 1` grep -v "food" ${CTLFILE} > ${TMPFILE} echo "food:${FOODX}:${FOODY}" >> ${TMPFILE} cp ${TMPFILE} ${CTLFILE}}# 是否在下一步能吃到食物,返回0表示能,返回1表示正常,# 返回2表示我被自己撞了一下腰或我被石头撞了一下头function isPieFromSky { # 取当前蛇头位置,根据方向计算出下一个要走的位置 HEADX=`grep "body" ${CTLFILE} | head -n1 | awk -F':' '{print $2}'` HEADY=`grep "body" ${CTLFILE} | head -n1 | awk -F':' '{print $3}'` NEXTX=${HEADX} NEXTY=${HEADY} DIRECTION=`cat ${DRCTFILE}` if [ "${DIRECTION}" = "${UP}" ] then NEXTX=`expr ${NEXTX} - 1` elif [ "${DIRECTION}" = "${LEFT}" ] then NEXTY=`expr ${NEXTY} - 2` elif [ "${DIRECTION}" = "${DOWN}" ] then NEXTX=`expr ${NEXTX} + 1` elif [ "${DIRECTION}" = "${RIGHT}" ] then NEXTY=`expr ${NEXTY} + 2` fi # X Y都不能越墙,否则是撞 if [ ${NEXTX} -gt 15 -o ${NEXTX} -lt 2 -o ${NEXTY} -gt 30 -o ${NEXTY} -lt 2 ] then return 2 fi # 撞自己了 LINES=`grep "body:${NEXTX}:${NEXTY}" ${CTLFILE} | wc | awk '{print $1}'` if [ ${LINES} -ne 0 ] then return 2 fi FOODX=`grep "food" ${CTLFILE} | awk -F':' '{print $2}'` FOODY=`grep "food" ${CTLFILE} | awk -F':' '{print $3}'` if [ "${NEXTX}" = "${FOODX}" -a "${NEXTY}" = "${FOODY}" ] then return 0 fi return 1}# 生成蛇头,初始化调用function genHead { grep -v "body" ${CTLFILE} > ${TMPFILE} echo "body:2:3" >> ${TMPFILE} cp ${TMPFILE} ${CTLFILE}}# 蛇移动,所谓蛇移动,其实是把蛇尾放到蛇头的下一个位置如有10个身体结点# 则前9个不动,第十个放到第一个的下一个位置,这个位置和方向有关系# 如此,至少在视觉上,他在移动……# 这里有个取巧的地方,已知只有在判断是否天上掉馅饼后才做此调用,故可用天上掉馅饼# 函数中已经得到的数据,如 NEXTXfunction goGoGo { # 先获取身体结点个数 LINES=`grep "body" ${CTLFILE} | wc | awk '{print $1}'` # 直接生成一个新的放前面 echo "body:${NEXTX}:${NEXTY}" > ${TMPFILE} HEADLINE=`expr ${LINES} - 1` grep "body" ${CTLFILE} | head -n${HEADLINE} >> ${TMPFILE} grep -v "body" ${CTLFILE} >> ${TMPFILE} cp ${TMPFILE} ${CTLFILE}}# 死的时候,杀自己爹,伦理丧失啊,通过杀自己爹,让自己爹清理部分东西,# 并且自己爹临死时会给自己交待后事# 我好像理解错误,对函数使用 & 这个函数还是在同一个进程里?也就是说父的说法不成立?# 其实应该 kill ${PID} 而不是 kill ${PPID}?# 在AIX下, 两者竟然都通过, redhat下,两者都不能成功杀# 如果是同一个进程,意思是说,exit就行了?function die { drawDie $1 kill -2 $$}# 分数过200就算赢了,赢了程序退出,同样杀爹……function win { die 0}# 这是独立的一个进程,用来检测当前游戏状态并绘制function moveThread { # 先生成下一个食物 nextFood # 生成蛇头 genHead THREADID=`echo $$` echo "${RIGHT}" > ${DRCTFILE} while true do # 先查看是否有退出标志 grep "exit" ${CTLFILE} | wc | read LINE DISCARD if [ "${LINE}" = "1" ] then rm -f ${CTLFILE} > /dev/null return fi # 画上墙 drawWall # 画分数 SCORE=`grep "score" ${CTLFILE} | awk -F':' '{print $2}'` if [ -z "${SCORE}" ] then SCORE=0 fi drawScore ${SCORE} if [ ${SCORE} -gt 190 ] then win fi # 画食物 X=`grep "food" ${CTLFILE} | awk -F':' '{print $2}'` Y=`grep "food" ${CTLFILE} | awk -F':' '{print $3}'` if [ -z "${X}" -o -z "${Y}" ] then echo "Food not found!" die continue fi drawFood ${X} ${Y} # 如果撞上食物,吃!,如果撞上墙,死!否则正常走 isPieFromSky RETVAL=$? if [ ${RETVAL} -eq 0 ] then eat elif [ ${RETVAL} -eq 2 ] then die else goGoGo fi # 没死的话,画身体吧 grep "body" ${CTLFILE} | awk -F':' '{print $2" "$3}' | while read X Y do drawBody ${X} ${Y} done sleep 1 done}# 获取当前蛇的前进方向function getCurDrct { cat ${DRCTFILE} | read DIRECTION if [ -z "${DIRECTION}" ] then DIRECTION=0 fi return ${DIRECTION}}# 游戏开始function gameStart { clear touch ${CFGFILE} ${CTLFILE} ${DRCTFILE} moveThread & # 循环读用户输入来改变蛇的方向,有个需要注意的是,比如,当前方向是右,则不能直接转向左 while true do read INPUT case ${INPUT} in a|A) getCurDrct if [ $? -ne ${RIGHT} ] then echo "${LEFT}" > ${DRCTFILE} fi ;; s|S) getCurDrct if [ $? -ne ${UP} ] then echo "${DOWN}" > ${DRCTFILE} fi ;; d|D) getCurDrct if [ $? -ne ${LEFT} ] then echo "${RIGHT}" > ${DRCTFILE} fi ;; w|W) getCurDrct if [ $? -ne ${DOWN} ] then echo "${UP}" > ${DRCTFILE} fi ;; q|Q) onExit;; *) continue;; esac done return 0}SINGLEFILE=${HOME}/.snake.flagif [ -f "${SINGLEFILE}" ]then echo "已经有一个实例在运行!" exit 0fitouch ${SINGLEFILE}MAXX=8MAXY=17LEFT=1DOWN=2RIGHT=3UP=4CFGFILE=snake.iniTMPFILE=.snake.tmpCTLFILE=.snake.ctlDRCTFILE=.snake.drctUSER=`whoami`trap "onExit" 2 3getConfig[ $? -ne 0 ] && exit 0gameStart