├── README.md
├── autoload
└── mineswp.vim
├── plugin
└── mineswp.vim
└── syntax
└── mineswp.vim
/README.md:
--------------------------------------------------------------------------------
1 | # vim-mine-sweeping
2 | mine sweeping game in vim and neovim
3 |
4 | ## ScreenShot
5 | 
6 | -
7 |
8 |
9 |
10 | ## Installation
11 | ```vimscript
12 | " vim-plug
13 | Plug 'iqxd/vim-mine-sweeping'
14 | ```
15 | ```lua
16 | -- packer
17 | use 'iqxd/vim-mine-sweeping'
18 | ```
19 |
20 | ## Usage
21 | * Launch `Vim Mine Sweeping` with command `:MineSweep`
22 | * Press h, j, k, l or `arrow key` to move between cells
23 | * Press c or `double left-click mouse` to reveal a cell,
24 | * Press f or `right-click mouse` to toggle flag on a cell
25 | * Press n g to start a new game
26 | * Press p b to print all mines and numbers in board
27 | * Press ? to toggle help
28 | * Press Z Z to exit current game
29 |
30 | ## Options
31 | Command `:MineSweep` can be called with following arugments:
32 | > `:MineSweep` `easy | medium | hard | row col` `-e | -n | -v | -t`
33 | * `easy` 9 x 9 board
34 | * `medium` 16 x 16 board
35 | * `hard` 24 x 24 board
36 | * `row col` user defined row x col board
37 | * `-e` create board in current window
38 | * `-n` create board in new split window
39 | * `-v` create board in new vsplit window
40 | * `-t` create board in new tabpage
41 | * `-f` create board in new floating window (neovim only)
42 |
43 | The default `:MineSweep` are equal to command with arguments like:
44 | > `:MineSweep` `12 20` `-v`
45 |
46 | which create game with 12 x 20 board in a new vsplit window
47 |
48 | You can also map the command in vimrc like below
49 | ```vimscript
50 | nnoremap :MineSweep medium -t
51 | ```
52 |
53 | ---
54 | **Enjoy! :)**
55 |
56 |
57 |
--------------------------------------------------------------------------------
/autoload/mineswp.vim:
--------------------------------------------------------------------------------
1 | let s:logo = '*'
2 | let s:title = 'Vim Mine Sweeping Score:'
3 | let s:win = 'You Won The Game! Score:'
4 | let s:lose = 'You Lost The Game Score:'
5 | let s:help = 'Toggle Help : ?'
6 | let s:help1 = "[Move] : h j k l or ← ↓ ↑ →"
7 | let s:help2 = '[Reveal] : c or <2-leftmouse>'
8 | let s:help3 = '[Flag] : f or '
9 | let s:help4 = '[NewGame] : ng'
10 | let s:help5 = '[PrintBoard] : pb'
11 |
12 | function! s:create_board()
13 | setlocal modifiable
14 | silent! normal! gg_dG
15 | call setline(1,'')
16 |
17 | let nrow = b:nrow
18 | let ncol = b:ncol
19 | let topline = "┌" .. repeat("───┬",ncol-1) .. "───┐"
20 |
21 | let b:boardwidth = strdisplaywidth(topline)
22 |
23 | " add title line
24 | let b:titleline = line('$') + 1
25 | call s:set_titleline(s:title,0)
26 | call append(line('$'),'')
27 | let b:startline = line('$')
28 |
29 | call append(line('$'),topline)
30 | let labelrow = "" .. repeat("│ ",ncol) .. "│"
31 | let linerow = "├" .. repeat("───┼",ncol-1) .. "───┤"
32 | for _ in range(nrow-1)
33 | call append(line('$'),labelrow)
34 | call append(line('$'),linerow)
35 | endfor
36 | call append(line('$'),labelrow)
37 |
38 | let botline = "└" .. repeat("───┴",ncol-1) .. "───┘"
39 | call append(line('$'),botline)
40 |
41 | " set cursor initial postion
42 | let [iline,ivcol] = s:get_window_pos_from_board_pos((b:nrow-1)/2,(b:ncol-1)/2)
43 | let bytescol = strlen(strcharpart(getline(iline),0,ivcol))
44 | call cursor(iline,bytescol)
45 |
46 | " add help line
47 | call append(line('$'),'')
48 | let b:helpline = line('$') + 1
49 | call s:set_helpline()
50 |
51 | setlocal nomodifiable
52 | endfunction
53 |
54 | function! s:set_titleline(text,score)
55 | let title = printf("%s %s%d/%d %s",s:logo,a:text,a:score,b:total,s:logo)
56 | let titlewidth = strdisplaywidth(title)
57 | if titlewidth >= b:boardwidth
58 | if exists("b:fwin")
59 | if (b:total >=10 && b:total<100 && a:score < 10 ) || (b:total>=100 && a:score >=10 && a:score <100)
60 | let title = title[:-2] .. ' ' .. s:logo
61 | elseif (b:total >=100 && a:score < 10 )
62 | let title = s:logo..' '..title[1:-2]..' '..s:logo
63 | endif
64 | endif
65 | call setline(b:titleline,title)
66 | else
67 | let rest = b:boardwidth - titlewidth
68 | let leftfills = repeat(s:logo,rest/2)
69 | let rightfills = repeat(s:logo,rest/2+rest%2)
70 | call setline(b:titleline,leftfills..title..rightfills)
71 | endif
72 | endfunction
73 |
74 | function! s:set_helpline()
75 | let fillchar = '-'
76 | let helptext = printf("%s %s %s",fillchar,s:help,fillchar)
77 | let helpwidth = strdisplaywidth(helptext)
78 | if helpwidth >= b:boardwidth
79 | call setline(b:helpline,helptext)
80 | else
81 | let rest = b:boardwidth - helpwidth
82 | let leftfills = repeat(fillchar,rest/2)
83 | let rightfills = repeat(fillchar,rest/2+rest%2)
84 | call setline(b:helpline,leftfills..helptext..rightfills)
85 | endif
86 | endfunction
87 |
88 | function! s:get_board_pos_from_window_pos(wline,wvcol)
89 | " wvcol should be virtcol number
90 | let loffset = (a:wline - b:startline) % 2
91 | if loffset == 0
92 | let grow = (a:wline- b:startline) / 2 - 1
93 | if grow >= b:nrow
94 | return [-1,-1]
95 | endif
96 | else
97 | " screen pos on gird line not in cell
98 | return [-1,-1]
99 | endif
100 | let voffset = (a:wvcol - 1)%4
101 | if voffset > 0
102 | let gcol = (a:wvcol-1)/4
103 | if gcol >= b:ncol
104 | return [-1,-1]
105 | else
106 | return [grow,gcol]
107 | endif
108 | else
109 | " screen pos on gird line not in cell
110 | return [-1,-1]
111 | endif
112 | endfunction
113 |
114 | function! s:get_window_pos_from_board_pos(grow,gcol)
115 | let wline = (a:grow + 1) * 2 + b:startline
116 | let wvcol = (a:gcol * 4) + 3
117 | " wvcol is virtcol number
118 | return [wline,wvcol]
119 | endfunction
120 |
121 | function! s:get_rand_numbers(low, high, numcount,skipnums) abort
122 | let numbers = {}
123 | let numrange = a:high-a:low+1
124 | if exists("*rand")
125 | let seed = srand()
126 | for _ in range(a:numcount)
127 | let randnum = rand(seed) % numrange + a:low
128 | let numbers[randnum] = 1
129 | endfor
130 | elseif has("nvim")
131 | call luaeval('math.randomseed(os.time())')
132 | for _ in range(a:numcount)
133 | let randnum = luaeval(printf("math.random(%d,%d)",a:low,a:high))
134 | let numbers[randnum] = 1
135 | endfor
136 | else
137 | for _ in range(a:numcount)
138 | let milisec = str2nr(matchstr(reltimestr(reltime()), '\v\.\zs\d+'))
139 | let randnum = milisec % numrange + a:low
140 | let numbers[randnum] = 1
141 | endfor
142 | endif
143 | for skipnum in a:skipnums
144 | if has_key(numbers,skipnum)
145 | unlet numbers[skipnum]
146 | endif
147 | endfor
148 | let n = []
149 | for k in keys(numbers)
150 | call add(n,str2nr(k))
151 | endfor
152 | " echom n
153 | return n
154 | endfunction
155 |
156 | function! s:create_mine(grow,gcol)
157 | " (grow, gcol) is the first sweep cell , should be safe on the first try
158 | " for easier start in early game , first try is better to be emtpy cell (0),
159 | " like windows minesweeper does
160 | let safenums = []
161 | for [x,y] in [[0,0],[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]]
162 | let srow = a:grow + y
163 | let scol = a:gcol + x
164 | if srow>=0 && srow=0 && scol=0 && arow=0 && acol curvcol
314 | return "l"
315 | elseif newvcol < curvcol
316 | return "h"
317 | else
318 | return "hl"
319 | endif
320 | else
321 | let [wline,newvcol] = s:get_window_pos_from_board_pos(grow,gcol+1)
322 | return (newvcol-curvcol).."l"
323 | endif
324 | endfunction
325 |
326 | function! s:move_left() abort
327 | let curvcol = virtcol('.')
328 | let [grow,gcol] = s:get_board_pos_from_window_pos(line('.'),curvcol)
329 | if grow == -1 && gcol == -1
330 | return "2h"
331 | elseif gcol == 0
332 | let [wline,newvcol] = s:get_window_pos_from_board_pos(grow,gcol)
333 | if newvcol > curvcol
334 | return "l"
335 | elseif newvcol < curvcol
336 | return "h"
337 | else
338 | return "lh"
339 | endif
340 | else
341 | let [wline,newvcol] = s:get_window_pos_from_board_pos(grow,gcol-1)
342 | return (curvcol-newvcol).."h"
343 | endif
344 | endfunction
345 |
346 | function! s:move_down() abort
347 | let curline = line('.')
348 | let curvcol = virtcol('.')
349 | let [grow,gcol] = s:get_board_pos_from_window_pos(curline,curvcol)
350 | if grow == -1 && gcol == -1
351 | return "j"
352 | else
353 | let [wline,newvcol] = s:get_window_pos_from_board_pos(grow,gcol)
354 | if newvcol > curvcol
355 | let rowmove = "l"
356 | elseif newvcol < curvcol
357 | let rowmove = "h"
358 | else
359 | let rowmove = ""
360 | endif
361 | if grow == b:nrow -1
362 | return rowmove == "" ? "kj" : rowmove
363 | else
364 | return rowmove .. "2j"
365 | endif
366 | endif
367 | endfunction
368 |
369 | function! s:move_up() abort
370 | let curline = line('.')
371 | let curvcol = virtcol('.')
372 | let [grow,gcol] = s:get_board_pos_from_window_pos(curline,curvcol)
373 | if grow == -1 && gcol == -1
374 | return "k"
375 | else
376 | let [wline,newvcol] = s:get_window_pos_from_board_pos(grow,gcol)
377 | if newvcol > curvcol
378 | let rowmove = "l"
379 | elseif newvcol < curvcol
380 | let rowmove = "h"
381 | else
382 | let rowmove = ""
383 | endif
384 | if grow == 0
385 | return rowmove == "" ? "jk" : rowmove
386 | else
387 | return rowmove .. "2k"
388 | endif
389 | endif
390 | endfunction
391 |
392 | function s:toggle_help()
393 | setlocal modifiable
394 | if line('$') == b:helpline
395 | call append(line('$'),s:help1)
396 | call append(line('$'),s:help2)
397 | call append(line('$'),s:help3)
398 | call append(line('$'),s:help4)
399 | call append(line('$'),s:help5)
400 | else
401 | silent! normal! ma
402 | call cursor(b:helpline+1,1)
403 | silent! normal! dG
404 | silent! normal! `a
405 | endif
406 | setlocal nomodifiable
407 | endfunction
408 |
409 | function! s:open_float_win(fwin_width,fwin_height)
410 | let height = a:fwin_height
411 | let row = float2nr((&lines -2 - height) / 2)
412 | let width = a:fwin_width
413 | let col = float2nr((&columns - width) / 2)
414 |
415 | let opts = {
416 | \ 'relative': 'editor',
417 | \ 'style': 'minimal',
418 | \ 'width': width,
419 | \ 'height': height,
420 | \ 'col': col,
421 | \ 'row': row,
422 | \ 'border' : 'single',
423 | \ }
424 |
425 | let buf = nvim_create_buf(v:false, v:true)
426 | let win = nvim_open_win(buf, v:true, opts)
427 | let b:fwin = win
428 | endfunction
429 |
430 |
431 | " for test
432 | function! s:print_board()
433 | if b:score == 0
434 | echom "Reveal a cell before print the board"
435 | else
436 | setlocal modifiable
437 | for arow in range(b:nrow)
438 | let linetext = []
439 | for acol in range(b:ncol)
440 | let label = b:board[arow..' '..acol]
441 | if label == 0
442 | let label = '-'
443 | elseif label == -1
444 | let label = '*'
445 | else
446 | let label = string(label)
447 | endif
448 | call add(linetext," "..label.." ")
449 | endfor
450 | call append('$',join(linetext))
451 | endfor
452 | setlocal nomodifiable
453 | endif
454 | endfunction
455 |
456 | function! s:start_game()
457 | call s:init_setting()
458 | let b:nrow = s:nrow
459 | let b:ncol = s:ncol
460 | let b:nmine = s:nmine
461 | let b:total = s:nrow * s:ncol
462 | call s:new_game()
463 | " echom b:board
464 | endfunction
465 |
466 | function! s:init_setting()
467 | setlocal buftype=nofile bufhidden=wipe nobuflisted nomodifiable nolist noswapfile
468 | \ nowrap nocursorline nocursorcolumn nospell maxfuncdepth=199
469 | \ encoding=utf8 noautoindent nosmartindent t_Co=256 mouse=a
470 | setfiletype mineswp
471 | nnoremap <2-leftmouse> :call reveal_cell(0)
472 | nnoremap :call reveal_cell(1)
473 | nnoremap c :call reveal_cell(0)
474 | nnoremap f :call reveal_cell(1)
475 | nnoremap ng :call new_game()
476 | nnoremap ? :call toggle_help()
477 | nnoremap pb :call print_board()
478 |
479 | nnoremap h move_left()
480 | nnoremap l move_right()
481 | nnoremap j move_down()
482 | nnoremap k move_up()
483 | nnoremap move_left()
484 | nnoremap move_right()
485 | nnoremap move_down()
486 | nnoremap move_up()
487 | endfunction
488 |
489 | function! mineswp#start(...) abort
490 | if a:0 >=1 && a:1 ==? "easy"
491 | let s:nrow = 9
492 | let s:ncol = 9
493 | let s:nmine = 11
494 | elseif a:0 >=1 && a:1 ==? "medium"
495 | let s:nrow = 16
496 | let s:ncol = 16
497 | let s:nmine = 42
498 | elseif a:0 >=1 && a:1 ==? "hard"
499 | let s:nrow = 24
500 | let s:ncol = 24
501 | let s:nmine = 102
502 | elseif a:0 >= 2 && a:1 =~ '\d\+' && a:2 =~ '\d\+' && a:1 >= 1 && a:1 <= 30 && a:2 >= 1 && a:2 <= 30
503 | let s:nrow = str2nr(a:1)
504 | let s:ncol = str2nr(a:2)
505 | let s:nmine = float2nr(s:nrow * s:ncol * 0.18)
506 | else
507 | let s:nrow = 12
508 | let s:ncol = 20
509 | let s:nmine = float2nr(s:nrow * s:ncol * 0.18)
510 | endif
511 |
512 | if a:0 != 0
513 | let winloc = a:000[-1]
514 | if winloc ==? '-n'
515 | new " split window
516 | elseif winloc ==? '-v'
517 | vnew " vsplit window
518 | elseif winloc ==? '-t'
519 | tabnew " tabpage
520 | elseif winloc ==? '-e'
521 | enew " current window
522 | elseif winloc ==? '-f' && has("nvim")
523 | " float window
524 | let fw_height = s:nrow*2+7
525 | let fw_width = s:ncol*4+1
526 | let fw_width_min = len(s:title)+6
527 | let total = s:nrow*s:ncol
528 | if total < 10
529 | let fw_width_min += 3
530 | elseif total < 100
531 | let fw_width_min += 5
532 | else
533 | let fw_width_min += 7
534 | endif
535 | if fw_width < fw_width_min
536 | let fw_width = fw_width_min
537 | endif
538 | call s:open_float_win(fw_width,fw_height)
539 | else
540 | vnew " default
541 | endif
542 | else
543 | vnew " default
544 | endif
545 | call s:start_game()
546 | endfunction
547 |
--------------------------------------------------------------------------------
/plugin/mineswp.vim:
--------------------------------------------------------------------------------
1 | " nnoremap