mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-01 05:16:05 +01:00
30568: Add tetriscurses contrib function, port of tetris to zcurses
This commit is contained in:
parent
bc55ddf364
commit
62b0e611ce
3 changed files with 402 additions and 0 deletions
|
@ -1,3 +1,8 @@
|
|||
2015-01-09 Mikael Magnusson <mikachu@gmail.com>
|
||||
|
||||
* 30568: Doc/Zsh/contrib.yo, Functions/Misc/tetriscurses: Add
|
||||
tetriscurses contrib function, port of tetris to zcurses
|
||||
|
||||
2015-01-08 Peter Stephenson <p.stephenson@samsung.com>
|
||||
|
||||
* Src/init.c, Src/input.c, Src/lex.c, Src/parse.c, Src/zsh.h,
|
||||
|
|
|
@ -3609,6 +3609,17 @@ If you quit in the middle of a game, the next invocation of the tt(tetris)
|
|||
widget will continue where you left off. If you lost, it will start a new
|
||||
game.
|
||||
)
|
||||
item(tt(tetriscurses))(
|
||||
This is a port of the above to zcurses. The input handling is improved
|
||||
a bit so that moving a block sideways doesn't automatically advance a
|
||||
timestep, and the graphics use unicode block graphics.
|
||||
|
||||
This version does not save the game state between invocations, and is not
|
||||
invoked as a widget, but rather as:
|
||||
|
||||
example(autoload -U tetriscurses
|
||||
tetriscurses)
|
||||
)
|
||||
findex(zargs)
|
||||
item(tt(zargs) [ var(option) ... tt(-)tt(-) ] [ var(input) ... ] [ tt(-)tt(-) var(command) [ var(arg) ... ] ])(
|
||||
This function has a similar purpose to GNU xargs. Instead of
|
||||
|
|
386
Functions/Misc/tetriscurses
Normal file
386
Functions/Misc/tetriscurses
Normal file
|
@ -0,0 +1,386 @@
|
|||
# I noticed we don't ship any contrib and/or example scripts using the
|
||||
# zcurses module, and also that the builtin tetris is sort of boring, so
|
||||
# I figured I'd port it to curses. It works pretty well, but I noticed
|
||||
# two problems with the zcurses module in the process:
|
||||
#
|
||||
# 1. the HAVE_USE_DEFAULT_COLORS define seems to never be defined?
|
||||
#
|
||||
# 2a. resizing the window causes 'zcurses input' to wait forever for a
|
||||
# key, even with a timeout defined.
|
||||
#
|
||||
# Bart says:
|
||||
# >This probably has something to do with the special-casing around wgetch()
|
||||
# >for signals handled by the "trap" command. See the big comment in
|
||||
# >Src/Modules/curses.c lines 1073-1103.
|
||||
#
|
||||
# >It may be problematic to mix curses with the generic signal handling in
|
||||
# >the main shell. We may need to swap in a SIGWINCH handler wrapper while
|
||||
# >the curses UI is active, and restore the main handler when leaving it.
|
||||
#
|
||||
# 2b. resizing the window doesn't cause an event while running the
|
||||
# program, but if i resize before starting(?) i get an event RESIZE on
|
||||
# my first input call.
|
||||
#
|
||||
# Bart says:
|
||||
# >There's probably some state that needs to be cleared on entry to
|
||||
# >zccmd_input() so that curses doesn't see something left over from the
|
||||
# >previous signal. Unfortunately I don't know what that would be.
|
||||
|
||||
if (( $LINES < 22 || $COLUMNS < 46 )); then
|
||||
echo >&2 'terminal needs to be at least 22 lines and 46 columns'
|
||||
return
|
||||
fi
|
||||
|
||||
emulate -L zsh
|
||||
|
||||
typeset -a tetris_shapes
|
||||
tetris_shapes=(
|
||||
0x0f00 0x4444 0x0f00 0x4444
|
||||
0x4e00 0x4c40 0x0e40 0x4640
|
||||
0x6600 0x6600 0x6600 0x6600
|
||||
0x4620 0x6c00 0x4620 0x6c00
|
||||
0x2640 0x6300 0x2640 0x6300
|
||||
0x6440 0x8e00 0x44c0 0x0e20
|
||||
0xc440 0x0e80 0x4460 0x2e00
|
||||
)
|
||||
typeset -A tetris_rotations
|
||||
tetris_rotations=(
|
||||
0x0f00 0x4444 0x4444 0x0f00
|
||||
0x4e00 0x4c40 0x4c40 0x0e40 0x0e40 0x4640 0x4640 0x4e00
|
||||
0x6600 0x6600
|
||||
0x4620 0x6c00 0x6c00 0x4620
|
||||
0x2640 0x6300 0x6300 0x2640
|
||||
0x6440 0x8e00 0x8e00 0x44c0 0x44c0 0x0e20 0x0e20 0x6440
|
||||
0xc440 0x0e80 0x0e80 0x4460 0x4460 0x2e00 0x2e00 0xc440
|
||||
)
|
||||
local tetris_vsz=20 tetris_hsz=11
|
||||
local tetris_blankline=${(l:11:: :)}
|
||||
local tetris_blankboard=${(j::):-${(l:11:: :)}${(s: :)^${(l:20:: :)}}}
|
||||
|
||||
local tetris_board=$tetris_blankboard
|
||||
local tetris_score=0
|
||||
local tetris_lines=0
|
||||
|
||||
local tetris_{block{,_next,_x,_y},i}
|
||||
|
||||
function __tetris-next-block {
|
||||
tetris_block_next=$tetris_shapes[1+RANDOM%$#tetris_shapes]
|
||||
}
|
||||
|
||||
function __tetris-new-block {
|
||||
tetris_block=$tetris_block_next
|
||||
__tetris-next-block
|
||||
__tetris-draw-next-block
|
||||
tetris_block_y=0
|
||||
tetris_block_x=4
|
||||
if ! __tetris-block-fits; then
|
||||
__tetris-game-over
|
||||
fi
|
||||
__tetris-place-block "*"
|
||||
}
|
||||
|
||||
function __tetris-left {
|
||||
__tetris-place-block " "
|
||||
(( tetris_block_x-- ))
|
||||
__tetris-block-fits || (( tetris_block_x++ ))
|
||||
__tetris-place-block "*"
|
||||
}
|
||||
|
||||
function __tetris-right {
|
||||
__tetris-place-block " "
|
||||
(( tetris_block_x++ ))
|
||||
__tetris-block-fits || (( tetris_block_x-- ))
|
||||
__tetris-place-block "*"
|
||||
}
|
||||
|
||||
function __tetris-rotate {
|
||||
__tetris-place-block " "
|
||||
local save_block=$tetris_block
|
||||
tetris_block=$tetris_rotations[$tetris_block]
|
||||
__tetris-block-fits || tetris_block=$save_block
|
||||
__tetris-place-block "*"
|
||||
}
|
||||
|
||||
function __tetris-drop {
|
||||
__tetris-place-block " "
|
||||
((tetris_block_y++))
|
||||
while __tetris-block-fits; do
|
||||
((tetris_block_y++))
|
||||
((tetris_score+=2))
|
||||
done
|
||||
((tetris_block_y--))
|
||||
__tetris-block-dropped
|
||||
}
|
||||
|
||||
function __tetris-timeout {
|
||||
__tetris-place-block " "
|
||||
((tetris_block_y++))
|
||||
if __tetris-block-fits; then
|
||||
__tetris-place-block "*"
|
||||
return
|
||||
fi
|
||||
((tetris_block_y--))
|
||||
__tetris-block-dropped
|
||||
}
|
||||
|
||||
function __tetris-block-dropped {
|
||||
integer bonus=1
|
||||
__tetris-place-block "O"
|
||||
local fl=${tetris_blankline// /O} i=$((tetris_block_y*tetris_hsz))
|
||||
repeat 4; do
|
||||
if [[ $tetris_board[i+1,i+tetris_hsz] == $fl ]]; then
|
||||
if (( fancygraphics )); then for char in {7..1}; do
|
||||
tetris_board[i+1,i+tetris_hsz]=${tetris_blankline// /$char}
|
||||
__tetris-render-screen
|
||||
zcurses timeout score 50
|
||||
zcurses input score
|
||||
done; fi
|
||||
tetris_board[i+1,i+tetris_hsz]=
|
||||
tetris_board=$tetris_blankline$tetris_board
|
||||
((tetris_score+=100*(bonus++*(tetris_lines/10+10))))
|
||||
((tetris_lines+=1))
|
||||
if ((tetris_lines % 10 == 0)); then
|
||||
((timestep = timestep * 0.80))
|
||||
fi
|
||||
fi
|
||||
((i += tetris_hsz))
|
||||
done
|
||||
__tetris-new-block
|
||||
}
|
||||
|
||||
function __tetris-block-fits {
|
||||
local y x i=$((1+tetris_block_y*tetris_hsz+tetris_block_x)) b=0x8000
|
||||
for ((y=0; y!=4; y++)); do
|
||||
for ((x=0; x!=4; x++)); do
|
||||
if ((tetris_block&b)); then
|
||||
((x+tetris_block_x >= 0)) || return 1
|
||||
((x+tetris_block_x < tetris_hsz)) || return 1
|
||||
((y+tetris_block_y >= 0)) || return 1
|
||||
((y+tetris_block_y < tetris_vsz)) || return 1
|
||||
[[ $tetris_board[i] == " " ]] || return 1
|
||||
fi
|
||||
((b >>= 1))
|
||||
((i++))
|
||||
done
|
||||
((i+=tetris_hsz-4))
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
function __tetris-draw-next-block {
|
||||
local tetris_preview
|
||||
local y x i=1 b=0x8000
|
||||
for ((y=0; y!=4; y++)); do
|
||||
tetris_preview=" "
|
||||
for ((x=0; x!=4; x++)); do
|
||||
((tetris_block_next&b)) && tetris_preview[i]=\*
|
||||
((b >>= 1))
|
||||
((i++))
|
||||
done
|
||||
i=1
|
||||
zcurses move preview $((y+1)) 1
|
||||
zcurses string preview ${${${tetris_preview//O/$filled_block}//\*/$active_block}// / }
|
||||
done
|
||||
}
|
||||
|
||||
function __tetris-place-block {
|
||||
local y x i=$((1+tetris_block_y*tetris_hsz+tetris_block_x)) b=0x8000
|
||||
for ((y=0; y!=4; y++)); do
|
||||
for ((x=0; x!=4; x++)); do
|
||||
((tetris_block&b)) && tetris_board[i]=$1
|
||||
((b >>= 1))
|
||||
((i++))
|
||||
done
|
||||
((i+=tetris_hsz-4))
|
||||
done
|
||||
}
|
||||
|
||||
function __tetris-render-screen {
|
||||
local i x piece
|
||||
setopt localoptions histsubstpattern extendedglob
|
||||
local -a match mbegin mend
|
||||
local -A animation
|
||||
animation=( 7 ▇▇ 6 ▆▆ 5 ▅▅ 4 ▄▄ 3 ▃▃ 2 ▂▂ 1 ▁▁ )
|
||||
for (( i = 0; i < tetris_vsz; i++ )); do
|
||||
zcurses move gamearea $(( i + 1 )) 1
|
||||
zcurses string gamearea ${${${${${tetris_board[1+i*tetris_hsz,(i+1)*tetris_hsz]}//O/$filled_block}//\*/$active_block}// / }//(#b)([1-7])/$animation[$match[1]]}
|
||||
done
|
||||
|
||||
zcurses clear score
|
||||
zcurses move score 1 1
|
||||
zcurses string score "Score: $tetris_score"$'\
|
||||
'" Lines: $tetris_lines"$'\
|
||||
'" Speed: ${timestep%.*} ms"
|
||||
|
||||
zcurses border gamearea
|
||||
zcurses border score
|
||||
zcurses border preview
|
||||
zcurses refresh gamearea score preview $debug
|
||||
}
|
||||
|
||||
function __tetris-game-over {
|
||||
gameover=1
|
||||
}
|
||||
|
||||
function __tetris-new-game {
|
||||
gameover=0
|
||||
timestep=1000
|
||||
tetris_score=0
|
||||
tetris_lines=0
|
||||
__tetris-next-block
|
||||
__tetris-new-block
|
||||
__tetris-render-screen
|
||||
}
|
||||
|
||||
function __tetris-game-over-screen {
|
||||
__tetris-debug "Died with $tetris_score points!"
|
||||
tetris_board=$tetris_blankboard
|
||||
local text="You got $tetris_score points!"
|
||||
local gameover_height=4 gameover_width=$(( $#text + 2 ))
|
||||
zcurses addwin gameover $gameover_height $gameover_width \
|
||||
$(( off_y + (game_height-gameover_height)/2 )) \
|
||||
$(( off_x + (game_width+score_width-gameover_width)/2 ))
|
||||
zcurses move gameover 1 1
|
||||
zcurses string gameover $text
|
||||
text='Play again? [yn]'
|
||||
zcurses move gameover 2 $(( (gameover_width - $#text)/2 ))
|
||||
zcurses string gameover $text
|
||||
zcurses border gameover
|
||||
keepplaying=
|
||||
until [[ $keepplaying = [ynq] ]]; do
|
||||
zcurses input gameover keepplaying
|
||||
done
|
||||
zcurses delwin gameover
|
||||
zcurses refresh stdscr
|
||||
zcurses timeout gamearea ${timestep%.*}
|
||||
__tetris-new-game
|
||||
}
|
||||
|
||||
function __tetris-debug {
|
||||
if [[ -z $debug ]]; then
|
||||
return
|
||||
fi
|
||||
zcurses scroll debug -1
|
||||
zcurses move debug 0 0
|
||||
zcurses string debug "$1"
|
||||
}
|
||||
|
||||
function __tetris-remove-wins {
|
||||
local delwin
|
||||
local -a delwins
|
||||
delwins=(gamearea score debug gameover help preview)
|
||||
for delwin in ${delwins:*zcurses_windows}; do
|
||||
zcurses delwin $delwin
|
||||
done
|
||||
}
|
||||
|
||||
function __tetris-help {
|
||||
local i
|
||||
local help_height=9 help_width=23
|
||||
zcurses addwin help $help_height $help_width \
|
||||
$(( off_y + (game_height - help_height) / 2 )) \
|
||||
$(( off_x + (game_width + score_width - help_width) / 2 ))
|
||||
zcurses move help 1 1
|
||||
zcurses string help $'left: h, j, left\
|
||||
right: right, n, l\
|
||||
rotate: up, c, i\
|
||||
soft drop: down, t, k\
|
||||
hard drop: space\
|
||||
quit: q\
|
||||
press space to return'
|
||||
zcurses border help
|
||||
until [[ $i == [\ q] ]]; do
|
||||
zcurses input help i
|
||||
if [[ $i == q ]]; then
|
||||
keepplaying=n
|
||||
fi
|
||||
done
|
||||
zcurses delwin help
|
||||
zcurses refresh stdscr
|
||||
}
|
||||
|
||||
zmodload zsh/curses && {
|
||||
zcurses init
|
||||
__tetris-remove-wins
|
||||
zcurses refresh
|
||||
echoti civis
|
||||
local debug=
|
||||
if (( ${@[(I)--debug|-d]} )); then
|
||||
debug=debug
|
||||
fi
|
||||
local off_x off_y
|
||||
local game_height=22 game_width=25
|
||||
local score_height=5 score_width=20
|
||||
local preview_height=6 preview_width=10
|
||||
local filled_block active_block
|
||||
local fancygraphics
|
||||
if zmodload zsh/langinfo && [[ $langinfo[CODESET] = UTF-8 ]]; then
|
||||
filled_block=██
|
||||
active_block=▒▒
|
||||
fancygraphics=${@[(I)--silly]}
|
||||
else
|
||||
filled_block='[]'
|
||||
active_block='()'
|
||||
fancygraphics=0
|
||||
fi
|
||||
off_x=$(( (COLUMNS-game_width-score_width-1) / 2 ))
|
||||
off_y=$(( (LINES-game_height) / 2 ))
|
||||
zcurses clear stdscr redraw
|
||||
zcurses refresh stdscr
|
||||
zcurses addwin gamearea $game_height $game_width $off_y $off_x
|
||||
zcurses scroll gamearea off
|
||||
zcurses addwin score $score_height $score_width \
|
||||
$off_y $(( off_x + game_width + 1 ))
|
||||
zcurses scroll score off
|
||||
zcurses addwin preview $preview_height $preview_width \
|
||||
$(( off_y + score_height )) $(( off_x + game_width + 1 ))
|
||||
zcurses scroll preview off
|
||||
if [[ -n $debug ]]; then
|
||||
zcurses addwin debug $(( game_height - score_height - preview_height - 1 )) \
|
||||
$score_width \
|
||||
$(( off_y + score_height + preview_height ))\
|
||||
$(( off_x + game_width + 1 ))
|
||||
fi
|
||||
typeset -F SECONDS
|
||||
local now prev timestep timeout key kkey keepplaying=y gameover=0
|
||||
prev=$SECONDS
|
||||
__tetris-new-game
|
||||
zcurses timeout gamearea 0
|
||||
while [[ $keepplaying == y ]]; do
|
||||
if zcurses input gamearea key kkey; then
|
||||
__tetris-debug "got input $key$kkey"
|
||||
case $key$kkey in
|
||||
LEFT|h|j) __tetris-left;;
|
||||
RIGHT|n|l) __tetris-right;;
|
||||
UP|c|i) __tetris-rotate;;
|
||||
DOWN|t|k) __tetris-timeout; ((tetris_score++)); prev=$SECONDS;;
|
||||
" ") __tetris-drop;;
|
||||
q) break;;
|
||||
F1|H) __tetris-help;;
|
||||
esac
|
||||
else
|
||||
__tetris-debug "timed out"
|
||||
__tetris-timeout
|
||||
fi
|
||||
now=$SECONDS
|
||||
if (( prev + timestep/1000. < now )); then
|
||||
(( prev += timestep/1000. ))
|
||||
fi
|
||||
timeout=${$(( 1000.*(prev + timestep/1000. - now) + 1 ))%.*}
|
||||
if (( timeout < 0 )); then
|
||||
__tetris-debug "BUG: timeout < 0"
|
||||
timeout=${timestep%.*}
|
||||
fi
|
||||
zcurses timeout gamearea $timeout
|
||||
__tetris-debug "timeout: $timeout"
|
||||
|
||||
__tetris-render-screen
|
||||
if [[ $gameover == 1 ]]; then
|
||||
__tetris-game-over-screen
|
||||
fi
|
||||
done
|
||||
} always {
|
||||
__tetris-remove-wins
|
||||
echoti cnorm
|
||||
zcurses end
|
||||
}
|
Loading…
Reference in a new issue