├── 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 | ![screenshot](https://user-images.githubusercontent.com/13008913/111640012-bcad0800-8836-11eb-85c3-bf20af90c1ba.png) 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