Repository: vim/killersheep
Branch: master
Commit: e91801129d2d
Files: 12
Total size: 18.4 KB
Directory structure:
gitextract_9tqvlwg2/
├── .gitignore
├── LICENSE
├── README.md
├── autoload/
│ └── killersheep.vim
└── plugin/
├── beh.ogg
├── fail.ogg
├── fire.ogg
├── killersheep.vim
├── music.ogg
├── poop.ogg
├── quack.ogg
└── win.ogg
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# All platforms
*.rej
*.orig
*.mo
*.swp
*~
# Mac OSX
.DS_Store
================================================
FILE: LICENSE
================================================
The Killersheep game goes under the same license as Vim.
You can find the text here:
https://github.com/vim/vim/blob/master/runtime/doc/uganda.txt
It basically means you can copy the files as you like and also re-distribute
them, so long as you keep the license.
================================================
FILE: README.md
================================================
# killersheep
Silly game to show off the new features of Vim 8.2:
- Popup windows with colors and mask
- Text properties to highlight text
- Sound
Installation
------------
Use your favorite plugin manager.
For example, using [vim-plug](https://github.com/junegunn/vim-plug):
```vim
Plug 'vim/killersheep'
```
Or download the files using the zip archive, and unpack them in your
pack directory `~/.vim/pack/mine/opt/killersheep/`.
Then load the pack manually with:
```vim
packadd killersheep
```
Or put this in your vimrc:
```vim
packadd! killersheep
```
How to play
-----------
First of all: make sure you can hear the sound (or put on your headphones if
you don't want your friends/colleagues to find out what you are doing).
```vim
:KillKillKill
```
Or, if you don't have conflicting commands, just:
```vim
:Kill
```
In the game:
| Key | Description |
| ------- | ----------------- |
| l | move cannon right |
| h | move cannon left |
| Space | fire cannon |
| Esc | exit game |
Requirements
------------
- Vim 8.2
- feature +textprop
- feature +sound or command "afplay", "paplay" or "cvlc".
- terminal with at least 45 lines
================================================
FILE: autoload/killersheep.vim
================================================
" Implementation of the silly game
" Use :KillKillKill to start
"
" Last Update: 2019 Dec 7
let s:did_init = 0
let s:sound_cmd = ''
func killersheep#Start(sounddir)
let s:dir = a:sounddir
if !has('sound')
if executable('afplay')
" Probably on Mac
let s:sound_cmd = 'afplay'
let g:killersheep_sound_ext = '.mp3'
elseif executable('paplay')
" Probably on Unix
let s:sound_cmd = 'paplay'
let g:killersheep_sound_ext = '.ogg'
elseif executable('cvlc')
" Probably on Unix
let s:sound_cmd = 'cvlc --play-and-exit'
let g:killersheep_sound_ext = '.ogg'
else
echomsg 'This build of Vim is lacking sound support, you are missing out!'
sleep 2
endif
endif
if !s:did_init
let s:did_init = 1
call s:Init()
endif
call s:Clear()
call s:Intro()
endfunc
func s:NoProp(text)
return #{text: a:text, props: []}
endfunc
func s:Intro()
hi SheepTitle cterm=bold gui=bold
hi introHL ctermbg=cyan guibg=cyan
call prop_type_delete('sheepTitle')
call prop_type_add('sheepTitle', #{highlight: 'SheepTitle'})
call prop_type_delete('introHL')
call prop_type_add('introHL', #{highlight: 'introHL'})
let s:intro = popup_create([
\ #{text: ' The sheep are out to get you!',
\ props: [#{col: 4, length: 29, type: 'sheepTitle'}]},
\ s:NoProp(''),
\ s:NoProp('In the game:'),
\ #{text: ' h move cannon left',
\ props: [#{col: 6, length: 1, type: 'sheepTitle'}]},
\ #{text: ' l move cannon right',
\ props: [#{col: 6, length: 1, type: 'sheepTitle'}]},
\ #{text: ' <Space> fire',
\ props: [#{col: 3, length: 7, type: 'sheepTitle'}]},
\ #{text: ' <Esc> quit (colon also works)',
\ props: [#{col: 4, length: 5, type: 'sheepTitle'}]},
\ s:NoProp(''),
\ #{text: 'Now press s to start or x to exit',
\ props: [#{col: 12, length: 1, type: 'sheepTitle'},
\ #{col: 28, length: 1, type: 'sheepTitle'}]},
\ ], #{
\ filter: function('s:IntroFilter'),
\ callback: function('s:IntroClose'),
\ border: [],
\ padding: [],
\ mapping: 0,
\ drag: 1,
\ close: 'button',
\ })
if has('sound') || len(s:sound_cmd)
let s:keep_playing = 1
call s:PlayMusic()
endif
call s:IntroHighlight(0)
endfunc
const s:introHL = [[4, 3], [8, 5], [14, 3], [18, 3], [22, 2], [25, 3], [29, 4]]
let s:intro_timer = 0
func s:IntroHighlight(idx)
let idx = a:idx
if idx >= len(s:introHL)
let idx = 0
endif
let buf = winbufnr(s:intro)
call prop_remove(#{type: 'introHL', bufnr: buf}, 1)
call prop_add(1, s:introHL[idx][0],
\ #{length: s:introHL[idx][1], bufnr: buf, type: 'introHL'})
let s:intro_timer = timer_start(300, { -> s:IntroHighlight(idx + 1)})
endfunc
func s:IntroFilter(id, key)
if a:key == 's' || a:key == 'S'
call s:Clear()
let s:round = 0
let s:ready = popup_create('Get Ready!', #{border: [], padding: [2, 4, 2, 4]})
call s:BlinkLevel(s:ready, 1)
call timer_start(s:blink_time * 8, { -> s:NextRound()})
let s:ready_timer = timer_start(300, {... -> s:ReadySound()})
elseif a:key == 'x' || a:key == 'X' || a:key == "\<Esc>"
call s:Clear()
endif
return 1
endfunc
func s:ReadySound()
call s:PlaySound('quack')
let s:ready_timer = timer_start(s:blink_time * 2, {... -> s:ReadySound()})
endfunc
func s:IntroClose(id, res)
call s:Clear()
endfunc
" Play the music in a loop
func s:PlayMusic()
if s:keep_playing
let fname = s:dir .. '/music' .. g:killersheep_sound_ext
if has('sound')
let s:music_id = sound_playfile(fname, {x -> s:PlayMusic()})
elseif len(s:sound_cmd)
let s:music_job = job_start(s:sound_cmd .. ' ' .. fname)
" Detecting job exit is a bit slow, use a timer to loop.
let s:music_timer = timer_start(14100, {x -> s:PlayMusic()})
endif
endif
endfunc
func s:StopMusic()
let s:keep_playing = 0
if has('sound')
call sound_clear()
elseif len(s:sound_cmd) && exists('s:music_job')
call job_stop(s:music_job)
call timer_stop(s:music_timer)
unlet s:music_job s:music_timer
endif
endfunc
func s:Init()
hi def KillerCannon ctermbg=blue guibg=blue
hi def KillerBullet ctermbg=red guibg=red
hi def KillerSheep ctermfg=black ctermbg=green guifg=black guibg=green
hi def KillerSheep2 ctermfg=black ctermbg=cyan guifg=black guibg=cyan
if &bg == 'light'
hi def KillerPoop ctermbg=black guibg=black
else
hi def KillerPoop ctermbg=white guibg=white
endif
hi def KillerPoop ctermbg=black guibg=black
hi def KillerLevel ctermbg=magenta guibg=magenta
hi def KillerLevelX ctermbg=yellow guibg=yellow
if !exists('g:killersheep_sound_ext')
if has('win32') || has('osx') || len(s:sound_cmd)
" most systems can play MP3 files
let g:killersheep_sound_ext = ".mp3"
else
" libcanberra defaults to supporting ogg only
let g:killersheep_sound_ext = ".ogg"
endif
endif
endfunc
func s:NextRound()
call s:Clear()
let s:round += 1
let s:sheepcount = 0
let s:frozen = 0
call s:ShowBulletSoon()
" Every once in a while let the next sheep that moves poop.
let s:wantpoop = 0
let s:poop_timer = timer_start(s:poop_interval[s:round - 1], {x -> s:WantPoop()}, #{repeat: -1})
" Create a few sheep to kill.
let topline = &lines > 50 ? &lines - 50 : 0
call s:PlaceSheep(topline + 0, 5, 'KillerSheep')
call s:PlaceSheep(topline + 5, 75, 'KillerSheep2')
call s:PlaceSheep(topline + 7, 35, 'KillerSheep')
call s:PlaceSheep(topline + 10, 15, 'KillerSheep')
call s:PlaceSheep(topline + 12, 70, 'KillerSheep')
call s:PlaceSheep(topline + 15, 55, 'KillerSheep2')
call s:PlaceSheep(topline + 20, 15, 'KillerSheep2')
call s:PlaceSheep(topline + 21, 30, 'KillerSheep')
call s:PlaceSheep(topline + 22, 60, 'KillerSheep2')
call s:PlaceSheep(topline + 28, 0, 'KillerSheep')
call s:ShowLevel(topline)
let s:canon_id = popup_create([' /#\ ', ' /###\ ', '/#####\'], #{
\ line: &lines - 2,
\ highlight: 'KillerCannon',
\ filter: function('s:MoveCanon'),
\ zindex: s:cannon_zindex,
\ mask: [[1,2,1,1], [6,7,1,1], [1,1,2,2], [7,7,2,2]],
\ mapping: 0,
\ })
endfunc
func s:ShowLevel(line)
let s:levelid = popup_create('Level ' .. s:round, #{
\ line: a:line ? a:line : 1,
\ border: [],
\ padding: [0,1,0,1],
\ highlight: 'KillerLevel'})
endfunc
func s:MoveCanon(id, key)
if s:frozen
return
endif
let pos = popup_getpos(a:id)
let move = 0
if a:key == 'h' && pos.col > 1
let move = pos.col - 2
endif
if a:key == 'l' && pos.col < &columns - 6
let move = pos.col + 2
endif
if move != 0
call popup_move(a:id, #{col: move})
if s:bullet_available
call popup_move(s:bullet_available, #{col: move + 3})
endif
endif
if a:key == ' '
call s:Fire(pos.col + 3)
endif
if a:key == "\e" || a:key == 'x' || a:key == ':'
call s:Clear()
endif
return a:key != ':'
endfunc
const s:bullet_holdoff = 800
const s:bullet_delay = 30
const s:poop_delay = 60
const s:sheep_anim = 40
const s:sheep_explode = 150
const s:cannon_zindex = 100
const s:bullet_zindex = 80
const s:sheep_zindex = 90
const s:poop_interval = [700, 500, 300, 200, 100]
const s:poop_countdown = 300 / s:sheep_anim
const s:blink_time = 300
const s:sheepSprite = [[
\ ' o^^) /^^^^^^\ ',
\ '==___ |',
\ ' \ ___ _/',
\ ' || || '],[
\ ' o^^) /^^^^^^\ ',
\ '==___ |',
\ ' \_ ____ _/',
\ ' | | '],[
\ ' o^^) /^^^^^^\ ',
\ '==___ |',
\ ' \ ___ _/',
\ ' || || '],[
\ ' o^^) /^^^^^^\ ',
\ '==___ |',
\ ' \ _ __ _ /',
\ ' / | / | '],[
\ ' /^^^^^^\ ',
\ ' | |',
\ ' O^^) ',
\ 'xx___ _ |',
\ ' \ _____ _/',
\ ' || || '],[
\ ' /^^^^^^\ ',
\ ' | |',
\ ' ',
\ ' O^^) ',
\ 'XX___ ',
\ ' \ __ _ _/',
\ ' || || ']]
const s:sheepSpriteMask = [[
\ ' xxxx xxxxxxxx ',
\ 'xxxxxxxxxxxxxxx',
\ ' xxxxxxxxxx',
\ ' xx xx '],[
\ ' xxxx xxxxxxxx ',
\ 'xxxxxxxxxxxxxxx',
\ ' xxxxxxxxxx',
\ ' x x '],[
\ ' xxxx xxxxxxxx ',
\ 'xxxxxxxxxxxxxxx',
\ ' xxxxxxxxxx',
\ ' xx xx '],[
\ ' xxxx xxxxxxxx ',
\ 'xxxxxxxxxxxxxxx',
\ ' xxxxxxxxxx',
\ ' x x x x '],[
\ ' xxxxxxxx ',
\ ' xxxxxxxxxx',
\ ' xxxx ',
\ 'xxxxx xxxxxxxxxxx',
\ ' xxxxxxxxxxx',
\ ' xx xx '],[
\ ' xxxxxxxx ',
\ ' xxxxxxxxxx',
\ ' ',
\ ' xxxx ',
\ 'xxxxx ',
\ ' xxxxxxxxxxx',
\ ' xx xx ']]
func GetMask(l)
let mask = []
for r in range(len(a:l))
let s = 0
let e = -1
let l = a:l[r]
for c in range(len(l))
if l[c] == ' '
let e = c
elseif e >= s
call add(mask, [s+1,e+1,r+1,r+1])
let s = c + 1
let e = c
else
let s = c + 1
endif
endfor
if e >= s
call add(mask, [s+1,e+1,r+1,r+1])
endif
endfor
return mask
endfunc
let s:sheepMasks = []
for l in s:sheepSpriteMask
call add(s:sheepMasks, GetMask(l))
endfor
func s:PlaceSheep(line, col, hl)
let id = popup_create(s:sheepSprite[0], #{
\ line: a:line,
\ col: a:col,
\ highlight: a:hl,
\ mask: s:sheepMasks[0],
\ fixed: 1,
\ zindex: s:sheep_zindex,
\ wrap: 0,
\})
call setwinvar(id, 'left', 0)
call setwinvar(id, 'dead', 0)
call timer_start(s:sheep_anim, {x -> s:AnimSheep(id, 1)})
let s:sheepcount += 1
sleep 10m
return id
endfunc
func s:AnimSheep(id, state)
if s:frozen
return
endif
let pos = popup_getpos(a:id)
if pos == {}
return
endif
if getwinvar(a:id, 'dead')
return
endif
let left = getwinvar(a:id, 'left')
if left == 1
if pos.line > &lines - 11
call s:PlaySoundForEnd()
endif
call popup_setoptions(a:id, #{pos: 'topleft', col: &columns + 1, line: pos.line + 5})
let left = 0
elseif pos.col > 1
call popup_move(a:id, #{col: pos.col - 1})
else
if left == 0
let left = 15
endif
call popup_setoptions(a:id, #{pos: 'topright', col: left - 1})
let left -= 1
endif
let poopid = getwinvar(a:id, 'poopid')
if poopid
let poopcount = getwinvar(a:id, 'poopcount')
if poopcount == 1
" drop the poop
call popup_close(poopid)
call setwinvar(a:id, 'poopid', 0)
call s:Poop(pos.line + 1, left ? left : pos.col + 12)
else
call popup_move(poopid, #{col: left ? left + 1 : pos.col + 14,
\ line: pos.line + 1})
endif
call setwinvar(a:id, 'poopcount', poopcount - 1)
endif
call setwinvar(a:id, 'left', left)
call popup_settext(a:id, s:sheepSprite[a:state])
call popup_setoptions(a:id, #{mask: s:sheepMasks[a:state]})
call timer_start(s:sheep_anim, {x -> s:AnimSheep(a:id, a:state == 3 ? 0 : a:state + 1)})
if left || pos.col < &columns - 14
if s:wantpoop && !getwinvar(a:id, 'poopid')
let s:wantpoop = 0
call setwinvar(a:id, 'poopcount', s:poop_countdown)
let poopid = popup_create('x', #{
\ col: left ? left + 1 : pos.col + 14,
\ line: pos.line + 1,
\ highlight: 'KillerPoop',
\ zindex: s:bullet_zindex,
\ })
call setwinvar(a:id, 'poopid', poopid)
endif
endif
endfunc
func s:KillSheep(id, state)
let pos = popup_getpos(a:id)
if pos == {}
return
endif
let poopid = getwinvar(a:id, 'poopid')
if poopid
call popup_close(poopid)
endif
let left = getwinvar(a:id, 'left')
if a:state == 6
let s:sheepcount -= 1
if s:sheepcount == 0
call s:PlaySoundForEnd()
endif
call popup_close(a:id)
return
endif
call popup_settext(a:id, s:sheepSprite[a:state])
call popup_setoptions(a:id, #{mask: s:sheepMasks[a:state], line: pos.line - 1, col: pos.col - 1})
call timer_start(s:sheep_explode, {x -> s:KillSheep(a:id, a:state + 1)})
call setwinvar(a:id, 'dead', 1)
endfunc
func s:WantPoop()
let s:wantpoop = 1
endfunc
func s:Poop(line, col)
if s:frozen
return
endif
let id = popup_create(['|', '|'], #{
\ col: a:col,
\ line: a:line,
\ highlight: 'KillerPoop',
\ zindex: s:bullet_zindex,
\ })
call s:PlaySound('poop')
call timer_start(s:poop_delay, {x -> s:MovePoop(x, id)}, #{repeat: -1})
endfunc
func s:MovePoop(x, id)
if s:frozen
return
endif
let pos = popup_getpos(a:id)
if pos == {}
call timer_stop(a:x)
return
endif
if pos.line >= &lines - 1
call popup_close(a:id)
call timer_stop(a:x)
else
call popup_move(a:id, #{line: pos.line + 2})
let winid = popup_locate(pos.line + 2, pos.col)
" TODO: no hit if no overlap
if winid != 0 && winid == s:canon_id
call s:PlaySoundForEnd()
endif
endif
endfunc
func s:ShowBulletSoon()
let s:bullet_available = 0
let s:bullet_timer = timer_start(s:bullet_holdoff, {x -> ShowBullet()})
endfunc
func ShowBullet()
if s:frozen
return
endif
let s:bullet_timer = 0
let pos = popup_getpos(s:canon_id)
let s:bullet_available = popup_create(['|', '|'], #{
\ col: pos.col + 3,
\ line: &lines - 3,
\ highlight: 'KillerBullet',
\ zindex: s:bullet_zindex,
\ })
endfunc
func s:Fire(col)
if s:frozen
return
endif
if !s:bullet_available
return
endif
let id = s:bullet_available
call s:ShowBulletSoon()
call s:PlaySound('fire')
call timer_start(s:bullet_delay, {x -> s:MoveBullet(x, id)}, #{repeat: -1})
endfunc
func s:MoveBullet(x, id)
if s:frozen
return
endif
let pos = popup_getpos(a:id)
if pos == {}
call timer_stop(a:x)
return
endif
if pos.line <= 2
call popup_close(a:id)
call timer_stop(a:x)
else
call popup_move(a:id, #{line: pos.line - 2})
let winid = popup_locate(pos.line - 2, pos.col)
if winid != 0 && winid != a:id
call s:KillSheep(winid, 4)
if s:sheepcount == 1
let s:frozen = 1
endif
call s:PlaySound('beh')
call popup_close(a:id)
endif
endif
endfunc
func s:PlaySound(name)
let fname = s:dir .. '/' .. a:name .. g:killersheep_sound_ext
if has('sound')
call sound_playfile(fname)
elseif len(s:sound_cmd)
call system(s:sound_cmd .. ' ' .. fname .. '&')
endif
endfunc
func s:BlinkLevel(winid, on)
call popup_setoptions(a:winid, #{highlight: a:on ? 'KillerLevelX': 'KillerLevel'})
let s:blink_timer = timer_start(s:blink_time, {x -> s:BlinkLevel(a:winid, !a:on)})
endfunc
func s:PlaySoundForEnd()
let s:frozen = 1
if s:sheepcount == 0
call s:PlaySound('win')
if s:round == 5
echomsg 'Amazing, you made it through ALL levels! (did you cheat???)'
let s:end_timer = timer_start(2000, {x -> s:Clear()})
else
call popup_settext(s:levelid, 'Level ' .. (s:round + 1))
call s:BlinkLevel(s:levelid, 1)
call timer_start(2000, {x -> s:NextRound()})
endif
else
call s:StopMusic()
call s:PlaySound('fail')
let s:end_timer = timer_start(4000, {x -> s:Clear()})
endif
endfunc
func s:Clear()
call s:StopMusic()
if s:intro_timer
call timer_stop(s:intro_timer)
let s:intro_timer = 0
endif
call popup_clear()
if get(s:, 'end_timer', 0)
call timer_stop(s:end_timer)
let s:end_timer = 0
endif
if get(s:, 'ready_timer', 0)
call timer_stop(s:ready_timer)
let s:ready_timer = 0
endif
if get(s:, 'poop_timer', 0)
call timer_stop(s:poop_timer)
let s:poop_timer = 0
endif
if get(s:, 'bullet_timer', 0)
call timer_stop(s:bullet_timer)
let s:bullet_timer = 0
endif
if get(s:, 'blink_timer', 0)
call timer_stop(s:blink_timer)
let s:blink_timer = 0
endif
endfunc
================================================
FILE: plugin/killersheep.vim
================================================
" Silly game to show off new features in Vim 8.2.
" Last Update: 2019 Dec 7
"
" Requirements:
" - feature +textprop
" - feature +sound or command "afplay", "paplay" or "cvlc".
" - Vim patch level 8.1.1705
" - terminal with at least 45 lines
"
" :KillKillKill start game
" l move cannon right
" h move cannot left
" <Space> fire
" <Esc> exit game
"
" By default plays .ogg files on Unix, .mp3 files on MS-Windows.
" Set g:killersheep_sound_ext to overrule, e.g.:
" let g:killersheep_sound_ext = '.mp3'
"
" Thanks to my colleagues Greg, Martijn and Shannon for the sounds!
"
if get(g:, 'loaded_killersheep', 0)
finish
endif
let g:loaded_killersheep = 1
let s:dir = expand('<sfile>:h')
command KillKillKill call s:StartKillerSheep()
func s:StartKillerSheep()
" Check features before loading the autoload file to avoid error messages.
if !has('patch-8.1.1705')
call s:Sorry('Sorry, This build of Vim is too old, you need at least 8.1.1705')
return
endif
if !has('textprop')
call s:Sorry('Sorry, This build of Vim is lacking the +textprop feature')
return
endif
if &lines < 45
call s:Sorry('Need at least a terminal height of 45 lines')
return
endif
" The implementation is in an autoload file, so that this plugin doesn't
" take much time when not being used.
call killersheep#Start(s:dir)
endfunc
func s:Sorry(msg)
echohl WarningMsg
echo a:msg
echohl None
endfunc
gitextract_9tqvlwg2/
├── .gitignore
├── LICENSE
├── README.md
├── autoload/
│ └── killersheep.vim
└── plugin/
├── beh.ogg
├── fail.ogg
├── fire.ogg
├── killersheep.vim
├── music.ogg
├── poop.ogg
├── quack.ogg
└── win.ogg
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (20K chars).
[
{
"path": ".gitignore",
"chars": 64,
"preview": "# All platforms\n*.rej\n*.orig\n*.mo\n*.swp\n*~\n\n# Mac OSX\n.DS_Store\n"
},
{
"path": "LICENSE",
"chars": 265,
"preview": "The Killersheep game goes under the same license as Vim.\nYou can find the text here: \nhttps://github.com/vim/vim/blob/ma"
},
{
"path": "README.md",
"chars": 1206,
"preview": "# killersheep\n\nSilly game to show off the new features of Vim 8.2:\n- Popup windows with colors and mask\n- Text prope"
},
{
"path": "autoload/killersheep.vim",
"chars": 15915,
"preview": "\" Implementation of the silly game\n\" Use :KillKillKill to start\n\"\n\" Last Update: 2019 Dec 7\n\nlet s:did_init = 0\nlet s:so"
},
{
"path": "plugin/killersheep.vim",
"chars": 1440,
"preview": "\" Silly game to show off new features in Vim 8.2.\n\" Last Update: 2019 Dec 7\n\"\n\" Requirements:\n\" - feature +textprop\n\" - "
}
]
// ... and 7 more files (download for full content)
About this extraction
This page contains the full source code of the vim/killersheep GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (18.4 KB), approximately 6.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.