updated on Thu Jan 19 12:17:07 UTC 2012
[aur-mirror.git] / sink / sink.sh
blobc3b24a48486a4526ef256f6d8f890780d39f352d
1 #!/bin/bash
2 # sink.sh - a networked battleship game for Bourne-again shell.
4 # Copyright Gregory Bonik, 2009
5 # gregory@bonik.org
7 # Beerware licence is well applicable.
10 # All external stuff we use. Change if needed.
11 NETCAT=nc.traditional
12 STTY=stty
13 CLEAR=clear
16 ##############################################################################
17 # Let there be code
18 ##############################################################################
20 VERSION="0.0.1a"
22 CHAR_SHIP='@'
23 CHAR_ACTIVE='%'
24 CHAR_DEAD='X'
25 CHAR_WATER='~'
26 CHAR_MISS='o'
27 CHAR_CROSSHAIR='*'
29 EXENAME="$0"
31 quit() {
32 read -t 0.5 -n 20
33 $STTY echo
34 exit $1
37 usage() {
38 echo "Sink'em all - a networked battleship game for Bourne-again shell."
39 echo "Version $VERSION"
40 echo "Usage: $EXENAME [ -serve | HOST ] [ -p PORT ]"
41 quit
44 title() {
45 $CLEAR
46 local SPACE=" "
47 local MSG1_XXXXXXXXXXXXXXXXXXXXXXXXXXX="$MSG1${SPACE:0:$((33 - ${#MSG1}))}"
48 local MSG2_XXXXXXXXXXXXXXXXXXXXXXXXXXX="$MSG2${SPACE:0:$((33 - ${#MSG2}))}"
49 local MSG3_XXXXXXXXXXXXXXXXXXXXXXX="$MSG3${SPACE:0:$((29 - ${#MSG3}))}"
50 local MSG4_XXXXXXXXXXXXXXXXXXXXXXX="$MSG4${SPACE:0:$((29 - ${#MSG4}))}"
51 echo "O-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..O"
52 echo ") .- . | . , _ .'| | | | ("
53 echo ") \`; | |\\| |\`. (_' |\`| :-: | | . ("
54 echo ') .-. _`_ _ _ _ _________________`_`_`_`_ _ .-. ('
55 echo ") \`.' \`--' ,__ \`--' '.' ("
56 echo ") ;__{ ("
57 echo ") $MSG1_XXXXXXXXXXXXXXXXXXXXXXXXXXX ; ("
58 echo ") $MSG2_XXXXXXXXXXXXXXXXXXXXXXXXXXX |.------. ("
59 echo ") ;-----| | ("
60 echo ") $MSG3_XXXXXXXXXXXXXXXXXXXXXXX | \`------\` ("
61 echo ") $MSG4_XXXXXXXXXXXXXXXXXXXXXXX \`---.------\\--- ("
62 echo ') ;-----| \\ | ('
63 echo ') / | \ \ | ('
64 echo ') / ;_______\ \ ('
65 echo ') ------.--------\ \ ('
66 echo ') / / / ; \ \ ('
67 echo ') / / / / ; \/ ('
68 echo ') / / / / /; .------/ ('
69 echo ") /-/-/-/-/-\`'_____//_, ("
70 echo ") / # } ("
71 echo ") / o o o o o o # | (C) 2009 G. B. ("
72 echo ") ~ ~~..~~.. ..~~~.. ~~. \`~~~~~....._____#_-' ~~.. ~~.. ..~~~... ~~.. ~~.. ~ ("
73 echo ") ~~ ..~~~.. ..~... ..~~~. ~~..~~ ~~..~~ ~~..~~ ~~..~~..~~..~ ~~ ~~ ~ ("
74 echo -n "O-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..O"
77 redraw() {
78 local i j
79 local SPACE=" "
80 local H="${HINT[0]}"
81 $CLEAR
82 echo "O-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..O"
83 echo ") .- . | . , _ .'| | | | ("
84 echo ") \`; | |\\| |\`. (_' |\`| :-: | | . ("
85 echo ') .-. _`_ _ _ _ _________________`_`_`_`_ _ .-. ('
86 echo ") \`.' \`--' \`--' '.' ("
87 echo ") o~-~-~-~-~-~-~-~-~-~-~-~-~o o-~-~-~-~-~-~-~-~-~-~-~-~-o o-~-~-~-~-~-~-~-~-~o ("
88 echo ") ; a b c d e f g h i j | ; a b c d e f g h i j | ; $H${SPACE:0:$((16-${#H}))} | ("
89 local S1="|"
90 local S2=";"
91 for (( i = 0; i < 10; ++i )) ; do
92 echo -n ") $S1 $(( (i + 1) % 10)) "
93 for (( j = 0; j < 10; ++j )) ; do
94 echo -n "${THY[$((i*10 + j))]} "
95 done
96 echo -n " $S2 $S1 $(( (i + 1) % 10)) "
97 for (( j = 0; j < 10; ++j )) ; do
98 echo -n "${FOE[$((i*10 + j))]} "
99 done
100 H="${HINT[$((i + 1))]}"
101 local HINTX="$H${SPACE:0:$((16 - ${#H}))}"
102 echo " $S2 $S1 $HINTX $S2 ("
103 local SWAP="$S1"
104 S1="$S2"
105 S2="$SWAP"
106 done
107 echo ") o~-~-~-~-~-~-~-~-~-~-~-~-~o o-~-~-~-~-~-~-~-~-~-~-~-~-o o-~-~-~-~-~-~-~-~-~o ("
108 echo ") ("
109 local MSGX="$MSG${SPACE:0:$((72 - ${#MSG}))}"
110 local MSG2X="$MSG2${SPACE:0:$((72 - ${#MSG2}))}"
111 echo ") o-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~o ("
112 echo ") | $MSGX | ("
113 echo ") | $MSG2X | ("
114 echo ") o~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-o ("
115 echo -n "O-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..-~-..O"
118 initfields() {
119 local i j
120 for (( i = 0; i < 100; ++i )) ; do
121 THY[$i]=$CHAR_WATER
122 FOE[$i]=$CHAR_WATER
123 done
126 placeship() {
127 local imin=$((ROW - 1))
128 local jmin=$((COL - 1))
129 local imax=$((ROW + H + 1))
130 local jmax=$((COL + W + 1))
131 local i j
133 ((imin < 0)) && imin=0
134 ((jmin < 0)) && jmin=0
135 ((imax > 10)) && imax=10
136 ((jmax > 10)) && jmax=10
139 for (( i = imin; i < imax; ++i )) ; do
140 for (( j = jmin; j < jmax; ++j )) ; do
141 [[ "${THY[$((i*10 + j))]}" == "$CHAR_SHIP" ]] && return 1
142 done
143 done
145 for (( i = 0; i < H; ++i )) ; do
146 for (( j = 0; j < W; ++j )) ; do
147 THY[$(((ROW + i)*10 + j + COL))]="$CHAR_SHIP"
148 THYSHIP[$(((ROW + i)*10 + j + COL))]=$(( 10 - LEFT ))
149 done
150 done
152 (( --LEFT ))
153 (( --SHIPS[SHIP] ))
154 (( SHIPS[SHIP] < 1 )) && (( ++SHIP ))
155 (( SHIP > 3 )) && return 0
156 (( W = 4 - SHIP ))
157 (( H = 1 ))
158 (( ROW = 0 ))
159 (( COL = 0 ))
161 return 0
164 arrayget() {
165 local ARR="$1"
166 local IDX="$2"
167 eval echo \${$ARR[$IDX]}
170 arrayset() {
171 local ARR="$1"
172 local IDX="$2"
173 eval $ARR[$IDX]="$3"
176 automiss() {
177 local imin="$((CELL / 10))"
178 local jmin="$((CELL % 10))"
179 local imax="$imin"
180 local jmax="$jmin"
181 local c="$CELL"
182 local i j
184 for (( c = $CELL; imin > 0; --imin )) ; do
185 [ "$(arrayget $1 $c)" == "$CHAR_DEAD" ] || break
186 (( c -= 10 ))
187 done
188 for (( c = $CELL; imax < 9; ++imax )) ; do
189 [ "$(arrayget $1 $c)" == "$CHAR_DEAD" ] || break
190 (( c += 10 ))
191 done
192 for (( c = $CELL; jmin > 0; --jmin )) ; do
193 [ "$(arrayget $1 $c)" == "$CHAR_DEAD" ] || break
194 (( c -= 1 ))
195 done
196 for (( c = $CELL; jmax < 9; ++jmax )) ; do
197 [ "$(arrayget $1 $c)" == "$CHAR_DEAD" ] || break
198 (( c += 1 ))
199 done
201 for (( i = imin; i <= imax; ++i )) ; do
202 for (( j = jmin; j <= jmax; ++j )) ; do
203 echo "$i $j $(arrayget $1 $((i*10 + j)) )"
204 if [ "$(arrayget $1 $((i*10 + j)) )" == "$CHAR_WATER" ] ; then
205 arrayset $1 $((i*10 + j)) "$CHAR_MISS"
207 done
208 done
211 checkconnection() {
212 if [ -z "$COPROC_PID" ] ; then
213 echo "Connection closed"
214 quit
218 placeships() {
219 SHIPS=( 1 2 3 4 )
220 THYDECKS=20
221 THYSHIPDECKS=( 4 3 3 2 2 2 1 1 1 1 )
222 FOEDECKS=20
223 SHIP=0
224 LEFT=10
225 ROW=0
226 COL=0
229 WIN=0
230 HINT=( "Keys:" " "
231 "w Up "
232 "a Left "
233 "s Down "
234 "d Right "
235 "r Rotate ship "
236 "p Place ship "
238 MSG="Place your ships."
240 while (( LEFT > 0 )) ; do
241 BACKUP=( "${THY[@]}" )
242 for (( i = 0; i < H; ++i )); do
243 for (( j = 0; j < W; ++j )); do
244 THY[$(( (i + ROW) * 10 + j + COL ))]="$CHAR_ACTIVE"
245 done
246 done
248 redraw
250 THY=( "${BACKUP[@]}" )
252 if ! read -sn 1 KEY ; then
253 quit
255 checkconnection
257 MSG="Place your ships."
258 case "$KEY" in
259 w) ((--ROW)) ;;
260 a) ((--COL)) ;;
261 s) ((++ROW)) ;;
262 d) ((++COL)) ;;
263 r) TMP=$W; W=$H; H=$TMP ;;
265 if placeship ; then
266 MSG="Ship placed."
267 else
268 MSG="Cannot place ship here because "
269 MSG+="another ship is too close"
272 esac
274 if (( ROW < 0 )) ; then ROW=0 ; fi
275 if (( COL < 0 )) ; then COL=0 ; fi
276 if (( ROW + H > 10 )) ; then ((ROW = 10 - H)); fi
277 if (( COL + W > 10 )) ; then ((COL = 10 - W)); fi
278 done
281 waitturn() {
282 redraw
283 while true; do
284 checkconnection
285 if ! read -u "${COPROC[0]}" CELL ; then
286 echo "Connection closed"
287 quit
290 if (( CELL < 0 || CELL > 99 )) ; then
291 echo "Received malformed data, exiting"
292 quit
295 CELL=$((CELL))
297 if [ "${THY[$CELL]}" == "$CHAR_SHIP" ] ; then
298 THY["$CELL"]="$CHAR_DEAD"
299 (( --THYDECKS ))
300 (( --THYSHIPDECKS[THYSHIP[CELL]] ))
302 if (( THYSHIPDECKS[THYSHIP[CELL]] > 0 )) ; then
303 MSG="Enemy has hit your ship!"
304 echo hit >&"${COPROC[1]}"
305 else
306 MSG="Enemy has sunk your ship!"
307 echo sunk >&"${COPROC[1]}"
310 if (( THYDECKS == 0 )) ; then
311 MSG="All your ships have been sunk. You've lost the game :("
312 return 1
314 else
315 if [ "${THY[$CELL]}" == "$CHAR_WATER" ] ; then
316 THY["$CELL"]="$CHAR_MISS"
319 MSG="Enemy has missed. Your turn."
320 echo miss >&"${COPROC[1]}"
321 break
323 redraw
324 done
325 redraw
326 return 0
329 hitfoe() {
330 FOE["$CELL"]="$CHAR_DEAD"
331 (( --FOEDECKS ))
332 if (( FOEDECKS == 0 )) ; then
333 MSG="Congratulations! You have won! :-)"
334 WIN=1
335 return 1
337 return 0
340 fire() {
341 if [ "${FOE[$CELL]}" != "$CHAR_WATER" ] ; then
342 MSG='Already shot there! Choose another target.'
343 redraw
344 return 1
347 checkconnection
348 echo "$CELL" >&"${COPROC[1]}"
350 checkconnection
351 if ! read -u "${COPROC[0]}" HIT ; then
352 echo "Connection closed"
353 quit
356 if [ "$HIT" == "hit" ] ; then
357 MSG="You have hit! Your turn again."
358 hitfoe || return 0
359 return 1
360 else
361 if [ "$HIT" == "sunk" ] ; then
362 MSG="You have sunk enemy's ship! Your turn again."
363 hitfoe || return 0
364 automiss FOE
365 return 1
369 FOE["$CELL"]="$CHAR_MISS"
370 MSG="You have missed. Now waiting enemy's turn..."
371 redraw
373 return 0
376 maketurn() {
377 COL=0
378 ROW=0
379 HINT=( "Keys: "
381 "w Up" "a Left" "s Down" "d Right" "f Fire"
384 while true; do
385 CELL=$(( ROW*10 + COL ))
386 BACKUP="${FOE[$CELL]}"
387 FOE[$CELL]="$CHAR_CROSSHAIR"
388 redraw
389 FOE[$CELL]=$BACKUP
391 if ! read -sn 1 KEY ; then
392 quit
394 checkconnection
396 case "$KEY" in
397 w) ((--ROW)) ;;
398 a) ((--COL)) ;;
399 s) ((++ROW)) ;;
400 d) ((++COL)) ;;
401 f) fire && break ;;
402 esac
404 (( ROW < 0 )) && ROW=0
405 (( COL < 0 )) && COL=0
406 (( ROW >= 10 )) && ROW=9
407 (( COL >= 10 )) && COL=9
408 done
410 return $WIN
413 $STTY -echo
415 # Parse command-line options
416 SERVE=0
417 HOST=""
418 PORT=21366
420 while (( $# > 0 )) ; do
421 case "$1" in
422 "-serve")
423 SERVE=1
425 "-p")
426 if (( $# < 2 )); then usage; fi
427 PORT=$2
428 shift
431 echo "Unknown option $1"
432 usage
435 if [ -n "$HOST" ]; then usage; fi
436 HOST=$1
438 esac
439 shift
440 done
442 if [ -z "$HOST" ] && (( SERVE == 0 )) ; then
443 echo "Need a hostname."
444 usage
447 $CLEAR
448 HELLOSTR="Hi, I want to play a game with you"
450 if (( SERVE == 1 )) ; then
451 MSG1="Waiting for incoming"
452 MSG2="connection on port $PORT..."
453 title
454 coproc $NETCAT -q 0 -l -p "$PORT"
455 READTIMEOUT=180
456 else
457 MSG1="Connecting to"
458 MSG2="$HOST:$PORT..."
459 coproc $NETCAT -w 5 -q 0 "$HOST" "$PORT"
460 READTIMEOUT=5
463 echo "$HELLOSTR" >&${COPROC[1]}
465 checkconnection
466 if ! read -t "$READTIMEOUT" -u "${COPROC[0]}" HELLO ; then
467 echo "Failed to connect, exiting."
468 quit 1
471 if [ "$HELLO" != "$HELLOSTR" ] ; then
472 echo "Unknown peer at the other end of connection, exiting."
473 quit 1
476 #echo "Connected! Press ENTER to start placing ships!"
477 MSG1="Connected!"
478 MSG2=""
479 MSG3="Press ENTER to start"
480 MSG4="placing ships"
481 title
482 read
484 checkconnection
486 while true; do
487 MSG=""
488 MSG2=""
489 initfields
490 placeships
491 HINT=()
493 MSG="All ships placed. Waiting for the enemy..."
494 redraw
495 checkconnection
497 THYNUMBER=$RANDOM
498 echo "$THYNUMBER" >&${COPROC[1]}
500 checkconnection
501 if ! read -u "${COPROC[0]}" FOENUMBER ; then
502 echo "Connection closed"
503 quit
506 if (( FOENUMBER > THYNUMBER )) ; then
507 MSG="Enemy's turn is first. Waiting..."
508 waitturn || LOST=1
509 else
510 MSG="You turn."
513 while ((LOST != 1)); do
514 maketurn || break
515 HINT=()
516 waitturn || break
517 done
518 HINT=()
520 MSG2="Play again? (y/n)"
521 redraw
523 while read -n 1 YESNO ; do
524 checkconnection
525 if [ "$YESNO" == 'y' ] || [ "$YESNO" == 'Y' ] ; then
526 continue 2
528 if [ "$YESNO" == 'n' ] || [ "$YESNO" == 'N' ] ; then
529 break 2
531 done
532 break
533 done
535 if [ -n "$COPROC_PID" ] ; then
536 kill $COPROC_PID
537 wait $COPROC_PID
540 quit