Vim Tetris

Thread in 'Discussion' started by marionintendo, 6 Mar 2017.

  1. Yay, another version of Tetris!~

    I don't know if some programmers here like to use the Vim editor, but if you do you might find this interesting, if you don't already know about it. It's been initially released in 2002, after all. Yeah, there's Tetris included in emacs, ... this is a thread about vim tetris. I've picked up the code that was sitting on vim.org and tried to make it closer to real tetris to the best of my abilities. I'm not familiar at all with vimscript, but if you want to participate to this project or if you have any suggestions, please let me know! Also let me know if you have any issues running the game.

    The game supports sonic drops (and you can quickly move after it too), pausing, and speeds up. The randomizer could be better, though. Still, quite a feat for a text editor!

    Controls (they're weird because the initial goal of this version was to teach how to control vim with h.j.k.l keys):
    h: left
    l: right
    j: down
    i,k: rotate
    <Space>: drop
    <Esc> or q: quit

    Script and instructions are available here:
    http://www.vim.org/scripts/script.php?script_id=172

    P.S.: The "Rotating" mode is broken since Feb 2002, use "Traditional" mode.
     

    Attached Files:

    Tomek and Burbruee like this.
  2. I use vim for many years now but I've never thought about that kind of Tetris inside an editor :D
     
  3. Oh this is lovely! I work with vim every day, so now have a game to play during my lunch breaks! :D

    Update:
    The controls are a bit awkward, although I guess I shouldn't be surprised, being VIM.
     
    Last edited: 8 Mar 2017
  4. I'll look at the script today and update it for those who want to modify the controls. :) Glad you like it!
     
  5. Alright, so I took the time to look at the code. See updated version below. I adapted the controls to be similar to Texmaster:
    z,c is left, right
    x is soft drop
    s is sonic drop
    n, m and ',' are CCW, CW and CCW respectively. (I didn't use m ',' '.' because the dot could not appear in the on-screen instructions for some reason)

    I you really want to have m ',' '.', just like in Texmaster, replace lines 234-237 with this:

    Code:
      if c=~ '[zxcm,.]'
      let nx=b:x+((c=='z')?-1:((c=='c')?1:0))
      let ny=b:y+((c=='x')?1:0)
      let npos=(c!~'[m,.]')?(b:pos):(c==','?((b:pos+3)%4):((b:pos+1)%4))
    
    I also noticed that my rotation for the I bar was wrong (woops), so you can fix this by replacing line 22 with

    Code:
      let sh00=0x0f00|let sh01=0x4444|let sh02=0x00f0|let sh03=0x2222 " I shape 
    
    (The mistake is I had 8888 instead of 4444, my hex to binary is rusty I guess!)

    So here is the final product, with the bug fixes and the Texmaster controls:
    Code:
    " Name: Tetris (game)
    " Version: 0.53
    " News: Several tweaks to match current official Tetris Standards
    " Maintainer, main author: Gergely Kontra <pihentagy@gmail.com>
    " Co-autors, helpers:
    "  Michael Geddes  v0.4, color, Plugin support, code optimizing
    "  Peter ??? raindog  Timing help, bug reports
    "  Dan Sharp  Bug reports, improvements
    "  Felix Leger  Improvements
    "  If your name is not here, but should be, drop me a mail
    
    " TODO FocusGained FocusLost Auto calibration during play
    let s:s='-Tetris_game-'
    let s:top10=(filewritable($HOME)==2)?($HOME.'/.tetris'):(expand('<sfile>:p:h').'/.tetris')
    let s:top10f=escape(s:top10,'\%#')
    let s:WIDTH=10|let s:NEXTXPOS=16|let s:NEXTYPOS=2
    let s:CLEAR=0|let s:CHECK=1|let s:DRAW=2
    let s:shs=7|let s:cols=7
    let s:i=24|let s:r=''|wh s:i|let s:r=s:r."\<C-y>"|let s:i=s:i-1|endw
    
    fu! s:Put(l,c,pos,m)
      let sh00=0x0f00|let sh01=0x4444|let sh02=0x00f0|let sh03=0x2222 " I shape
      let sh10=0x0660|let sh11=0x0660|let sh12=0x0660|let sh13=0x0660 " O shape
      let sh20=0x0071|let sh21=0x0322|let sh22=0x0470|let sh23=0x0226 " J shape
      let sh30=0x0074|let sh31=0x0223|let sh32=0x0170|let sh33=0x0622 " L shape
      let sh40=0x0360|let sh41=0x4620|let sh42=0x0360|let sh43=0x4620 " S shape
      let sh50=0x0630|let sh51=0x0264|let sh52=0x0630|let sh53=0x0264 " Z shape
      let sh60=0x0720|let sh61=0x2320|let sh62=0x2700|let sh63=0x2620 " T shape
    
      let sgn0='[]'|let sgn1='MM'|let sgn2='{}'|let sgn3='XX'|let sgn4='@@'|let sgn5='<>'|let sgn6='$$'
      exe 'norm! '.a:l.'G'.(a:c*2+1)."|a\<Esc>"
      let c=1|let r=1
      let s=(a:c!=s:NEXTXPOS)?(sh{b:sh}{a:pos}):(sh{b:nsh}{a:pos})
      wh r<5
      if s%2
      if a:m==s:DRAW
      exe "norm! R".sgn{a:c!=s:NEXTXPOS?(b:col):(b:ncol)}."\<Esc>l"
      elsei a:m==s:CHECK
      let ch=getline('.')[col('.')-1]
      if (b:col<s:cols) && ch!='.' || (b:col==s:cols) && ch=='#'
      retu 0
      en
      norm! 2l
      el
      norm! 2r.l
      en
      el
      norm! 2l
      en
      let c=c+1
      if c>4
      let c=1
      norm! 8hj
      let r=r+1
      en
      let s=s/2
      endw
      norm! ggr#
      retu 1
    endf
    
    
    fu! s:Cnt(i)
      let m=search('^  ##[^#.]\{'.2*s:WIDTH.'}##')
      if !m|retu|en
      wh m>1
      exe 'norm!' m.'GR'.s:r."\<Esc>"
      let m=m-1
      endw
      match Flash /\./
      redr
      sl 150m
      match none
      redr
      sl 150m
      exe "norm! 9G".a:i."\<C-a>"
      let l=getline(9)
      let s:score=0+strpart(l,match(l,'[1-9]'))
      if s:score>=s:nxLevel
      if s:nxLevel==10 && exists('s:starttime')
      exe 'redir >>' s:top10.'_stat'
      echo 'CNT='.s:CNT.' CNT2='.s:CNT2.' '.(localtime()-s:starttime) ' sec ' s:TICKS ' ticks'
      redir END
      en
      let s:DTIME=s:DTIME*7/8
      let s:COUNTER=(s:CNT*s:DTIME)/1000
      let s:nxLevel=s:nxLevel+10
      en
      cal s:Cnt(a:i*2)
    endf
    
    fu! s:Resume()
      exe bufwinnr(bufnr(s:s)).'winc w'
      res 21
      setl ma
      se gcr=a:hor1-blinkon0 ve=all
      se noea
    endf
    
    fun! s:Pause()
      let &gcr=s:gcr
      let &ve=s:ve
      setl noma
      let &ea=s:wa
      exe bufwinnr(s:ow).'winc w'
      retu 1
    endf
    
    fu! s:Sort()
      wh line('.')>1&&matchstr(getline(line('.')-1),'\d\+$')<s:score|move -2|endw
      let s:pos=line('.')
      g/^$/d   " Clears empty lines
      11,$d _
      redr
    endf
    
    
    fu! s:End()
      exe 'redir >>' s:top10.'_stat'
      echo|let i=0|wh i<7|echon 'Sh'.i.' '.s:sh{i}.' '|let i=i+1|endw|echo
      redir END
      norm! 22GdG
      let &gcr=s:gcr
      let &ea=s:wa
      se nolz
      exe 'vsp' s:top10f
      if line('$')<10 || matchstr(getline('$'),'\d\+$')<s:score
      let numlen=20-strlen(s:score)
      setl ve=all ma
      cal append('$',s:name)
      exe "norm! G".numlen."|a".(s:score)."\<Esc>"
      sil! cal s:Sort()
      sil w
      el
      let s:pos=0
      en
      1|setl bh=delete|vert res 43|noh
      exe 'match Search /.*\%'.s:pos.'l.*/'
      echo | echo
      redr|echon 'Press a key to quit game'|cal getchar()|q
      let i=21|wh i|del|sl 40ms|let i=i-1|redr|endw|bd
      let &ve=s:ve
      let &lz=s:lz
    endf
    
    fu! s:Init()
      let s:nxLevel=10
      let s:ow=bufnr('%')
      let s:score=0
      exe 'sp '.escape(s:s,' ').'|set ma|1,$d'
      let b:col=0
      let b:sh=0
      let b:nsh=(localtime()+8*b:sh)%s:shs
      let b:ncol=b:nsh
      let b:pos=0
      let b:x=6
      let b:y=20
      let s:starttime=localtime()
      let s:TICKS=0
      let s:gcr=&gcr
      let s:wa=&ea
      let s:ve=&ve
      let s:lz=&lz
      let i=0|wh i<7|let s:sh{i}=0|let i=i+1|endw
      se ve=all
      setl bh=delete noswf bt=nofile nf= gcr=a:hor1-blinkon0 nolz
      exe "norm!i  ##\<Esc>".s:WIDTH."a..\<Esc>2a#\<Esc>yy19pGo0\<C-d>  #\<Esc>".(2*s:WIDTH+4-1)."a#\<Esc>yy3pgg"
    
      hi Bg term=reverse ctermfg=Black ctermbg=Black guifg=Black guibg=Black
      syn match Bg "\."
      hi Wall term=reverse ctermfg=LightBlue ctermbg=Blue guifg=LightBlue guibg=Blue
      syn match Wall "[#\/|-]"
      hi Shape0 term=reverse ctermfg=DarkCyan ctermbg=Cyan guifg=DarkCyan guibg=Cyan
      syn match Shape0 "[[\]]"
      hi Shape1 term=reverse ctermfg=DarkYellow ctermbg=Yellow guifg=DarkYellow guibg=Yellow
      syn match Shape1 "MM"
      hi Shape2 term=reverse ctermfg=DarkBlue ctermbg=Blue guifg=Darkblue guibg=Blue
      syn match Shape2 "{}"
      hi Shape3 term=reverse ctermfg=Grey ctermbg=White guifg=Grey guibg=White
      syn match Shape3 "XX"
      hi Shape4 term=reverse ctermfg=DarkGreen ctermbg=Green guifg=DarkMagenta guibg=Magenta
      syn match Shape4 "@@"
      hi Shape5 term=reverse ctermfg=DarkRed ctermbg=Red guifg=DarkGreen guibg=Green
      syn match Shape5 "<>"
      hi Shape6 term=reverse ctermfg=DarkMagenta ctermbg=Magenta guifg=DarkRed guibg=Red
      syn match Shape6 "$\$"
    
      hi Flash term=reverse ctermfg=DarkBlue ctermbg=Blue guifg=LightBlue guibg=Blue
    
      let n="\<Esc>9hji"
      let v1="/--------\\".n
      let f="|........|".n
      let v2="\\--------/".n
      exe "norm! 21\<C-w>_50\<C-W>|"
      exe "norm! 1G32\<Bar>i".v1.f.f.f.f.v2."\<Esc>8G32\<Bar>iScore:\<Esc>j2h6i0\<Esc>"
      exe "norm! jj32\<Bar>iKeys:\<Esc>bji'z,c': Left, Right\<Esc>2F'jix,(nm,): Down, Rotate\<Esc>"
      exe "norm! Fxji's': Drop\<Esc>2F'ji'+,=':  Speed up"
      exe "norm! 2F+jiq,q: Pause, Quit\<esc>"
      if !exists('s:CNT')
      let s:CNT=0
      echon '' | echon 'Patience! Calibrating delay...'
      let t0=localtime()
      let t1=t0|wh t1==t0|let t1=localtime()|endw
      let t0=t1|wh t1==t0|let t0=localtime()|cal s:Loop('h')|let s:CNT=s:CNT+1|endw
      let t0=localtime()
      let t1=t0|wh t1==t0|let t1=localtime()|endw
      let one=1|let s:CNT2=0|let t0=t1
      wh t1==t0
      let s:CNT2=s:CNT2+1|let t0=localtime()|exe 'sleep' one.'m'
      endw
      let s:DELAY=(1000/s:CNT)-((1000-s:CNT2)/s:CNT2)
      let s:DELAY2=0
      if s:DELAY<0
      echo 'Hmmm. Loop execution needs more time, than exe "sleep" one."m"'
      let s:DELAY2=-s:DELAY
      let s:DELAY=1
      let s=s:CNT
      let s:CNT=s:CNT2
      let s:CNT2=s
      en
      en
      let s:DTIME=500
      let s:COUNTER=(s:CNT*s:DTIME)/1000
      echon 'Delay:'.s:DELAY.' Counter: '.s:COUNTER
      if !exists('s:name') || s:name==''
      let s:name=strpart(inputdialog("What's your name?\nIt will be used in the top10 list: "),0,30)
      en
      let s:mode=confirm('Game mode',"Traditional\nRotating")-1 "0=Trad, 1=Rotating
    endf
    
    fu! s:Loop(c)
      let c=a:c
      cal s:Put(b:y,b:x,b:pos,s:CLEAR)
      if c=~ '[zxcnm,]'
      let nx=b:x+((c=='z')?-1:((c=='c')?1:0))
      let ny=b:y+((c=='x')?1:0)
      let npos=(c!~'[nm,]')?(b:pos):(c=='m'?((b:pos+3)%4):((b:pos+1)%4))
      if s:Put(ny,nx,npos,s:CHECK)
      let b:x=nx
      let b:y=ny
      let b:pos=npos
      endif
      elsei c=='s'
      wh s:Put(b:y+1,b:x,b:pos,s:CHECK)
      let b:y=b:y+1
      endw
      elsei c=="\<Esc>" || c=='q'
      cal s:End()
      retu 2
      elsei c=~'[+=]'
      if s:COUNTER-10>0
      let s:COUNTER=s:COUNTER-10
      en
      en
      cal s:Put(b:y,b:x,b:pos,s:DRAW)
      redr
      if c=='p'|retu s:Pause()|en
      retu 0
    endf
    
    fu! s:Main()
      if !buflisted(s:s)
      cal s:Init()
      el
      let s:ow=bufnr('%')
      if bufwinnr(bufnr(s:s))==-1
      new|exe 'b' bufnr(s:s)
      en
      cal s:Resume()
      unlet s:starttime
      en
      setl ma
      let CURRXPOS=6 | let CURRYPOS=1
      wh 1
      wh 1
      let s:TICKS=s:TICKS+1|let cnt=s:COUNTER
      wh cnt
      let cnt=cnt-1
      let c=getchar(0)
      if c
      let c=nr2char(c)|let r=s:Loop(c)
      if r|retu r|en
      if s:DELAY2
      exe 'sl' s:DELAY2.'m'
      en
      el
      exe 'sl '.s:DELAY.'m'
      en
      endw
      "timeout
      cal s:Put(b:y,b:x,b:pos,s:CLEAR)
      " try to move down
      if !s:Put(b:y+1,b:x,b:pos,s:CHECK)
      cal s:Put(b:y,b:x,b:pos,s:DRAW)|brea
      en
      let b:y=b:y+1|cal s:Put(b:y,b:x,b:pos,s:DRAW)|redr
      endw
      cal s:Cnt(1)
      if s:mode
      exe "norm!1G7|\<C-V>19j2ld18lP"
      en
      cal s:Put(s:NEXTYPOS,s:NEXTXPOS,0,s:CLEAR)
      let b:sh=b:nsh|let b:col=b:ncol
      let b:nsh=(localtime()+8*b:sh)%s:shs
      let b:ncol=b:nsh
      let s:sh{b:nsh}=s:sh{b:nsh}+1
      let b:pos=0
      cal s:Put(s:NEXTYPOS,s:NEXTXPOS,0,s:DRAW)
      let b:x=CURRXPOS|let b:y=CURRYPOS
      if !s:Put(b:y,b:x,b:pos,s:CHECK)
      cal s:End()
      retu 0
      en
      cal s:Put(b:y,b:x,b:pos,s:DRAW)
      redr
      endw
    endf
    
    nmap <Leader>te :cal <SID>Main()<CR>
     
  6. Cool. Can you help out a vim noob and explain exactly how to run this?
    I looked at vim.org's page and didn't really understand the instructions..
     
  7. Sure!
    First of all, download the file from vim.org (or create a file called TeTrIs.vim with the code content from the post I made just above yours) and place it in your directory in ~/.vim/plugin
    (If the .vim and .vim/plugin folders don't exist, simply make them using mkdir)

    Once that is done, open vim by doing 'vim' in your terminal. You must source the game so that vim knows about its existence, by doing in vim
    :source ~/.vim/plugin/TeTrIs.vim
    (make sure you don't forget the colon)

    By then, the game will be installed in vim's memory. To start it, press the following keys in order: \te
    The game will start, asking you your name for high scores and the mode you want to play (select traditional, because rotating does not work). For the rest, the instructions from my post above or on vim.org should be clear enough.

    Hope that helps, and let me know if you have more questions :)
     
    Jayce, Tomek and Betelgeuse like this.
  8. Great stuff! I use vim every day and this looks like another fine addition to horrify my colleagues with :D
     

Share This Page