├── .github ├── pr-labeler.yml └── workflows │ ├── pr-labeler.yml │ ├── test.yml │ └── release.yml ├── docs ├── othello_win.gif.gif ├── othello_linux.gif.gif └── release_workflow.puml ├── tests ├── config.nims ├── test_gameplay.nim └── test_unit.nim ├── .gitignore ├── .monit.yml ├── nimothello.nimble ├── .chglog ├── config.yml └── CHANGELOG.tpl.md ├── src ├── nimothello.nim └── nimothellopkg │ ├── views.nim │ └── models.nim ├── LICENSE ├── tool └── setup.nim └── README.rst /.github/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | feature: feature/* 2 | bug: hotfix/* 3 | chore: chore/* 4 | -------------------------------------------------------------------------------- /docs/othello_win.gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiro4989/nimothello/HEAD/docs/othello_win.gif.gif -------------------------------------------------------------------------------- /docs/othello_linux.gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiro4989/nimothello/HEAD/docs/othello_linux.gif.gif -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | switch("hints", "off") 3 | switch("d", "modeTest") 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | tests/test1 3 | nimcache 4 | tests/test_gameplay 5 | testresults/pattern.json 6 | tests/test_unit 7 | testresults/test_unit.nim 8 | -------------------------------------------------------------------------------- /.monit.yml: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | %TAG !n! tag:nimyaml.org,2016: 3 | --- !n!custom:MonitorConfig 4 | sleep: 1 5 | targets: 6 | - 7 | name: Task name 8 | paths: [src, tests] 9 | commands: 10 | - testament p "tests/*.nim" 11 | extensions: [.nim] 12 | files: [] 13 | exclude_extensions: [] 14 | exclude_files: [] 15 | once: y -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: labeler 2 | 3 | on: 4 | pull_request: 5 | types: [opened] 6 | 7 | jobs: 8 | labeler: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: TimonVS/pr-labeler-action@v3.1.0 12 | with: 13 | configuration-path: .github/pr-labeler.yml 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /nimothello.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "1.0.0" 4 | author = "jiro4989" 5 | description = "A teminal othello (reversi) in Nim." 6 | license = "MIT" 7 | srcDir = "src" 8 | bin = @["nimothello"] 9 | binDir = "bin" 10 | 11 | 12 | # Dependencies 13 | 14 | requires "nim >= 1.4.4" 15 | requires "illwill >= 0.2.0" 16 | 17 | task tests, "Run test": 18 | exec "testament p 'tests/*.nim'" 19 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/YOUR_NAME/REPOSITORY 6 | options: 7 | commits: 8 | filters: 9 | Type: 10 | - feat 11 | - fix 12 | - perf 13 | - refactor 14 | commit_groups: 15 | # title_maps: 16 | # feat: Features 17 | # fix: Bug Fixes 18 | # perf: Performance Improvements 19 | # refactor: Code Refactoring 20 | header: 21 | pattern: "^(\\w*)\\:\\s(.*)$" 22 | pattern_maps: 23 | - Type 24 | - Subject 25 | notes: 26 | keywords: 27 | - BREAKING CHANGE 28 | -------------------------------------------------------------------------------- /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ range .Versions }} 2 | ## Changes 3 | 4 | {{ range .CommitGroups -}} 5 | ### {{ .Title }} 6 | 7 | {{ range .Commits -}} 8 | * {{ .Subject }} 9 | {{ end }} 10 | {{ end -}} 11 | 12 | {{- if .RevertCommits -}} 13 | ### Reverts 14 | 15 | {{ range .RevertCommits -}} 16 | * {{ .Revert.Header }} 17 | {{ end }} 18 | {{ end -}} 19 | 20 | {{- if .MergeCommits -}} 21 | ### Pull Requests 22 | 23 | {{ range .MergeCommits -}} 24 | * {{ .Header }} 25 | {{ end }} 26 | {{ end -}} 27 | 28 | {{- if .NoteGroups -}} 29 | {{ range .NoteGroups -}} 30 | ### {{ .Title }} 31 | 32 | {{ range .Notes }} 33 | {{ .Body }} 34 | {{ end }} 35 | {{ end -}} 36 | {{ end -}} 37 | {{ end -}} 38 | -------------------------------------------------------------------------------- /src/nimothello.nim: -------------------------------------------------------------------------------- 1 | import illwill 2 | import nimothellopkg/[models, views] 3 | 4 | proc main() = 5 | var 6 | game = newGame() 7 | view = newGameView() 8 | 9 | while not game.isFinished(): 10 | let key = getKey() 11 | case key 12 | of Key.None: discard 13 | of Key.Escape: 14 | break 15 | of Key.H, Key.A: game.moveLeft() 16 | of Key.J, Key.S: game.moveDown() 17 | of Key.K, Key.W: game.moveUp() 18 | of Key.L, Key.D: game.moveRight() 19 | of Key.Space, Key.Enter: game.putCell() 20 | else: discard 21 | 22 | view.draw(game) 23 | 24 | exitProc() 25 | echo "" 26 | game.printResult() 27 | 28 | when isMainModule and not defined modeTest: 29 | main() -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | NIM_VERSION: 'stable' 9 | 10 | jobs: 11 | skip: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - run: echo "Skip job" 15 | 16 | before: 17 | runs-on: ubuntu-latest 18 | if: "! contains(github.event.head_commit.message, '[skip ci]')" 19 | steps: 20 | - run: echo "not contains '[skip ci]'" 21 | 22 | build: 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | matrix: 26 | os: 27 | - ubuntu-latest 28 | - windows-latest 29 | - macOS-latest 30 | needs: before 31 | steps: 32 | - uses: actions/checkout@v1 33 | - uses: jiro4989/setup-nim-action@v1 34 | with: 35 | nim-version: ${{ env.NIM_VERSION }} 36 | - run: nimble build -Y 37 | - run: nimble tests 38 | -------------------------------------------------------------------------------- /docs/release_workflow.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | start 4 | 5 | partition build { 6 | fork 7 | : build-artifact ; 8 | fork 9 | : build on ubuntu ; 10 | fork again 11 | : build on windows ; 12 | fork again 13 | : build on macOS ; 14 | end fork 15 | fork again 16 | : build-linux-packages; 17 | fork 18 | : build debian package ; 19 | fork again 20 | : build rpm package ; 21 | end fork 22 | end fork 23 | 24 | } 25 | 26 | : create release ; 27 | 28 | partition upload { 29 | fork 30 | : upload-release ; 31 | fork 32 | : upload binary for linux ; 33 | fork again 34 | : upload binary for windows ; 35 | fork again 36 | : upload binary for macOS ; 37 | end fork 38 | fork again 39 | : upload-linux-packages ; 40 | fork 41 | : upload debian package ; 42 | fork again 43 | : upload rpm package ; 44 | end fork 45 | end fork 46 | } 47 | 48 | end 49 | 50 | @enduml 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 jiro4989 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tool/setup.nim: -------------------------------------------------------------------------------- 1 | import strutils, os, strformat, times, parseopt 2 | 3 | const 4 | doc = """ 5 | setup setups this template repository. 6 | 7 | Usage: 8 | setup [options] 9 | setup (-h | --help) 10 | setup --author: --appname: 11 | 12 | Options: 13 | -h, --help Print this help 14 | --author: Set author name 15 | --appname: Set application name 16 | """ 17 | 18 | proc changeFile(beforeFile, afterFile, appName, author, dt: string) = 19 | let body = 20 | readFile(beforeFile) 21 | .replace("APPNAME", appName) 22 | .replace("", author) 23 | .replace("", dt) 24 | writeFile(beforeFile, body) 25 | echo &"{beforeFile} was replaced." 26 | 27 | if afterFile == "": return 28 | moveFile(beforeFile, afterFile) 29 | echo &"{beforeFile} was renamed {afterFile}." 30 | 31 | var 32 | optParser = initOptParser(commandLineParams()) 33 | author = "" 34 | appName = "" 35 | 36 | for kind, key, val in optParser.getopt(): 37 | case kind 38 | of cmdLongOption, cmdShortOption: 39 | case key 40 | of "help", "h": 41 | echo doc 42 | quit 0 43 | of "author": 44 | author = val 45 | of "appname": 46 | appName = val 47 | of cmdEnd: 48 | assert false # cannot happen 49 | else: discard 50 | 51 | if author == "" or appName == "": 52 | stderr.writeLine "'author' and 'appname' must be set." 53 | stderr.writeLine "see 'setup -h'." 54 | quit 1 55 | 56 | let 57 | now = now().format("yyyy") 58 | 59 | changeFile("APPNAME.nimble", &"{appName}.nimble", appName, author, now) 60 | changeFile("README.rst", "", appName, author, now) 61 | changeFile("src"/"APPNAME.nim", "src" / &"{appName}.nim", appName, author, now) 62 | changeFile("tests"/"test1.nim", "", appName, author, now) 63 | changeFile("LICENSE", "", appName, author, now) 64 | changeFile(".github" / "workflows" / "release.yml", "", appName, author, now) 65 | removeFile("tool/setup") 66 | 67 | echo "" 68 | echo "success." 69 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==== 2 | nimothello 3 | ==== 4 | 5 | |nimble-version| |nimble-install| |gh-actions| 6 | 7 | A teminal othello (reversi) in Nim. 8 | 9 | Linux demo: 10 | 11 | |demo-linux| 12 | 13 | Windows demo: 14 | 15 | |demo-windows| 16 | 17 | .. contents:: Table of contents 18 | 19 | Usage 20 | ===== 21 | 22 | .. code-block:: shell 23 | 24 | $ nimothello 25 | 26 | Key bindings 27 | ^^^^^^^^^^^^ 28 | 29 | Vim like key-bindings. 30 | 31 | =============== ======= 32 | Key Motion 33 | =============== ======= 34 | H / A Move left 35 | J / S Move down 36 | K / W Move up 37 | L / D Move right 38 | / Put cell 39 | =============== ======= 40 | 41 | Installation 42 | ============ 43 | 44 | .. code-block:: shell 45 | 46 | $ nimble install -Y nimothello 47 | 48 | LICENSE 49 | ======= 50 | 51 | MIT 52 | 53 | Development 54 | =========== 55 | 56 | Release workflow 57 | ^^^^^^^^^^^^^^^^ 58 | 59 | GitHub Action runs when you pushed new tags. 60 | 61 | .. code-block:: shell 62 | 63 | $ git tag 64 | $ git push origin 65 | 66 | or 67 | 68 | $ git push origin --tags 69 | 70 | GitHub Action creates a new release and upload your assets. 71 | 72 | Release workflows: 73 | 74 | |image-release-workflow| 75 | 76 | Release note will be generated by `git-chglog `_. 77 | Config files are `.chglog <./.chglog>`_. 78 | 79 | Details, see `release.yml <./.github/workflows/release.yml>`_. 80 | 81 | .. |gh-actions| image:: https://github.com/jiro4989/nimothello/workflows/test/badge.svg 82 | :target: https://github.com/jiro4989/nimothello/actions 83 | .. |nimble-version| image:: https://nimble.directory/ci/badges/nimothello/version.svg 84 | :target: https://nimble.directory/ci/badges/nimothello/nimdevel/output.html 85 | .. |nimble-install| image:: https://nimble.directory/ci/badges/nimothello/nimdevel/status.svg 86 | :target: https://nimble.directory/ci/badges/nimothello/nimdevel/output.html 87 | 88 | .. |image-release-workflow| image:: https://user-images.githubusercontent.com/13825004/87944618-9897fc00-cada-11ea-9401-74167f04b5c4.png 89 | 90 | .. |demo-linux| image:: https://raw.githubusercontent.com/jiro4989/nimothello/master/docs/othello_linux.gif.gif 91 | .. |demo-windows| image:: https://raw.githubusercontent.com/jiro4989/nimothello/master/docs/othello_win.gif.gif -------------------------------------------------------------------------------- /tests/test_gameplay.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | exitcode: 0 3 | """ 4 | 5 | import unittest 6 | 7 | import nimothellopkg/models 8 | 9 | var game = newGame() 10 | let tests = [ 11 | (x: 6, y: 4, scoreP1: 4, scoreP2: 1, status: GameStatus.isRunning), # p1 12 | (x: 6, y: 3, scoreP1: 3, scoreP2: 3, status: GameStatus.isRunning), # p2 13 | (x: 5, y: 3, scoreP1: 5, scoreP2: 2, status: GameStatus.isRunning), # p1 14 | (x: 4, y: 3, scoreP1: 3, scoreP2: 5, status: GameStatus.isRunning), # p2 15 | (x: 5, y: 2, scoreP1: 5, scoreP2: 4, status: GameStatus.isRunning), # p1 16 | (x: 6, y: 5, scoreP1: 2, scoreP2: 8, status: GameStatus.isRunning), # p2 17 | (x: 5, y: 6, scoreP1: 5, scoreP2: 6, status: GameStatus.isRunning), # p1 18 | (x: 4, y: 6, scoreP1: 4, scoreP2: 8, status: GameStatus.isRunning), # p2 19 | (x: 3, y: 3, scoreP1: 6, scoreP2: 7, status: GameStatus.isRunning), # p1 20 | (x: 2, y: 3, scoreP1: 3, scoreP2: 11, status: GameStatus.isRunning), # p2 21 | (x: 7, y: 4, scoreP1: 7, scoreP2: 8, status: GameStatus.isRunning), # p1 22 | (x: 8, y: 4, scoreP1: 4, scoreP2: 12, status: GameStatus.isRunning), # p2 23 | (x: 1, y: 3, scoreP1: 9, scoreP2: 8, status: GameStatus.isRunning), # p1 24 | (x: 5, y: 1, scoreP1: 7, scoreP2: 11, status: GameStatus.isRunning), # p2 25 | (x: 4, y: 7, scoreP1: 11, scoreP2: 8, status: GameStatus.isRunning), # p1 26 | (x: 5, y: 7, scoreP1: 10, scoreP2: 10, status: GameStatus.isRunning), # p2 27 | (x: 6, y: 7, scoreP1: 13, scoreP2: 8, status: GameStatus.isRunning), # p1 28 | (x: 5, y: 8, scoreP1: 11, scoreP2: 11, status: GameStatus.isRunning), # p2 29 | (x: 6, y: 6, scoreP1: 14, scoreP2: 9, status: GameStatus.isRunning), # p1 30 | (x: 7, y: 6, scoreP1: 12, scoreP2: 12, status: GameStatus.isRunning), # p2 31 | (x: 8, y: 6, scoreP1: 14, scoreP2: 11, status: GameStatus.isRunning), # p1 32 | (x: 3, y: 7, scoreP1: 11, scoreP2: 15, status: GameStatus.isRunning), # p2 33 | (x: 7, y: 8, scoreP1: 13, scoreP2: 14, status: GameStatus.isRunning), # p1 34 | (x: 7, y: 7, scoreP1: 11, scoreP2: 17, status: GameStatus.isRunning), # p2 35 | (x: 8, y: 8, scoreP1: 15, scoreP2: 14, status: GameStatus.isRunning), # p1 36 | (x: 8, y: 7, scoreP1: 13, scoreP2: 17, status: GameStatus.isRunning), # p2 37 | (x: 2, y: 8, scoreP1: 16, scoreP2: 15, status: GameStatus.isRunning), # p1 38 | (x: 4, y: 2, scoreP1: 12, scoreP2: 20, status: GameStatus.isRunning), # p2 39 | (x: 7, y: 5, scoreP1: 16, scoreP2: 17, status: GameStatus.isRunning), # p1 40 | (x: 8, y: 5, scoreP1: 11, scoreP2: 23, status: GameStatus.isRunning), # p2 41 | (x: 8, y: 3, scoreP1: 18, scoreP2: 17, status: GameStatus.isRunning), # p1 42 | (x: 7, y: 3, scoreP1: 16, scoreP2: 20, status: GameStatus.isRunning), # p2 43 | (x: 6, y: 8, scoreP1: 18, scoreP2: 19, status: GameStatus.isRunning), # p1 44 | (x: 2, y: 7, scoreP1: 17, scoreP2: 21, status: GameStatus.isRunning), # p2 45 | (x: 3, y: 1, scoreP1: 22, scoreP2: 17, status: GameStatus.isRunning), # p1 46 | (x: 2, y: 2, scoreP1: 21, scoreP2: 19, status: GameStatus.isRunning), # p2 47 | (x: 3, y: 4, scoreP1: 25, scoreP2: 16, status: GameStatus.isRunning), # p1 48 | (x: 3, y: 2, scoreP1: 24, scoreP2: 18, status: GameStatus.isRunning), # p2 49 | (x: 2, y: 1, scoreP1: 28, scoreP2: 15, status: GameStatus.isRunning), # p1 50 | (x: 3, y: 5, scoreP1: 26, scoreP2: 18, status: GameStatus.isRunning), # p2 51 | (x: 2, y: 5, scoreP1: 31, scoreP2: 14, status: GameStatus.isRunning), # p1 52 | (x: 3, y: 6, scoreP1: 27, scoreP2: 19, status: GameStatus.isRunning), # p2 53 | (x: 1, y: 7, scoreP1: 32, scoreP2: 15, status: GameStatus.isRunning), # p1 54 | (x: 1, y: 8, scoreP1: 31, scoreP2: 17, status: GameStatus.isRunning), # p2 55 | (x: 2, y: 6, scoreP1: 36, scoreP2: 13, status: GameStatus.isRunning), # p1 56 | (x: 1, y: 6, scoreP1: 29, scoreP2: 21, status: GameStatus.isRunning), # p2 57 | (x: 6, y: 2, scoreP1: 34, scoreP2: 17, status: GameStatus.isRunning), # p1 58 | (x: 3, y: 8, scoreP1: 30, scoreP2: 22, status: GameStatus.isRunning), # p2 59 | (x: 2, y: 4, scoreP1: 33, scoreP2: 20, status: GameStatus.isRunning), # p1 60 | (x: 1, y: 4, scoreP1: 30, scoreP2: 24, status: GameStatus.isRunning), # p2 61 | (x: 1, y: 5, scoreP1: 34, scoreP2: 21, status: GameStatus.isRunning), # p1 62 | (x: 1, y: 1, scoreP1: 32, scoreP2: 24, status: GameStatus.isRunning), # p2 63 | (x: 1, y: 2, scoreP1: 34, scoreP2: 23, status: GameStatus.isRunning), # p1 64 | (x: 4, y: 1, scoreP1: 28, scoreP2: 30, status: GameStatus.isRunning), # p2 65 | (x: 6, y: 1, scoreP1: 32, scoreP2: 27, status: GameStatus.isRunning), # p1 66 | (x: 7, y: 1, scoreP1: 29, scoreP2: 31, status: GameStatus.isRunning), # p2 67 | (x: 4, y: 8, scoreP1: 37, scoreP2: 24, status: GameStatus.isRunning), # p1 68 | (x: 7, y: 2, scoreP1: 36, scoreP2: 26, status: GameStatus.isRunning), # p2 69 | (x: 8, y: 2, scoreP1: 40, scoreP2: 23, status: GameStatus.isRunning), # p1 70 | (x: 8, y: 1, scoreP1: 39, scoreP2: 25, status: GameStatus.isFinished), # p2 71 | ] 72 | for tt in tests: 73 | game.putCell(tt.x, tt.y) 74 | debugPrint game.getBoard 75 | check game.getPlayer1Score() == tt.scoreP1 76 | check game.getPlayer2Score() == tt.scoreP2 77 | check game.getStatus == tt.status -------------------------------------------------------------------------------- /src/nimothellopkg/views.nim: -------------------------------------------------------------------------------- 1 | from terminal import eraseScreen 2 | import os, strutils, strformat 3 | import illwill 4 | import models 5 | 6 | type 7 | GameView = ref object 8 | buf: TerminalBuffer 9 | boardView: BoardView 10 | scoreView: ScoreView 11 | currentPlayerView: CurrentPlayerView 12 | timeView: TimeView 13 | helpView: HelpView 14 | 15 | BoardView = object 16 | ## オセロ盤を表示するビュー。 17 | x, y: int 18 | 19 | ScoreView = object 20 | ## プレイヤーの得点を表示するビュー。 21 | x, y: int 22 | 23 | CurrentPlayerView = object 24 | ## 現在の操作プレイヤーを表示するビュー。 25 | x, y: int 26 | 27 | TimeView = object 28 | ## 経過時間を表示するビュー。 29 | x, y: int 30 | 31 | HelpView = object 32 | ## ヘルプメッセージを表示するビュー。 33 | x, y: int 34 | 35 | const 36 | rightViewWidth = 26 37 | 38 | proc printResult*(self: Game) = 39 | let 40 | p1 = self.getPlayer1Score 41 | p2 = self.getPlayer2Score 42 | winner = 43 | if p1 < p2: "PLAYER2" 44 | elif p2 < p1: "PLAYER1" 45 | else: "NONE" 46 | elapsedTime = self.getElapsedTime() 47 | echo &""" 48 | ELAPSED TIME: 49 | {elapsedTime} sec 50 | 51 | SCORE: 52 | PLAYER1 = {p1} 53 | PLAYER2 = {p2} 54 | 55 | WINNER: 56 | {winner} 57 | """ 58 | 59 | proc exitProc*() {.noconv.} = 60 | ## 終了処理 61 | illwillDeinit() 62 | showCursor() 63 | eraseScreen() 64 | 65 | proc init = 66 | illwillInit(fullscreen=true) 67 | setControlCHook(exitProc) 68 | hideCursor() 69 | 70 | init() 71 | 72 | proc newBoardView(): BoardView = 73 | BoardView(x: 0, y: 0) 74 | 75 | proc newScoreView(): ScoreView = 76 | ScoreView(x: 22, y: 0) 77 | 78 | proc newCurrentPlayerView(): CurrentPlayerView = 79 | CurrentPlayerView(x: 22, y: 4) 80 | 81 | proc newTimeView(): TimeView = 82 | TimeView(x: 22, y: 7) 83 | 84 | proc newHelpView(): HelpView = 85 | HelpView(x: 0, y: 11) 86 | 87 | proc newTerminal: TerminalBuffer = 88 | let 89 | w = terminalWidth() 90 | h = terminalHeight() 91 | result = newTerminalBuffer(w, h) 92 | 93 | proc newGameView*(): GameView = 94 | var buf = newTerminal() 95 | result = GameView( 96 | buf: buf, 97 | boardView: newBoardView(), 98 | scoreView: newScoreView(), 99 | currentPlayerView: newCurrentPlayerView(), 100 | timeView: newTimeView(), 101 | helpView: newHelpView(), 102 | ) 103 | 104 | proc color(c: Cell): BackgroundColor = 105 | case c 106 | of empty: bgNone 107 | of wall: bgWhite 108 | of player1: bgNone 109 | of player2: bgNone 110 | 111 | proc text(c: Cell): string = 112 | case c 113 | of empty: " " 114 | of wall: " " 115 | of player1: "00" 116 | of player2: "--" 117 | 118 | proc draw*(self: BoardView, buf: var TerminalBuffer, game: Game) = 119 | buf = newTerminal() 120 | let poses = game.getPuttableCellPositions() 121 | for yy, row in game.getBoard: 122 | for xx, cell in row: 123 | let 124 | x = xx + self.x 125 | y = yy + self.y 126 | pos = newCellPosition(x, y) 127 | cursol = game.getCursol 128 | if pos == cursol: 129 | buf.setForegroundColor(fgBlack) 130 | buf.setBackgroundColor(bgGreen) 131 | else: 132 | buf.setForegroundColor(fgWhite) 133 | buf.setBackgroundColor(cell.color) 134 | 135 | if pos in poses: 136 | buf.write(x*2, y, "**") 137 | else: 138 | buf.write(x*2, y, cell.text) 139 | 140 | buf.resetAttributes() 141 | 142 | template draw(buf: var TerminalBuffer, x, y: int, text: string, alignCount: int, fg: ForegroundColor, bg: BackgroundColor, blight: bool) = 143 | buf.setForegroundColor(fg, blight) 144 | buf.setBackgroundColor(bg) 145 | buf.write(x, y, " " & text.alignLeft(alignCount+1)) 146 | buf.resetAttributes() 147 | 148 | proc drawHeader(buf: var TerminalBuffer, x, y: int, text: string, alignCount: int) = 149 | buf.draw(x, y, text, alignCount, fgWhite, bgBlack, true) 150 | 151 | proc drawBody(buf: var TerminalBuffer, x, y: int, text: string, alignCount: int) = 152 | buf.draw(x, y, text, alignCount, fgBlack, bgWhite, false) 153 | 154 | proc draw*(self: ScoreView, buf: var TerminalBuffer, game: Game) = 155 | let 156 | p1Score = game.getPlayer1Score() 157 | p2Score = game.getPlayer2Score() 158 | x = self.x 159 | y = self.y 160 | buf.drawHeader(x, y, "SCORE", rightViewWidth) 161 | buf.drawBody(x, y+1, &"PLAYER1 {p1Score}", rightViewWidth) 162 | buf.drawBody(x, y+2, &"PLAYER2 {p2Score}", rightViewWidth) 163 | buf.resetAttributes() 164 | 165 | proc draw*(self: CurrentPlayerView, buf: var TerminalBuffer, game: Game) = 166 | let 167 | name = game.getCurrentPlayerName() 168 | x = self.x 169 | y = self.y 170 | buf.drawHeader(x, y, "CURRENT PLAYER", rightViewWidth) 171 | buf.drawBody(x, y+1, name, rightViewWidth) 172 | buf.resetAttributes() 173 | 174 | proc draw*(self: TimeView, buf: var TerminalBuffer, game: Game) = 175 | let 176 | x = self.x 177 | y = self.y 178 | elapsedTime = game.getElapsedTime() 179 | buf.drawHeader(x, y, "TIME", rightViewWidth) 180 | buf.drawBody(x, y+1, &"{elapsedTime} sec", rightViewWidth) 181 | buf.resetAttributes() 182 | 183 | proc draw*(self: HelpView, buf: var TerminalBuffer) = 184 | let 185 | x = self.x 186 | y = self.y 187 | width = 48 188 | buf.drawHeader(x, y, "KEYS", width) 189 | buf.drawBody(x, y+1, "LEFT = H,A | DOWN = J,S | UP = K,W | RIGHT = L,F", width) 190 | buf.drawBody(x, y+2, "ENTER = PUT CELL | QUIT = ESC", width) 191 | buf.resetAttributes() 192 | 193 | proc draw*(self: GameView, game: Game) = 194 | self.boardView.draw(self.buf, game) 195 | self.scoreView.draw(self.buf, game) 196 | self.currentPlayerView.draw(self.buf, game) 197 | self.timeView.draw(self.buf, game) 198 | self.helpView.draw(self.buf) 199 | self.buf.display() 200 | 201 | when isMainModule: 202 | var gv = newGameView() 203 | var game = newGame() 204 | sleep 1000 205 | gv.draw(game) 206 | sleep 3000 207 | exitProc() -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | env: 9 | APP_NAME: 'nimothello' 10 | NIM_VERSION: 'stable' 11 | MAINTAINER: 'jiro' 12 | RELEASE_FILES: bin LICENSE README.* 13 | DESC: 'A teminal othello (reversi) in Nim.' 14 | 15 | jobs: 16 | build-artifact: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: 21 | - ubuntu-latest 22 | - windows-latest 23 | - macOS-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: jiro4989/setup-nim-action@v1 27 | with: 28 | nim-version: ${{ env.NIM_VERSION }} 29 | - run: nimble build -Y -d:release 30 | - name: Create artifact 31 | run: | 32 | assets="${{ env.APP_NAME }}_$(echo "${{ runner.os }}" | tr '[:upper:]' '[:lower:]')" 33 | echo "$assets" 34 | mkdir -p "dist/$assets" 35 | cp -r ${{ env.RELEASE_FILES }} "dist/$assets/" 36 | ( 37 | cd dist 38 | if [[ "${{ runner.os }}" == Windows ]]; then 39 | 7z a "$assets.zip" "$assets" 40 | else 41 | tar czf "$assets.tar.gz" "$assets" 42 | fi 43 | ls -lah *.* 44 | ) 45 | shell: bash 46 | - uses: actions/upload-artifact@v2 47 | with: 48 | name: artifact-${{ matrix.os }} 49 | path: | 50 | dist/*.tar.gz 51 | dist/*.zip 52 | 53 | build-linux-packages: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | - uses: jiro4989/setup-nim-action@v1 58 | with: 59 | nim-version: ${{ env.NIM_VERSION }} 60 | - run: nimble build -Y -d:release 61 | 62 | - name: create sample script 63 | run: | 64 | mkdir -p .debpkg/usr/bin 65 | mkdir -p .rpmpkg/usr/bin 66 | cp -p bin/* .debpkg/usr/bin/ 67 | cp -p bin/* .rpmpkg/usr/bin/ 68 | - uses: jiro4989/build-deb-action@v2 69 | with: 70 | package: ${{ env.APP_NAME }} 71 | package_root: .debpkg 72 | maintainer: ${{ env.MAINTAINER }} 73 | version: ${{ github.ref }} 74 | arch: 'amd64' 75 | desc: '${{ env.DESC }}' 76 | 77 | - uses: jiro4989/build-rpm-action@v2 78 | with: 79 | summary: '${{ env.DESC }}' 80 | package: ${{ env.APP_NAME }} 81 | package_root: .rpmpkg 82 | maintainer: ${{ env.MAINTAINER }} 83 | version: ${{ github.ref }} 84 | arch: 'x86_64' 85 | desc: '${{ env.DESC }}' 86 | 87 | - uses: actions/upload-artifact@v2 88 | with: 89 | name: artifact-deb 90 | path: | 91 | ./*.deb 92 | 93 | - uses: actions/upload-artifact@v2 94 | with: 95 | name: artifact-rpm 96 | path: | 97 | ./*.rpm 98 | !./*-debuginfo-*.rpm 99 | 100 | create-release: 101 | runs-on: ubuntu-latest 102 | needs: 103 | - build-artifact 104 | - build-linux-packages 105 | steps: 106 | - uses: actions/checkout@v2 107 | - name: Generate changelog 108 | run: | 109 | wget https://github.com/git-chglog/git-chglog/releases/download/0.9.1/git-chglog_linux_amd64 110 | chmod +x git-chglog_linux_amd64 111 | mv git-chglog_linux_amd64 git-chglog 112 | ./git-chglog --output ./changelog $(git describe --tags $(git rev-list --tags --max-count=1)) 113 | 114 | - name: Create Release 115 | id: create-release 116 | uses: actions/create-release@v1 117 | env: 118 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 119 | with: 120 | tag_name: ${{ github.ref }} 121 | release_name: ${{ github.ref }} 122 | body_path: ./changelog 123 | draft: false 124 | prerelease: false 125 | 126 | - name: Write upload_url to file 127 | run: echo '${{ steps.create-release.outputs.upload_url }}' > upload_url.txt 128 | 129 | - uses: actions/upload-artifact@v2 130 | with: 131 | name: create-release 132 | path: upload_url.txt 133 | 134 | upload-release: 135 | runs-on: ubuntu-latest 136 | needs: create-release 137 | strategy: 138 | matrix: 139 | include: 140 | - os: ubuntu-latest 141 | asset_name_suffix: linux.tar.gz 142 | asset_content_type: application/gzip 143 | - os: windows-latest 144 | asset_name_suffix: windows.zip 145 | asset_content_type: application/zip 146 | - os: macOS-latest 147 | asset_name_suffix: macos.tar.gz 148 | asset_content_type: application/gzip 149 | steps: 150 | - uses: actions/download-artifact@v2 151 | with: 152 | name: artifact-${{ matrix.os }} 153 | 154 | - uses: actions/download-artifact@v2 155 | with: 156 | name: create-release 157 | 158 | - id: vars 159 | run: | 160 | echo "::set-output name=upload_url::$(cat upload_url.txt)" 161 | 162 | - name: Upload Release Asset 163 | id: upload-release-asset 164 | uses: actions/upload-release-asset@v1 165 | env: 166 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 167 | with: 168 | upload_url: ${{ steps.vars.outputs.upload_url }} 169 | asset_path: ${{ env.APP_NAME }}_${{ matrix.asset_name_suffix }} 170 | asset_name: ${{ env.APP_NAME }}_${{ matrix.asset_name_suffix }} 171 | asset_content_type: ${{ matrix.asset_content_type }} 172 | 173 | upload-linux-packages: 174 | runs-on: ubuntu-latest 175 | needs: create-release 176 | strategy: 177 | matrix: 178 | include: 179 | - pkg: deb 180 | asset_content_type: application/vnd.debian.binary-package 181 | - pkg: rpm 182 | asset_content_type: application/x-rpm 183 | steps: 184 | - uses: actions/download-artifact@v2 185 | with: 186 | name: artifact-${{ matrix.pkg }} 187 | 188 | - uses: actions/download-artifact@v2 189 | with: 190 | name: create-release 191 | 192 | - id: vars 193 | run: | 194 | echo "::set-output name=upload_url::$(cat upload_url.txt)" 195 | echo "::set-output name=asset_name::$(ls *.${{ matrix.pkg }} | head -n 1)" 196 | 197 | - name: Upload Release Asset 198 | id: upload-release-asset 199 | uses: actions/upload-release-asset@v1 200 | env: 201 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 202 | with: 203 | upload_url: ${{ steps.vars.outputs.upload_url }} 204 | asset_path: ${{ steps.vars.outputs.asset_name }} 205 | asset_name: ${{ steps.vars.outputs.asset_name }} 206 | asset_content_type: ${{ matrix.asset_content_type }} 207 | -------------------------------------------------------------------------------- /src/nimothellopkg/models.nim: -------------------------------------------------------------------------------- 1 | import sequtils, times 2 | 3 | type 4 | Game* = object 5 | board: Board 6 | currentPlayer: Player 7 | cursol: CellPosition 8 | startTime: DateTime 9 | Board* = array[10, array[10, Cell]] 10 | ## ゲーム板を表す型。 11 | ## 12 | ## オセロのコマの配置可能な範囲は通常 8 x 8 だが、壁を表現するために 10 x 10 で定義する。 13 | ## つまり、一番外の枠は常に壁セルが配置される。 14 | ## 壁セルは Cell.wall で表現する。 15 | ## 図示すると以下のようになる。 16 | ## 17 | ## | a b c d e f g h i j 18 | ## --+-------------------- 19 | ## 0 | 0 0 0 0 0 0 0 0 0 0 20 | ## 1 | 0 - - - - - - - - 0 21 | ## 2 | 0 - - - - - - - - 0 22 | ## 3 | 0 - - - - - - - - 0 23 | ## 4 | 0 - - - 2 3 - - - 0 24 | ## 5 | 0 - - - 3 2 - - - 0 25 | ## 6 | 0 - - - - - - - - 0 26 | ## 7 | 0 - - - - - - - - 0 27 | ## 8 | 0 - - - - - - - - 0 28 | ## 9 | 0 0 0 0 0 0 0 0 0 0 29 | Cell* {.pure.} = enum 30 | wall, empty, player1, player2 31 | ## セルの状態を表現する。 32 | CellPosition* = object 33 | x, y: int 34 | RefCellPosition = ref CellPosition 35 | Player* {.pure.} = enum 36 | p1, p2 37 | GameStatus* {.pure.} = enum 38 | isRunning, isFinished 39 | 40 | func newBoard*(): Board = 41 | ## ゲーム板を生成する。 42 | result = 43 | [ 44 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 45 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 46 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 47 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 48 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 49 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 50 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 51 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 52 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 53 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 54 | ] 55 | 56 | proc newGame*(): Game = 57 | ## ゲームインスタンスを生成する。 58 | result = Game( 59 | board: newBoard(), 60 | currentPlayer: p1, 61 | cursol: CellPosition(x: 5, y: 5), 62 | startTime: now(), 63 | ) 64 | 65 | func newCellPosition*(x, y: int): CellPosition = 66 | CellPosition(x: x, y: y) 67 | 68 | func getStatus*(self: Game): GameStatus = 69 | var emptyCount: int 70 | for row in self.board: 71 | for cell in row: 72 | if cell == empty: 73 | inc emptyCount 74 | 75 | result = 76 | if 0 < emptyCount: 77 | isRunning 78 | else: 79 | isFinished 80 | 81 | func isRunning*(self: Game): bool = 82 | self.getStatus == GameStatus.isRunning 83 | 84 | func isFinished*(self: Game): bool = 85 | self.getStatus == GameStatus.isFinished 86 | 87 | func `[]`*(self: Board, x, y: int): Cell = 88 | ## x, y座標のセルを返す。 89 | self[y][x] 90 | 91 | func `[]=`*(self: var Board, x, y: int, cell: Cell) = 92 | ## x, y座標のセルを返す。 93 | self[y][x] = cell 94 | 95 | func countScore(self: Board, cellType: Cell): int = 96 | for row in self: 97 | for cell in row: 98 | if cell == cellType: 99 | inc result 100 | 101 | func getPlayer1Score*(self: Game): int = 102 | ## プレイヤー1の得点を返す。 103 | self.board.countScore(player1) 104 | 105 | func getPlayer2Score*(self: Game): int = 106 | ## プレイヤー2の得点を返す。 107 | self.board.countScore(player2) 108 | 109 | func playerToCell(p: Player): Cell = 110 | case p 111 | of p1: player1 112 | of p2: player2 113 | 114 | func inclVal(a, b: int): int = 115 | result = 116 | if a < b: 1 117 | else: -1 118 | 119 | func setLineVertical(self: var Board, x1, y1, x2, y2: int, cell: Cell) = 120 | ## 垂直方向にコマを配置する。 121 | ## 高さが下から上方向でもOK. 122 | let yp = inclVal(y1, y2) 123 | let diff = abs(y1 - y2) 124 | var y = y1 125 | for i in 1..diff+1: 126 | self[x1, y] = cell 127 | y += yp 128 | 129 | func setLineHorizontal(self: var Board, x1, y1, x2, y2: int, cell: Cell) = 130 | ## 水平方向にコマを配置する。 131 | ## 右から左方向の配置でもOK. 132 | let xp = inclVal(x1, x2) 133 | let diff = abs(x1 - x2) 134 | var x = x1 135 | for i in 1..diff+1: 136 | self[x, y1] = cell 137 | x += xp 138 | 139 | func setLineOblique(self: var Board, x1, y1, x2, y2: int, cell: Cell) = 140 | ## 斜め方向にコマを配置する。 141 | let xp = inclVal(x1, x2) 142 | let yp = inclVal(y1, y2) 143 | let diff = abs(x1 - x2) 144 | var 145 | x = x1 146 | y = y1 147 | for i in 1..diff+1: 148 | self[x, y] = cell 149 | x += xp 150 | y += yp 151 | 152 | func setLine*(self: var Board, x1, y1, x2, y2: int, cell: Cell) = 153 | ## 直線上のセルを反転する。 154 | if y1 == y2: 155 | self.setLineHorizontal x1, y1, x2, y2, cell 156 | return 157 | if x1 == x2: 158 | self.setLineVertical x1, y1, x2, y2, cell 159 | return 160 | self.setLineOblique x1, y1, x2, y2, cell 161 | 162 | func turnPlayer(self: var Game) = 163 | let p = self.currentPlayer 164 | self.currentPlayer = 165 | case p 166 | of p1: p2 167 | of p2: p1 168 | 169 | func getPuttableObliqueLinePosition(self: Board, x1, y1, x2, y2: int, cell: Cell): RefCellPosition = 170 | ## 斜め方向にコマを配置する。 171 | if x1 == x2 and y1 == y2: return 172 | let xp = inclVal(x1, x2) 173 | let yp = inclVal(y1, y2) 174 | let diff = abs(x1 - x2) 175 | var 176 | x = x1 177 | y = y1 178 | for i in 1..diff+1: 179 | x += xp 180 | y += yp 181 | 182 | let c = self[x, y] 183 | # 自分のセルか壁が見つかったら早期リターン 184 | if c in [cell, wall]: 185 | return 186 | # 空のセルが見つかったら返す。 187 | # ただし元セルに隣接する場合はNG 188 | if c == empty: 189 | if abs(x1 - x) == 1: 190 | return nil 191 | return RefCellPosition(x: x, y: y) 192 | # それ以外のときは相手のセルなのでスルー 193 | 194 | func getPuttableHotizontalLinePosition(self: Board, x1, y1, x2, y2: int, cell: Cell): RefCellPosition = 195 | ## 水平方向にコマを配置する。 196 | if x1 == x2 and y1 == y2: return 197 | let xp = inclVal(x1, x2) 198 | let diff = abs(x1 - x2) 199 | var 200 | x = x1 201 | y = y1 202 | for i in 1..diff+1: 203 | x += xp 204 | 205 | let c = self[x, y] 206 | # 自分のセルか壁が見つかったら早期リターン 207 | if c in [cell, wall]: 208 | return 209 | # 空のセルが見つかったら返す。 210 | # ただし元セルに隣接する場合はNG 211 | if c == empty: 212 | if abs(x1 - x) == 1: 213 | return nil 214 | return RefCellPosition(x: x, y: y) 215 | # それ以外のときは相手のセルなのでスルー 216 | 217 | func getPuttableVerticalLinePosition(self: Board, x1, y1, x2, y2: int, cell: Cell): RefCellPosition = 218 | ## 垂直方向にコマを配置する。 219 | if x1 == x2 and y1 == y2: return 220 | let yp = inclVal(y1, y2) 221 | let diff = abs(y1 - y2) 222 | var 223 | x = x1 224 | y = y1 225 | for i in 1..diff+1: 226 | y += yp 227 | 228 | let c = self[x, y] 229 | # 自分のセルか壁が見つかったら早期リターン 230 | if c in [cell, wall]: 231 | return 232 | # 空のセルが見つかったら返す。 233 | # ただし元セルに隣接する場合はNG 234 | if c == empty: 235 | if abs(y1 - y) == 1: 236 | return nil 237 | return RefCellPosition(x: x, y: y) 238 | # それ以外のときは相手のセルなのでスルー 239 | 240 | func getFarestPosition(self: Board, x, y, xp, yp: int): RefCellPosition = 241 | ## xp, yp方向の最も遠い座標を返す。 242 | var 243 | x = x 244 | y = y 245 | 246 | let 247 | maxWidth = self[0].len 248 | maxHeight = self.len 249 | 250 | while true: 251 | if x <= 0 or maxWidth <= x or y <= 0 or maxHeight <= y: 252 | return RefCellPosition(x: x, y: y) 253 | 254 | x += xp 255 | y += yp 256 | 257 | func getPuttableCellPositions(self: Board, x, y: int, cell: Cell): seq[CellPosition] = 258 | template checkAdd(pos: RefCellPosition, fn: proc(self: Board, x, y, xp, yp: int, cell: Cell): RefCellPosition) = 259 | if not pos.isNil: 260 | let got = self.fn(x, y, pos.x, pos.y, cell) 261 | if not got.isNil: 262 | result.add got[] 263 | 264 | # 1. 左上 265 | checkAdd(self.getFarestPosition(x, y, -1, -1), getPuttableObliqueLinePosition) 266 | 267 | # 2. 上 268 | checkAdd(self.getFarestPosition(x, y, 0, -1), getPuttableVerticalLinePosition) 269 | 270 | # 3. 右上 271 | checkAdd(self.getFarestPosition(x, y, 1, -1), getPuttableObliqueLinePosition) 272 | 273 | # 4. 左 274 | checkAdd(self.getFarestPosition(x, y, -1, 0), getPuttableHotizontalLinePosition) 275 | 276 | # 5. 右 277 | checkAdd(self.getFarestPosition(x, y, 1, 0), getPuttableHotizontalLinePosition) 278 | 279 | # 6. 左下 280 | checkAdd(self.getFarestPosition(x, y, -1, 1), getPuttableObliqueLinePosition) 281 | 282 | # 7. 下 283 | checkAdd(self.getFarestPosition(x, y, 0, 1), getPuttableVerticalLinePosition) 284 | 285 | # 8. 右下 286 | checkAdd(self.getFarestPosition(x, y, 1, 1), getPuttableObliqueLinePosition) 287 | 288 | func getPuttableCellPositions*(self: Game): seq[CellPosition] = 289 | let 290 | playerCell = self.currentPlayer.playerToCell 291 | board = self.board 292 | 293 | for y, row in board: 294 | for x, cell in row: 295 | if cell != playerCell: 296 | continue 297 | for pos in board.getPuttableCellPositions(x, y, playerCell): 298 | result.add pos 299 | 300 | deduplicate result 301 | 302 | func putCell*(self: var Game, x, y: int) = 303 | ## 現在のプレイヤーに対応するセルを指定の座標のセルにセットする。 304 | ## セットの結果反転される箇所があれば反転される。 305 | # ボードのセルを全部網羅し、現在のプレイヤーのセルのときだけ処理をする 306 | let cell = self.currentPlayer.playerToCell 307 | type Pos = object 308 | x1, y1, x2, y2: int 309 | var puttablePoses: seq[Pos] 310 | for yy, row in self.board: 311 | for xx, c in row: 312 | if c != cell: 313 | continue 314 | # 現在のプレイヤーのセルから見て配置可能な位置を取得する 315 | let poses = self.board.getPuttableCellPositions(xx, yy, cell) 316 | for pos in poses: 317 | if pos.x == x and pos.y == y: 318 | puttablePoses.add Pos(x1: xx, y1: yy, x2: x, y2: y) 319 | break 320 | 321 | # 配置可能なセルが存在しないということは、誤った位置に置こうとしたので無視 322 | if puttablePoses.len < 1: return 323 | 324 | puttablePoses = deduplicate(puttablePoses) 325 | for pos in puttablePoses: 326 | self.board.setLine pos.x1, pos.y1, pos.x2, pos.y2, cell 327 | self.turnPlayer() 328 | 329 | # 配置可能なセルが存在しない時はプレイヤーを交代する 330 | if self.getPuttableCellPositions().len < 1: 331 | self.turnPlayer() 332 | 333 | func putCell*(self: var Game) = 334 | ## 現在のプレイヤーに対応するセルを指定の座標のセルにセットする。 335 | ## セットの結果反転される箇所があれば反転される。 336 | self.putCell(self.cursol.x, self.cursol.y) 337 | 338 | func getBoard*(self: Game): Board = 339 | self.board 340 | 341 | func debugPrint*(self: Board) = 342 | for row in self: 343 | var line: string 344 | for cell in row: 345 | let s = 346 | case cell 347 | of wall: "w" 348 | of empty: "-" 349 | of player1: "1" 350 | of player2: "2" 351 | line.add s 352 | line.add " " 353 | debugEcho line 354 | 355 | func getCurrentPlayerName*(self: Game): string = 356 | case self.currentPlayer 357 | of p1: "PLAYER1 (00)" 358 | of p2: "PLAYER2 (--)" 359 | 360 | proc getElapsedTime*(self: Game): int64 = 361 | ## 経過時間を返す。 362 | let 363 | duration = now() - self.startTime 364 | parts = duration.toParts 365 | result = parts[Hours] * 3600 + parts[Minutes] * 60 + parts[Seconds] 366 | 367 | proc getCursol*(self: Game): CellPosition = 368 | self.cursol 369 | 370 | proc moveLeft*(self: var Game) = 371 | if 1 < self.cursol.x: 372 | dec self.cursol.x 373 | 374 | proc moveRight*(self: var Game) = 375 | if self.cursol.x < self.board[0].len-2: 376 | inc self.cursol.x 377 | 378 | proc moveUp*(self: var Game) = 379 | if 1 < self.cursol.y: 380 | dec self.cursol.y 381 | 382 | proc moveDown*(self: var Game) = 383 | if self.cursol.y < self.board.len-2: 384 | inc self.cursol.y 385 | 386 | func `$`*(self: RefCellPosition): string = 387 | if not self.isNil: 388 | return $self[] -------------------------------------------------------------------------------- /tests/test_unit.nim: -------------------------------------------------------------------------------- 1 | discard """ 2 | exitcode: 0 3 | """ 4 | 5 | import unittest 6 | 7 | include nimothellopkg/models 8 | 9 | block: 10 | checkpoint "Game status" 11 | var g = newGame() 12 | check g.isRunning() 13 | for y in 1..9: 14 | for x in 1..9: 15 | g.board[x, y] = player1 16 | check g.isFinished() 17 | 18 | block: 19 | checkpoint "Player score" 20 | var g = newGame() 21 | check g.getPlayer1Score() == 2 22 | check g.getPlayer2Score() == 2 23 | g.board[1, 1] = player1 24 | check g.getPlayer1Score() == 3 25 | check g.getPlayer2Score() == 2 26 | g.board[1, 2] = player2 27 | check g.getPlayer1Score() == 3 28 | check g.getPlayer2Score() == 3 29 | 30 | block: 31 | checkpoint "Player to cell" 32 | var g = newGame() 33 | check g.currentPlayer.playerToCell == player1 34 | g.turnPlayer() 35 | check g.currentPlayer.playerToCell == player2 36 | 37 | block: 38 | # set Vertical line 39 | let want1 = 40 | [ 41 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 42 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 43 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 44 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 45 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 46 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 47 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 48 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 49 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 50 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 51 | ] 52 | let want2 = 53 | [ 54 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 55 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 56 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 57 | [wall, empty, empty, empty, empty, player1, empty, empty, empty, wall], 58 | [wall, empty, empty, empty, player1, player1, empty, empty, empty, wall], 59 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 60 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 61 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 62 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 63 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 64 | ] 65 | 66 | let tests = [ 67 | (desc: "set cells to edge area", x1: 1, y1: 1, x2: 1, y2: 3, cell: player1, want: want1), 68 | (desc: "overwrite cells", x1: 5, y1: 3, x2: 5, y2: 5, cell: player1, want: want2), 69 | (desc: "set cells to edge area (desc)", x1: 1, y1: 3, x2: 1, y2: 1, cell: player1, want: want1), 70 | ] 71 | for tt in tests: 72 | checkpoint tt.desc 73 | var g = newGame() 74 | g.board.setLineVertical(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 75 | check tt.want == g.board 76 | 77 | block: 78 | # set Horizontal line 79 | let want1 = 80 | [ 81 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 82 | [wall, player1, player1, player1, empty, empty, empty, empty, empty, wall], 83 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 84 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 85 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 86 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 87 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 88 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 89 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 90 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 91 | ] 92 | let want2 = 93 | [ 94 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 95 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 96 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 97 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 98 | [wall, empty, empty, empty, player1, player1, player1, empty, empty, wall], 99 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 100 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 101 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 102 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 103 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 104 | ] 105 | 106 | let tests = [ 107 | (desc: "set horizontal line to edge", x1: 1, y1: 1, x2: 3, y2: 1, cell: player1, want: want1), 108 | (desc: "overwrite cells", x1: 4, y1: 4, x2: 6, y2: 4, cell: player1, want: want2), 109 | (desc: "set horizontal line to edge (desc)", x1: 3, y1: 1, x2: 1, y2: 1, cell: player1, want: want1), 110 | ] 111 | for tt in tests: 112 | checkpoint tt.desc 113 | var g = newGame() 114 | g.board.setLineHorizontal(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 115 | check tt.want == g.board 116 | 117 | block: 118 | # set Oblique line 119 | let want1 = 120 | [ 121 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 122 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 123 | [wall, empty, player1, empty, empty, empty, empty, empty, empty, wall], 124 | [wall, empty, empty, player1, empty, empty, empty, empty, empty, wall], 125 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 126 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 127 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 128 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 129 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 130 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 131 | ] 132 | let want2 = 133 | [ 134 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 135 | [wall, empty, empty, player1, empty, empty, empty, empty, empty, wall], 136 | [wall, empty, player1, empty, empty, empty, empty, empty, empty, wall], 137 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 138 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 139 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 140 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 141 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 142 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 143 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 144 | ] 145 | 146 | let tests = [ 147 | (desc: "right down", x1: 1, y1: 1, x2: 3, y2: 3, cell: player1, want: want1), 148 | (desc: "left down", x1: 3, y1: 1, x2: 1, y2: 3, cell: player1, want: want2), 149 | (desc: "left up", x1: 3, y1: 3, x2: 1, y2: 1, cell: player1, want: want1), 150 | (desc: "right up", x1: 1, y1: 3, x2: 3, y2: 1, cell: player1, want: want2), 151 | ] 152 | for tt in tests: 153 | checkpoint tt.desc 154 | var g = newGame() 155 | g.board.setLineOblique(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 156 | check tt.want == g.board 157 | 158 | block: 159 | # set line 160 | let wantRightDown = 161 | [ 162 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 163 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 164 | [wall, empty, player1, empty, empty, empty, empty, empty, empty, wall], 165 | [wall, empty, empty, player1, empty, empty, empty, empty, empty, wall], 166 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 167 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 168 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 169 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 170 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 171 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 172 | ] 173 | let wantLeftDown = 174 | [ 175 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 176 | [wall, empty, empty, player1, empty, empty, empty, empty, empty, wall], 177 | [wall, empty, player1, empty, empty, empty, empty, empty, empty, wall], 178 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 179 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 180 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 181 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 182 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 183 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 184 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 185 | ] 186 | let wantUp = 187 | [ 188 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 189 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 190 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 191 | [wall, player1, empty, empty, empty, empty, empty, empty, empty, wall], 192 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 193 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 194 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 195 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 196 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 197 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 198 | ] 199 | let wantLeft = 200 | [ 201 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 202 | [wall, player1, player1, player1, empty, empty, empty, empty, empty, wall], 203 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 204 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 205 | [wall, empty, empty, empty, player1, player2, empty, empty, empty, wall], 206 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 207 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 208 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 209 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 210 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 211 | ] 212 | 213 | let tests = [ 214 | (desc: "left up", x1: 3, y1: 3, x2: 1, y2: 1, cell: player1, want: wantRightDown), 215 | (desc: "up", x1: 1, y1: 3, x2: 1, y2: 1, cell: player1, want: wantUp), 216 | (desc: "right up", x1: 1, y1: 3, x2: 3, y2: 1, cell: player1, want: wantLeftDown), 217 | (desc: "left", x1: 3, y1: 1, x2: 1, y2: 1, cell: player1, want: wantLeft), 218 | (desc: "right", x1: 1, y1: 1, x2: 3, y2: 1, cell: player1, want: wantLeft), 219 | (desc: "left down", x1: 3, y1: 1, x2: 1, y2: 3, cell: player1, want: wantLeftDown), 220 | (desc: "down", x1: 1, y1: 1, x2: 1, y2: 3, cell: player1, want: wantUp), 221 | (desc: "right down", x1: 1, y1: 1, x2: 3, y2: 3, cell: player1, want: wantRightDown), 222 | ] 223 | for tt in tests: 224 | checkpoint tt.desc 225 | var g = newGame() 226 | g.board.setLine(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 227 | check tt.want == g.board 228 | 229 | block: 230 | checkpoint "turnPlayer" 231 | var g = newGame() 232 | check g.currentPlayer == p1 233 | g.turnPlayer() 234 | check g.currentPlayer == p2 235 | 236 | block: 237 | checkpoint "getPuttableObliqueLinePosition" 238 | var nilWant: RefCellPosition 239 | var g1 = newGame() 240 | g1.board[2, 2] = player2 241 | g1.board[2, 4] = player2 242 | let tests = [ 243 | (desc: "ok: [right up] found", game: g1, x1: 1, y1: 3, x2: 3, y2: 1, cell: player1, want: RefCellPosition(x: 3, y: 1), err: false), 244 | (desc: "ng: [right up] not found when same position", game: newGame(), x1: 1, y1: 3, x2: 1, y2: 3, cell: player1, want: nilWant, err: true), 245 | (desc: "ng: [right up] not found when distance is 1", game: newGame(), x1: 1, y1: 3, x2: 2, y2: 2, cell: player1, want: nilWant, err: true), 246 | (desc: "ng: [right up] not found when no player2 cell", game: newGame(), x1: 1, y1: 3, x2: 3, y2: 1, cell: player1, want: nilWant, err: true), 247 | (desc: "ok: [right down] found", game: g1, x1: 1, y1: 3, x2: 3, y2: 5, cell: player1, want: RefCellPosition(x: 3, y: 5), err: false), 248 | (desc: "ok: [right down] found when over distance", game: g1, x1: 1, y1: 3, x2: 4, y2: 6, cell: player1, want: RefCellPosition(x: 3, y: 5), err: false), 249 | (desc: "ok: [right down] found when over distance (2)", game: g1, x1: 1, y1: 3, x2: 5, y2: 7, cell: player1, want: RefCellPosition(x: 3, y: 5), err: false), 250 | (desc: "ng: [right down] not found when same position", game: newGame(), x1: 1, y1: 3, x2: 1, y2: 3, cell: player1, want: nilWant, err: true), 251 | (desc: "ng: [right down] not found when distance is 1", game: newGame(), x1: 1, y1: 3, x2: 2, y2: 4, cell: player1, want: nilWant, err: true), 252 | (desc: "ng: [right down] not found when distance is 1", game: newGame(), x1: 1, y1: 3, x2: 3, y2: 5, cell: player1, want: nilWant, err: true), 253 | (desc: "ok: [left up] found", game: g1, x1: 3, y1: 3, x2: 1, y2: 1, cell: player1, want: RefCellPosition(x: 1, y: 1), err: false), 254 | (desc: "ng: [left up] not found when same position", game: newGame(), x1: 3, y1: 3, x2: 3, y2: 3, cell: player1, want: nilWant, err: true), 255 | (desc: "ng: [left up] not found when distance is 1", game: newGame(), x1: 3, y1: 3, x2: 2, y2: 2, cell: player1, want: nilWant, err: true), 256 | (desc: "ok: [left down] found", game: g1, x1: 3, y1: 3, x2: 1, y2: 5, cell: player1, want: RefCellPosition(x: 1, y: 5), err: false), 257 | (desc: "ng: [left down] not found when distance is 1", game: newGame(), x1: 3, y1: 3, x2: 2, y2: 4, cell: player1, want: nilWant, err: true), 258 | ] 259 | for tt in tests: 260 | checkpoint tt.desc 261 | var g = tt.game 262 | let got = g.board.getPuttableObliqueLinePosition(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 263 | if tt.err: 264 | check got.isNil 265 | continue 266 | check tt.want[] == got[] 267 | 268 | block: 269 | checkpoint "getPuttableHotizontalLinePosition" 270 | var nilWant: RefCellPosition 271 | var g1 = newGame() 272 | g1.board[3, 1] = player2 273 | g1.board[5, 1] = player2 274 | g1.board[6, 1] = player2 275 | var g2 = newGame() 276 | g2.board[1, 1] = player2 277 | let tests = [ 278 | (desc: "ok: [left] found", game: g1, x1: 4, y1: 1, x2: 2, y2: 1, cell: player1, want: RefCellPosition(x: 2, y: 1), err: false), 279 | (desc: "ng: [left] not found when same position", game: newGame(), x1: 4, y1: 1, x2: 4, y2: 1, cell: player1, want: nilWant, err: true), 280 | (desc: "ng: [left] not found when distance is 1", game: newGame(), x1: 4, y1: 1, x2: 3, y2: 1, cell: player1, want: nilWant, err: true), 281 | (desc: "ng: [left] not found when reached wall", game: g2, x1: 2, y1: 1, x2: 0, y2: 1, cell: player1, want: nilWant, err: true), 282 | (desc: "ok: [right] found", game: g1, x1: 4, y1: 1, x2: 6, y2: 1, cell: player1, want: RefCellPosition(x: 7, y: 1), err: false), 283 | (desc: "ng: [right] not found when distance is 1", game: newGame(), x1: 4, y1: 1, x2: 5, y2: 1, cell: player1, want: nilWant, err: true), 284 | ] 285 | for tt in tests: 286 | checkpoint tt.desc 287 | var g = tt.game 288 | let got = g.board.getPuttableHotizontalLinePosition(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 289 | if tt.err: 290 | check got.isNil 291 | continue 292 | check tt.want[] == got[] 293 | 294 | block: 295 | checkpoint "getPuttableVerticalLinePosition" 296 | var nilWant: RefCellPosition 297 | var g1 = newGame() 298 | g1.board[3, 3] = player2 299 | g1.board[3, 5] = player2 300 | g1.board[3, 6] = player2 301 | var g2 = newGame() 302 | g2.board[3, 1] = player2 303 | let tests = [ 304 | (desc: "ok: [up] found", game: g1, x1: 3, y1: 4, x2: 3, y2: 2, cell: player1, want: RefCellPosition(x: 3, y: 2), err: false), 305 | (desc: "ng: [up] not found when same position", game: newGame(), x1: 3, y1: 4, x2: 3, y2: 4, cell: player1, want: nilWant, err: true), 306 | (desc: "ng: [up] not found when distance is 1", game: newGame(), x1: 3, y1: 4, x2: 3, y2: 3, cell: player1, want: nilWant, err: true), 307 | (desc: "ng: [up] not found when reached wall", game: g2, x1: 3, y1: 2, x2: 3, y2: 0, cell: player1, want: nilWant, err: true), 308 | (desc: "ok: [down] found", game: g1, x1: 3, y1: 4, x2: 3, y2: 7, cell: player1, want: RefCellPosition(x: 3, y: 7), err: false), 309 | (desc: "ng: [down] not found when distance is 1", game: newGame(), x1: 3, y1: 4, x2: 3, y2: 5, cell: player1, want: nilWant, err: true), 310 | ] 311 | for tt in tests: 312 | checkpoint tt.desc 313 | var g = tt.game 314 | let got = g.board.getPuttableVerticalLinePosition(tt.x1, tt.y1, tt.x2, tt.y2, tt.cell) 315 | if tt.err: 316 | check got.isNil 317 | continue 318 | check tt.want[] == got[] 319 | 320 | block: 321 | checkpoint "getFarestPosition" 322 | var nilWant: RefCellPosition 323 | let tests = [ 324 | (desc: "ok: [left up] found", x1: 3, y1: 3, xp: -1, yp: -1, want: RefCellPosition(x: 0, y: 0), err: false), 325 | (desc: "ok: [up] found", x1: 3, y1: 3, xp: 0, yp: -1, want: RefCellPosition(x: 3, y: 0), err: false), 326 | (desc: "ok: [right up] found", x1: 3, y1: 3, xp: 1, yp: -1, want: RefCellPosition(x: 6, y: 0), err: false), 327 | (desc: "ok: [left] found", x1: 3, y1: 3, xp: -1, yp: 0, want: RefCellPosition(x: 0, y: 3), err: false), 328 | (desc: "ok: [right] found", x1: 3, y1: 3, xp: 1, yp: 0, want: RefCellPosition(x: 10, y: 3), err: false), 329 | (desc: "ok: [left down] found", x1: 3, y1: 3, xp: -1, yp: 1, want: RefCellPosition(x: 0, y: 6), err: false), 330 | (desc: "ok: [down] found", x1: 3, y1: 3, xp: 0, yp: 1, want: RefCellPosition(x: 3, y: 10), err: false), 331 | (desc: "ok: [right down] found", x1: 3, y1: 3, xp: 1, yp: 1, want: RefCellPosition(x: 10, y: 10), err: false), 332 | ] 333 | for tt in tests: 334 | checkpoint tt.desc 335 | let g = newGame() 336 | let got = g.board.getFarestPosition(tt.x1, tt.y1, tt.xp, tt.yp) 337 | if tt.err: 338 | check got.isNil 339 | continue 340 | check tt.want[] == got[] 341 | 342 | block: 343 | checkpoint "getPuttableCellPositions" 344 | var g1 = newGame() 345 | g1.board[2, 2] = player2 346 | g1.board[4, 2] = player2 347 | let tests = [ 348 | (desc: "ok: [left up, right up] found", g: g1, x: 3, y: 3, wantLen: 2, cell: player1), 349 | (desc: "ok: [left, right] found", g: g1, x: 3, y: 2, wantLen: 2, cell: player1), 350 | (desc: "ok: [left down, right down] found", g: g1, x: 3, y: 1, wantLen: 2, cell: player1), 351 | (desc: "ok: [down] found", g: g1, x: 2, y: 1, wantLen: 1, cell: player1), 352 | (desc: "ok: [up] found", g: g1, x: 2, y: 3, wantLen: 1, cell: player1), 353 | (desc: "ok: [left] found", g: g1, x: 5, y: 2, wantLen: 1, cell: player1), 354 | (desc: "ok: [right] found", g: g1, x: 1, y: 2, wantLen: 1, cell: player1), 355 | (desc: "ng: [NONE] not found", g: g1, x: 8, y: 8, wantLen: 0, cell: player1), 356 | ] 357 | for tt in tests: 358 | checkpoint tt.desc 359 | let got = tt.g.board.getPuttableCellPositions(tt.x, tt.y, tt.cell) 360 | check tt.wantLen == got.len 361 | 362 | block: 363 | checkpoint "putCell" 364 | let want1 = 365 | [ 366 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 367 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 368 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 369 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 370 | [wall, empty, empty, empty, player1, player1, player1, empty, empty, wall], 371 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 372 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 373 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 374 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 375 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 376 | ] 377 | let want2 = 378 | [ 379 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 380 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 381 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 382 | [wall, empty, empty, empty, empty, player1, empty, empty, empty, wall], 383 | [wall, empty, empty, empty, player1, player1, empty, empty, empty, wall], 384 | [wall, empty, empty, empty, player2, player1, empty, empty, empty, wall], 385 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 386 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 387 | [wall, empty, empty, empty, empty, empty, empty, empty, empty, wall], 388 | [wall, wall, wall, wall, wall, wall, wall, wall, wall, wall], 389 | ] 390 | let tests = [ 391 | (desc: "ok: put", game: newGame(), x: 6, y: 4, want: want1), 392 | (desc: "ok: put", game: newGame(), x: 5, y: 3, want: want2), 393 | ] 394 | for tt in tests: 395 | checkpoint tt.desc 396 | var g = tt.game 397 | g.putCell(tt.x, tt.y) 398 | check tt.want == g.board 399 | debugPrint g.board 400 | --------------------------------------------------------------------------------