├── .coveragerc ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── __init__.py ├── for_test.py ├── helper.py ├── kifu.py ├── modules │ ├── input.py │ ├── parse_input.py │ ├── shogi.py │ ├── shogi_input.py │ └── shogi_output.py ├── shogi.py ├── slack_utils │ ├── __init__.py │ └── user.py └── validator.py ├── circle.yml ├── docker-compose.yml ├── fabfile.py ├── input_emojis ├── .python-version ├── images │ ├── eight.png │ ├── example.png │ ├── five.png │ ├── four.png │ ├── fu.png │ ├── fu_.png │ ├── gin.png │ ├── gin_.png │ ├── go.png │ ├── gyoku.png │ ├── gyoku_.png │ ├── hati.png │ ├── hi.png │ ├── hi_.png │ ├── iti.png │ ├── kaku.png │ ├── kaku_.png │ ├── kei.png │ ├── kei_.png │ ├── kin.png │ ├── kin_.png │ ├── kyou.png │ ├── kyou_.png │ ├── kyu.png │ ├── last_fu.png │ ├── last_fu_.png │ ├── last_gin.png │ ├── last_gin_.png │ ├── last_gyoku.png │ ├── last_gyoku_.png │ ├── last_hi.png │ ├── last_hi_.png │ ├── last_kaku.png │ ├── last_kaku_.png │ ├── last_kei.png │ ├── last_kei_.png │ ├── last_kin.png │ ├── last_kin_.png │ ├── last_kyou.png │ ├── last_kyou_.png │ ├── last_narigin.png │ ├── last_narigin_.png │ ├── last_narikei.png │ ├── last_narikei_.png │ ├── last_narikyou.png │ ├── last_narikyou_.png │ ├── last_ou.png │ ├── last_ou_.png │ ├── last_ryu.png │ ├── last_ryu_.png │ ├── last_tokin.png │ ├── last_tokin_.png │ ├── last_uma.png │ ├── last_uma_.png │ ├── mu.png │ ├── nana.png │ ├── narigin.png │ ├── narigin_.png │ ├── narikei.png │ ├── narikei_.png │ ├── narikyou.png │ ├── narikyou_.png │ ├── ni.png │ ├── nine.png │ ├── one.png │ ├── origin.png │ ├── ou.png │ ├── ou_.png │ ├── roku.png │ ├── ryu.png │ ├── ryu_.png │ ├── san.png │ ├── seven.png │ ├── six.png │ ├── three.png │ ├── tokin.png │ ├── tokin_.png │ ├── two.png │ ├── uma.png │ ├── uma_.png │ └── yon.png └── input_emojis.py ├── main.py ├── setup.py ├── slackbot_settings.py.default ├── slackbot_settings.py.docker └── test ├── __init__.py ├── modules ├── __init__.py ├── input_parse_test.py ├── shogi_input_test.py ├── shogi_output_test.py └── shogi_test.py ├── slack_utils ├── __init__.py └── user_test.py └── test.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | # Regexes for lines to exclude from consideration 6 | exclude_lines = 7 | # Have to re-enable the standard pragma 8 | pragma: no cover 9 | 10 | # Don't complain about missing debug-only code: 11 | def __repr__ 12 | if self\.debug 13 | 14 | # Don't complain if tests don't hit defensive assertion code: 15 | raise AssertionError 16 | raise NotImplementedError 17 | 18 | # Don't complain if non-runnable code isn't run: 19 | if 0: 20 | if __name__ == .__main__.: 21 | 22 | ignore_errors = True 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | slackbot_settings.py 2 | 3 | ### https://raw.github.com/github/gitignore/fceac113a3a20e00718d6317e468eec27f6e2d99/Global/osx.gitignore 4 | 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | 30 | 31 | ### https://raw.github.com/github/gitignore/fceac113a3a20e00718d6317e468eec27f6e2d99/python.gitignore 32 | 33 | # Byte-compiled / optimized / DLL files 34 | __pycache__/ 35 | *.py[cod] 36 | *$py.class 37 | 38 | # C extensions 39 | *.so 40 | 41 | # Distribution / packaging 42 | .Python 43 | env/ 44 | build/ 45 | develop-eggs/ 46 | dist/ 47 | downloads/ 48 | eggs/ 49 | .eggs/ 50 | lib/ 51 | lib64/ 52 | parts/ 53 | sdist/ 54 | var/ 55 | *.egg-info/ 56 | .installed.cfg 57 | *.egg 58 | 59 | # PyInstaller 60 | # Usually these files are written by a python script from a template 61 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 62 | *.manifest 63 | *.spec 64 | 65 | # Installer logs 66 | pip-log.txt 67 | pip-delete-this-directory.txt 68 | 69 | # Unit test / coverage reports 70 | htmlcov/ 71 | .tox/ 72 | .coverage 73 | .coverage.* 74 | .cache 75 | nosetests.xml 76 | coverage.xml 77 | *,cover 78 | .hypothesis/ 79 | 80 | # Translations 81 | *.mo 82 | *.pot 83 | 84 | # Django stuff: 85 | *.log 86 | 87 | # Sphinx documentation 88 | docs/_build/ 89 | 90 | # PyBuilder 91 | target/ 92 | 93 | 94 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.5.1 2 | MAINTAINER Kouki Saito 3 | 4 | RUN groupadd -r slackbot && useradd -r -g slackbot slackbot 5 | COPY . /app 6 | WORKDIR /app 7 | RUN python setup.py develop 8 | COPY slackbot_settings.py.docker slackbot_settings.py 9 | 10 | USER slackbot 11 | CMD ["python", "main.py"] 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 setokinto 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CircleCI](https://circleci.com/gh/setokinto/slack-shogi.svg?style=svg)](https://circleci.com/gh/setokinto/slack-shogi) 2 | [![Code Climate](https://codeclimate.com/github/setokinto/slack-shogi/badges/gpa.svg)](https://codeclimate.com/github/setokinto/slack-shogi) 3 | [![Test Coverage](https://codeclimate.com/github/setokinto/slack-shogi/badges/coverage.svg)](https://codeclimate.com/github/setokinto/slack-shogi/coverage) 4 | [![Issue Count](https://codeclimate.com/github/setokinto/slack-shogi/badges/issue_count.svg)](https://codeclimate.com/github/setokinto/slack-shogi) 5 | 6 | Slack-Shogi 7 | ----------- 8 | This is a room, with a shogi board. 9 | 10 | # How to use 11 | 1. Create a Bot https://your-team-name.slack.com/apps/A0F7YS25R-bots 12 | 2. Copy config file `cp slackbot_settings.py.default slackbot_settings.py` 13 | 3. Replace your api key with your bot from slack 14 | 15 | # Commands 16 | `@shogibot: start with @username` 17 | `@shogibot: board` 18 | `@shogibot: 76歩` 19 | `@shogibot: resign` or `@shogibot: 参りました` 20 | And more! 21 | 22 | If you want to try it, use this command `@shogibot: start with @your_user_name` 23 | 24 | # How to input shogi emojis 25 | We provide an images for playing shogi, and a script to input images to your slack team. 26 | The script required your slack id and password because an api which saves a new emoji does not exist. 27 | Emoji example is here: 28 | ![Shogi](https://raw.githubusercontent.com/setokinto/slack-shogi/master/input_emojis/images/example.png) 29 | 30 | It requires Python2 for mechanize. 31 | ``` 32 | % cd input_emojis 33 | % pip install mechanize 34 | % python input_emojis.py 35 | your slack team id: [your_team_id] 36 | your id (email): [your_id@example.com] 37 | your password: [******] 38 | authentication code for two factor(If needed) : [two-factor code or empty if you don't use two-factor-authentication] 39 | ... 40 | ... 41 | ... 42 | Completed!! 43 | ``` 44 | 45 | If you already inputted emojis and has a difference for adding emojis, you can use `--patch` option for this script. The script called with this optioin ignores emojis which already registed. 46 | 47 | # Using Docker 48 | You are using Docker? Yes. It is available for Docker. 49 | 50 | ## Try it easily 51 | Try this bot with `docker run --env SLACK_SHOGI_API_TOKEN="" setokinto/slack-shogi` 52 | 53 | ## Develop with Docker 54 | 1. Move slackbot_settings.py from slackbot_settings.py.default and put youput your keys. 55 | 2. `docker-compose up` 56 | 3. In your slack team, say `@yourbot: start with @username` 57 | 58 | # Thanks for 59 | - [Slackbot](https://github.com/lins05/slackbot) 60 | - [Slacker](https://github.com/os/slacker) 61 | - [Slack](https://slack.com) 62 | 63 | # LICENSE 64 | MIT 65 | 66 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | import app.shogi 2 | -------------------------------------------------------------------------------- /app/for_test.py: -------------------------------------------------------------------------------- 1 | 2 | class ForTest: 3 | 4 | def return1(self): 5 | return 1 6 | -------------------------------------------------------------------------------- /app/helper.py: -------------------------------------------------------------------------------- 1 | 2 | import functools 3 | 4 | from app.modules.shogi_input import ShogiInput 5 | 6 | def channel_info(f): 7 | @functools.wraps(f) 8 | def _warp(*args, **kwargs): 9 | message = args[0] 10 | channel_id = message.body["channel"] 11 | own_id = message.body["user"] 12 | 13 | channel_info = ChannelInfo(channel_id, own_id) 14 | args = (channel_info,) + args 15 | f(*args, **kwargs) 16 | return _warp 17 | 18 | def should_exist_shogi(f): 19 | @functools.wraps(f) 20 | def _warp(*args, **kwargs): 21 | channel = args[0] 22 | message = args[1] 23 | if not ShogiInput.exists(channel.channel_id): 24 | message.reply("start withから初めてね") 25 | return 26 | 27 | f(*args, **kwargs) 28 | return _warp 29 | 30 | class ChannelInfo: 31 | def __init__(self, channel_id, own_id): 32 | self.channel_id = channel_id 33 | self.own_id = own_id 34 | 35 | -------------------------------------------------------------------------------- /app/kifu.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Kifu: 4 | 5 | def __init__(self): 6 | self.kifu = [] 7 | 8 | def add(self, from_x, from_y, to_x, to_y, promote, koma): 9 | self.kifu.append((from_x, from_y, to_x, to_y, promote, koma)) 10 | 11 | def pop(self): 12 | return self.kifu.pop() 13 | 14 | -------------------------------------------------------------------------------- /app/modules/input.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/app/modules/input.py -------------------------------------------------------------------------------- /app/modules/parse_input.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | from app.modules.shogi import Koma 4 | 5 | str2info = { 6 | "一": 0, "1": 0, "1": 0, 7 | "二": 1, "2": 1, "2": 1, 8 | "三": 2, "3": 2, "3": 2, 9 | "四": 3, "4": 3, "4": 3, 10 | "五": 4, "5": 4, "5": 4, 11 | "六": 5, "6": 5, "6": 5, 12 | "七": 6, "7": 6, "7": 6, 13 | "八": 7, "8": 7, "8": 7, 14 | "九": 8, "9": 8, "9": 8 15 | } 16 | 17 | str2koma = { 18 | "歩": Koma.fu, "と": Koma.promoted_fu, 19 | "成歩": Koma.promoted_fu, "成と": Koma.promoted_fu, 20 | 21 | "香": Koma.kyosha, "香車": Koma.kyosha, 22 | "成香": Koma.promoted_kyosha, "成香車": Koma.promoted_kyosha, 23 | 24 | "桂": Koma.keima, "桂馬": Koma.keima, 25 | "成桂": Koma.promoted_keima, "成桂馬": Koma.promoted_keima, 26 | 27 | "銀": Koma.gin, "銀将": Koma.gin, 28 | "成銀": Koma.promoted_gin, "成銀将": Koma.promoted_gin, 29 | 30 | "金": Koma.kin, "金将": Koma.kin, 31 | "成金": Koma.kin, "成金将": Koma.kin, 32 | 33 | "角": Koma.kaku, "角行": Koma.kaku, "馬": Koma.promoted_kaku, 34 | "成角": Koma.promoted_kaku, "成角行": Koma.promoted_kaku, 35 | "成馬": Koma.promoted_kaku, 36 | 37 | "飛": Koma.hisha, "飛車": Koma.hisha, "龍": Koma.promoted_hisha, 38 | "成飛": Koma.promoted_hisha, "成飛車": Koma.promoted_hisha, 39 | "成龍": Koma.promoted_hisha, 40 | 41 | "王": Koma.gyoku, "玉": Koma.gyoku, 42 | "王将": Koma.gyoku, "玉将": Koma.gyoku, 43 | "成王": Koma.gyoku, "成玉": Koma.gyoku, 44 | "成王将": Koma.gyoku, "成玉将": Koma.gyoku 45 | } 46 | 47 | str2oppkoma = { 48 | "歩": Koma.opponent_fu, "と": Koma.opponent_promoted_fu, 49 | "成歩": Koma.opponent_promoted_fu, "成と": Koma.opponent_promoted_fu, 50 | 51 | "香": Koma.opponent_kyosha, "香車": Koma.opponent_kyosha, 52 | "成香": Koma.opponent_promoted_kyosha, 53 | "成香車": Koma.opponent_promoted_kyosha, 54 | 55 | "桂": Koma.opponent_keima, "桂馬": Koma.opponent_keima, 56 | "成桂": Koma.opponent_promoted_keima, 57 | "成桂馬": Koma.opponent_promoted_keima, 58 | 59 | "銀": Koma.opponent_gin, "銀将": Koma.opponent_gin, 60 | "成銀": Koma.opponent_promoted_gin, 61 | "成銀将": Koma.opponent_promoted_gin, 62 | 63 | "金": Koma.opponent_kin, "金将": Koma.opponent_kin, 64 | "成金": Koma.opponent_kin, "成金将": Koma.opponent_kin, 65 | 66 | "角": Koma.opponent_kaku, "角行": Koma.opponent_kaku, 67 | "馬": Koma.opponent_promoted_kaku, 68 | "成角": Koma.opponent_promoted_kaku, 69 | "成角行": Koma.opponent_promoted_kaku, 70 | "成馬": Koma.opponent_promoted_kaku, 71 | 72 | "飛": Koma.opponent_hisha, "飛車": Koma.opponent_hisha, 73 | "龍": Koma.opponent_promoted_hisha, 74 | "成飛": Koma.opponent_promoted_hisha, 75 | "成飛車": Koma.opponent_promoted_hisha, 76 | "成龍": Koma.opponent_promoted_hisha, 77 | 78 | "王": Koma.opponent_gyoku, "玉": Koma.opponent_gyoku, 79 | "王将": Koma.opponent_gyoku, "玉将": Koma.opponent_gyoku, 80 | "成王": Koma.opponent_gyoku, "成玉": Koma.opponent_gyoku, 81 | "成王将": Koma.opponent_gyoku, "成玉将": Koma.opponent_gyoku 82 | } 83 | 84 | koma_names = [ 85 | "歩", "と", 86 | "香", "香車", 87 | "桂", "桂馬", 88 | "銀", "銀将", 89 | "金", "金将", 90 | "角", "角行", "馬", 91 | "飛", "飛車", "龍", 92 | "王", "玉", "王将", "玉将" 93 | ] 94 | 95 | koma_names += list(map(lambda n: "成" + n, koma_names)) 96 | 97 | 98 | def transposition_num(num): 99 | """ 100 | transposition axis(y) number. 101 | 0 => 8, 1 => 7, ..., 8 => 0 102 | """ 103 | return (4 - num) + 4 104 | 105 | 106 | class ParseInput: 107 | 108 | @staticmethod 109 | def parse(input_str, shogi): 110 | """ 111 | parse input text and get (from, to) Coordinate. 112 | """ 113 | is_first_turn = shogi.first 114 | 115 | def get_koma(): 116 | # input_str is only koma name 117 | if input_str in koma_names: 118 | if is_first_turn: 119 | koma = str2koma[input_str] 120 | else: 121 | koma = str2oppkoma[input_str] 122 | return koma 123 | return False 124 | 125 | # promote 126 | promote = False 127 | if input_str[-1] == ("成"): 128 | if input_str.find("打") != -1: 129 | return False 130 | 131 | if input_str[-2] == ("不"): 132 | input_str = input_str.replace("不成", "") 133 | else: 134 | # TODO : Detect to be able to promote 135 | promote = True 136 | input_str = input_str.replace("成", "") 137 | 138 | # same 139 | if input_str.find("同") != -1: 140 | idx = input_str.find("同") + 1 141 | input_str = input_str[idx:] 142 | to_x = shogi.last_move_x 143 | to_y = shogi.last_move_y 144 | 145 | # get to_x, to_y from text 146 | else: 147 | if input_str[0] in str2info and input_str[0] in str2info: 148 | to_x = transposition_num(str2info[input_str[0]]) 149 | to_y = str2info[input_str[1]] 150 | else: 151 | # TODO : Send Error Message 152 | return False 153 | 154 | input_str = input_str[2:] # remove number of axis 155 | 156 | # setting from flag 157 | from_flag = 0 158 | if input_str.find("上") != -1: 159 | from_flag = 1 160 | input_str = input_str.replace("上", "") 161 | 162 | if input_str.find("右") != -1: 163 | from_flag += 2 164 | input_str = input_str.replace("右", "") 165 | 166 | # 3 => 右上 167 | 168 | if input_str.find("引") != -1: 169 | from_flag += 4 170 | input_str = input_str.replace("引", "") 171 | 172 | # 5 => None 173 | # 6 => 右引 174 | # 7 => None 175 | 176 | if input_str.find("左") != -1: 177 | from_flag += 8 178 | input_str = input_str.replace("左", "") 179 | 180 | # 9 => 左上 181 | # 10,11 => None 182 | # 12 => 左引 183 | # 13~15 => None 184 | 185 | if input_str.find("寄") != -1: 186 | from_flag = 16 187 | input_str = input_str.replace("寄", "") 188 | 189 | if input_str.find("直") != -1: 190 | from_flag = 17 191 | input_str = input_str.replace("直", "") 192 | 193 | # drop 194 | if input_str.find("打") != -1: 195 | from_x = -1 196 | from_y = -1 197 | input_str = input_str.replace("打", "") 198 | koma = get_koma() 199 | if shogi.droppable(koma, to_x, to_y): 200 | return (from_x, from_y, to_x, to_y, promote, koma) 201 | else: 202 | return False 203 | 204 | # if in this block, input_str is only koma name 205 | koma = get_koma() 206 | if not koma: 207 | return False 208 | candidate_komas = shogi.find_koma(koma) 209 | movable_komas = [] 210 | for candidate_koma in candidate_komas: 211 | if shogi.movable(candidate_koma[0], 212 | candidate_koma[1], 213 | to_x, to_y, promote): 214 | movable_komas.append(candidate_koma) 215 | 216 | if len(movable_komas) == 0: 217 | # TODO : Send Error Message 218 | return False 219 | 220 | elif len(movable_komas) == 1: 221 | from_x = movable_komas[0][0] 222 | from_y = movable_komas[0][1] 223 | 224 | else: 225 | turn = is_first_turn # for pep 226 | # "上" 227 | if from_flag == 1: 228 | for t in movable_komas: # t => "t"arget 229 | if (turn and t[1] > to_y) or \ 230 | (not turn and t[1] < to_y): 231 | from_x, from_y = t 232 | from_flag = 0 233 | break 234 | 235 | # "右" 236 | elif from_flag == 2: 237 | for t in movable_komas: 238 | if (turn and t[0] > to_x) or \ 239 | (not turn and t[0] < to_x): 240 | from_x, from_y = t 241 | from_flag = 0 242 | break 243 | 244 | # "右上" 245 | elif from_flag == 3: 246 | for t in movable_komas: 247 | if (turn and t[0] > to_x and t[1] > to_y) or \ 248 | (not turn and t[0] < to_x and t[1] < to_y): 249 | from_x, from_y = t 250 | from_flag = 0 251 | break 252 | 253 | # "引" 254 | elif from_flag == 4: 255 | for t in movable_komas: 256 | if (turn and t[1] < to_y) or \ 257 | (not turn and t[1] > to_y): 258 | from_x, from_y = t 259 | from_flag = 0 260 | break 261 | 262 | # "右引" 263 | elif from_flag == 6: 264 | for t in movable_komas: 265 | if (turn and t[0] > to_x and t[1] < to_y) or \ 266 | (not turn and t[0] < to_x and t[1] > to_y): 267 | from_x, from_y = t 268 | from_flag = 0 269 | break 270 | 271 | # "左" 272 | elif from_flag == 8: 273 | for t in movable_komas: 274 | if (turn and t[0] < to_x) or \ 275 | (not turn and t[0] > to_x): 276 | from_x, from_y = t 277 | from_flag = 0 278 | break 279 | 280 | # "左上" 281 | elif from_flag == 9: 282 | for t in movable_komas: 283 | if (turn and t[0] < to_x and t[1] > to_y) or \ 284 | (not turn and t[0] > to_x and t[1] < to_y): 285 | from_x, from_y = t 286 | from_flag = 0 287 | break 288 | 289 | # "左引" 290 | elif from_flag == 12: 291 | for t in movable_komas: 292 | if (turn and t[0] < to_x and t[1] < to_y) or \ 293 | (not turn and t[0] > to_x and t[1] > to_y): 294 | from_x, from_y = t 295 | from_flag = 0 296 | break 297 | 298 | # "寄" 299 | elif from_flag == 16: 300 | for t in movable_komas: 301 | if (t[1] == to_y): 302 | from_x, from_y = t 303 | from_flag = 0 304 | break 305 | 306 | # "直" 307 | elif from_flag == 17: 308 | for t in movable_komas: 309 | if (t[0] == to_x and ((turn and t[1] > to_y) or (not turn and t[1] < to_y))): 310 | from_x, from_y = t 311 | from_flag = 0 312 | break 313 | 314 | # TODO : Send Error Message 315 | if from_flag != 0: 316 | return False 317 | return (from_x, from_y, to_x, to_y, promote, koma) 318 | 319 | -------------------------------------------------------------------------------- /app/modules/shogi.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from enum import IntEnum 4 | 5 | 6 | class Koma(IntEnum): 7 | empty = 0 8 | fu = 0x01 9 | kyosha = 0x02 10 | keima = 0x03 11 | gin = 0x04 12 | kin = 0x05 13 | kaku = 0x06 14 | hisha = 0x07 15 | gyoku = 0x08 16 | promoted_fu = 0x11 17 | promoted_kyosha = 0x12 18 | promoted_keima = 0x13 19 | promoted_gin = 0x14 20 | promoted_kaku = 0x16 21 | promoted_hisha = 0x17 22 | 23 | opponent_fu = 0x21 24 | opponent_kyosha = 0x22 25 | opponent_keima = 0x23 26 | opponent_gin = 0x24 27 | opponent_kin = 0x25 28 | opponent_kaku = 0x26 29 | opponent_hisha = 0x27 30 | opponent_gyoku = 0x28 31 | opponent_promoted_fu = 0x31 32 | opponent_promoted_kyosha = 0x32 33 | opponent_promoted_keima = 0x33 34 | opponent_promoted_gin = 0x34 35 | opponent_promoted_kaku = 0x36 36 | opponent_promoted_hisha = 0x37 37 | 38 | def is_first(self): 39 | if 0x01 <= self.value < 0x20: 40 | return True 41 | elif 0x20 <= self.value < 0x40: 42 | return False 43 | 44 | def is_keima(self): 45 | if self is Koma.keima: 46 | return True 47 | if self is Koma.opponent_keima: 48 | return True 49 | return False 50 | 51 | def can_promote(self): 52 | if self is Koma.kin or self is Koma.gyoku or self is Koma.opponent_kin or self is Koma.opponent_gyoku: 53 | return False 54 | if self.value & 0x10: 55 | return False 56 | return True 57 | 58 | def promote(self): 59 | try: 60 | return Koma(self.value | 0x10) 61 | except ValueError: 62 | return self 63 | 64 | def unpromote(self): 65 | try: 66 | return Koma(self.value & 0x2F) 67 | except ValueError: 68 | return self 69 | 70 | def go_enemy(self): 71 | if self.is_first(): 72 | return Koma(self.value + 0x20) 73 | else: 74 | return Koma(self.value - 0x20) 75 | 76 | 77 | class Shogi: 78 | # TODO: implement komaochi 79 | 80 | def __init__(self): 81 | self.first = True 82 | self.first_tegoma = [] 83 | self.second_tegoma = [] 84 | self.last_move_x = None 85 | self.last_move_y = None 86 | self.board = [ 87 | [ 88 | Koma.opponent_kyosha, 89 | Koma.opponent_keima, 90 | Koma.opponent_gin, 91 | Koma.opponent_kin, 92 | Koma.opponent_gyoku, 93 | Koma.opponent_kin, 94 | Koma.opponent_gin, 95 | Koma.opponent_keima, 96 | Koma.opponent_kyosha, 97 | ], 98 | [ 99 | Koma.empty, 100 | Koma.opponent_hisha, 101 | Koma.empty, 102 | Koma.empty, 103 | Koma.empty, 104 | Koma.empty, 105 | Koma.empty, 106 | Koma.opponent_kaku, 107 | Koma.empty, 108 | ], 109 | [ 110 | Koma.opponent_fu, 111 | Koma.opponent_fu, 112 | Koma.opponent_fu, 113 | Koma.opponent_fu, 114 | Koma.opponent_fu, 115 | Koma.opponent_fu, 116 | Koma.opponent_fu, 117 | Koma.opponent_fu, 118 | Koma.opponent_fu, 119 | ], 120 | [ 121 | Koma.empty, 122 | Koma.empty, 123 | Koma.empty, 124 | Koma.empty, 125 | Koma.empty, 126 | Koma.empty, 127 | Koma.empty, 128 | Koma.empty, 129 | Koma.empty, 130 | ], 131 | [ 132 | Koma.empty, 133 | Koma.empty, 134 | Koma.empty, 135 | Koma.empty, 136 | Koma.empty, 137 | Koma.empty, 138 | Koma.empty, 139 | Koma.empty, 140 | Koma.empty, 141 | ], 142 | [ 143 | Koma.empty, 144 | Koma.empty, 145 | Koma.empty, 146 | Koma.empty, 147 | Koma.empty, 148 | Koma.empty, 149 | Koma.empty, 150 | Koma.empty, 151 | Koma.empty, 152 | ], 153 | [ 154 | Koma.fu, 155 | Koma.fu, 156 | Koma.fu, 157 | Koma.fu, 158 | Koma.fu, 159 | Koma.fu, 160 | Koma.fu, 161 | Koma.fu, 162 | Koma.fu, 163 | ], 164 | [ 165 | Koma.empty, 166 | Koma.kaku, 167 | Koma.empty, 168 | Koma.empty, 169 | Koma.empty, 170 | Koma.empty, 171 | Koma.empty, 172 | Koma.hisha, 173 | Koma.empty, 174 | ], 175 | [ 176 | Koma.kyosha, 177 | Koma.keima, 178 | Koma.gin, 179 | Koma.kin, 180 | Koma.gyoku, 181 | Koma.kin, 182 | Koma.gin, 183 | Koma.keima, 184 | Koma.kyosha, 185 | ], 186 | ] 187 | 188 | def move(self, from_x, from_y, to_x, to_y, promote): 189 | koma = self.board[from_y][from_x] 190 | koma_for_komadai = self.board[to_y][to_x] 191 | if koma_for_komadai is not Koma.empty: 192 | koma_for_komadai = koma_for_komadai.unpromote().go_enemy() 193 | if self.first: 194 | self.first_tegoma.append(koma_for_komadai) 195 | else: 196 | self.second_tegoma.append(koma_for_komadai) 197 | self.board[from_y][from_x] = Koma.empty 198 | if promote: 199 | self.board[to_y][to_x] = koma.promote() 200 | else: 201 | self.board[to_y][to_x] = koma 202 | self.first = not self.first 203 | self.last_move_x = to_x 204 | self.last_move_y = to_y 205 | 206 | def movable(self, from_x, from_y, to_x, to_y, promote): 207 | board = self.board 208 | from_koma = board[from_y][from_x] 209 | to_koma = board[to_y][to_x] 210 | 211 | if from_koma is Koma.empty: 212 | return False 213 | if not self.first == from_koma.is_first(): 214 | return False 215 | if not promote: 216 | if (from_koma is Koma.fu or from_koma is Koma.kyosha) and to_y == 0: 217 | return False 218 | if from_koma is Koma.keima and to_y <= 1: 219 | return False 220 | if (from_koma is Koma.opponent_fu or from_koma is Koma.opponent_kyosha) and to_y == 8: 221 | return False 222 | if from_koma is Koma.opponent_keima and 7 <= to_y: 223 | return False 224 | if promote: 225 | if not from_koma.can_promote(): 226 | return False 227 | if from_koma.is_first(): 228 | if not (0 <= from_y <= 2 or 0 <= to_y <= 2): 229 | return False 230 | else: 231 | if not (6 <= from_y <= 8 or 6 <= to_y <= 8): 232 | return False 233 | 234 | for movable_position in movable_positions[from_koma]: 235 | if from_x + movable_position[0] == to_x and from_y + movable_position[1] == to_y: 236 | if to_koma is Koma.empty or not to_koma.is_first() == self.first: 237 | if self.checkObstacle(from_x, from_y, to_x, to_y): 238 | return True 239 | return False 240 | 241 | def drop(self, koma, to_x, to_y): 242 | if self.first: 243 | self.first_tegoma.remove(koma) 244 | else: 245 | self.second_tegoma.remove(koma) 246 | koma_for_komadai = self.board[to_y][to_x] 247 | self.board[to_y][to_x] = koma 248 | if koma_for_komadai is not Koma.empty: 249 | koma_for_komadai = koma_for_komadai.unpromote().go_enemy() 250 | if self.first: 251 | self.first_tegoma.append(koma_for_komadai) 252 | else: 253 | self.second_tegoma.append(koma_for_komadai) 254 | self.first = not self.first 255 | self.last_move_x = to_x 256 | self.last_move_y = to_y 257 | 258 | def droppable(self, koma, to_x, to_y): 259 | if self.first: 260 | tegoma = self.first_tegoma 261 | else: 262 | tegoma = self.second_tegoma 263 | if koma in tegoma and self.board[to_y][to_x] is Koma.empty: 264 | if koma is Koma.fu or koma is Koma.opponent_fu: 265 | for y_board in self.board: 266 | if y_board[to_x] is koma: 267 | # 2fu 268 | return False 269 | if koma is Koma.fu or koma is Koma.kyosha: 270 | if to_y == 0: 271 | return False 272 | if koma is Koma.keima: 273 | if to_y <= 1: 274 | return False 275 | if koma is Koma.opponent_fu or koma is Koma.opponent_kyosha: 276 | if to_y == 8: 277 | return False 278 | if koma is Koma.opponent_keima: 279 | if to_y >= 7: 280 | return False 281 | return True 282 | return False 283 | 284 | def checkObstacle(self, from_x, from_y, to_x, to_y): 285 | if self.board[from_y][from_x].is_keima(): 286 | return True 287 | while True: 288 | to_x = _approach(from_x, to_x) 289 | to_y = _approach(from_y, to_y) 290 | if from_x == to_x and from_y == to_y: 291 | break 292 | road_koma = self.board[to_y][to_x] 293 | if road_koma is Koma.empty: 294 | continue 295 | else: 296 | return False 297 | return True 298 | 299 | def find_koma(self, koma): 300 | koma_positions = [] 301 | for y in range(len(self.board)): 302 | for x in range(len(self.board[y])): 303 | if self.board[y][x] == koma: 304 | koma_positions.append([x, y]) 305 | return koma_positions 306 | 307 | 308 | def _approach(to, by): 309 | if to < by: 310 | return by - 1 311 | elif to > by: 312 | return by + 1 313 | else: 314 | return by 315 | 316 | movable_positions = { 317 | Koma.fu: [[0, -1]], 318 | Koma.kyosha: [[0, -1], [0, -2], [0, -3], [0, -4], [0, -5], [0, -6], [0, -7], [0, -8]], 319 | Koma.keima: [[-1, -2], [1, -2]], 320 | Koma.gin: [[-1, -1], [0, -1], [1, -1], [-1, 1], [1, 1]], 321 | Koma.kin: [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [0, 1]], 322 | Koma.kaku: [[-8, -8], [8, -8], [-7, -7], [7, -7], [-6, -6], [6, -6], [-5, -5], [5, -5], [-4, -4], [4, -4], [-3, -3], [3, -3], [-2, -2], [2, -2], [-1, -1], [1, -1], [-1, 1], [1, 1], [-2, 2], [2, 2], [-3, 3], [3, 3], [-4, 4], [4, 4], [-5, 5], [5, 5], [-6, 6], [6, 6], [-7, 7], [7, 7], [-8, 8], [8, 8]], 323 | Koma.hisha: [[0, -8], [0, -7], [0, -6], [0, -5], [0, -4], [0, -3], [0, -2], [0, -1], [-8, 0], [-7, 0], [-6, 0], [-5, 0], [-4, 0], [-3, 0], [-2, 0], [-1, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0], [6, 0], [7, 0], [8, 0], [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8]], 324 | Koma.gyoku: [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]], 325 | } 326 | movable_positions[Koma.promoted_fu] = movable_positions[Koma.promoted_kyosha] = movable_positions[ 327 | Koma.promoted_keima] = movable_positions[Koma.promoted_gin] = movable_positions[Koma.kin] 328 | movable_positions[Koma.promoted_kaku] = movable_positions[ 329 | Koma.kaku] + [[0, -1], [-1, 0], [1, 0], [0, 1]] 330 | movable_positions[Koma.promoted_hisha] = movable_positions[ 331 | Koma.hisha] + [[-1, -1], [1, -1], [-1, 1], [1, 1]] 332 | 333 | movable_positions[Koma.opponent_fu] = [[0, 1]] 334 | movable_positions[Koma.opponent_kyosha] = [ 335 | [0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [0, 6], [0, 7], [0, 8]] 336 | movable_positions[Koma.opponent_keima] = [[1, 2], [-1, 2]] 337 | movable_positions[Koma.opponent_gin] = [ 338 | [1, 1], [0, 1], [-1, 1], [1, -1], [-1, -1]] 339 | movable_positions[Koma.opponent_kin] = [ 340 | [1, 1], [0, 1], [-1, 1], [1, 0], [-1, 0], [0, -1]] 341 | movable_positions[Koma.opponent_kaku] = movable_positions[Koma.kaku] 342 | movable_positions[Koma.opponent_hisha] = movable_positions[Koma.hisha] 343 | movable_positions[Koma.opponent_gyoku] = movable_positions[Koma.gyoku] 344 | 345 | movable_positions[Koma.opponent_promoted_fu] = movable_positions[Koma.opponent_promoted_kyosha] = movable_positions[ 346 | Koma.opponent_promoted_keima] = movable_positions[Koma.opponent_promoted_gin] = movable_positions[Koma.opponent_kin] 347 | 348 | movable_positions[Koma.opponent_promoted_kaku] = movable_positions[ 349 | Koma.promoted_kaku] 350 | movable_positions[Koma.opponent_promoted_hisha] = movable_positions[ 351 | Koma.promoted_hisha] 352 | -------------------------------------------------------------------------------- /app/modules/shogi_input.py: -------------------------------------------------------------------------------- 1 | 2 | import uuid 3 | import random 4 | 5 | from app.slack_utils.user import User as UserFinder 6 | from app.modules.shogi import Shogi as ShogiModule 7 | from app.modules.parse_input import ParseInput 8 | from app.validator import BasicUserValidator, AllPassUserValidator 9 | from app.kifu import Kifu 10 | 11 | 12 | class UserDifferentException(Exception): 13 | pass 14 | 15 | 16 | class KomaCannotMoveException(Exception): 17 | pass 18 | 19 | 20 | class ShogiManager: 21 | 22 | def __init__(self): 23 | self.shogi = {} 24 | 25 | def is_creatable(self, channel_id): 26 | if channel_id in self.shogi: 27 | return False 28 | else: 29 | return True 30 | 31 | def create(self, channel_id, users): 32 | if self.is_creatable(channel_id): 33 | shogi = Shogi(channel_id, users) 34 | self.shogi[channel_id] = shogi 35 | return shogi 36 | else: 37 | raise Exception() 38 | 39 | def get_shogi(self, channel_id): 40 | if channel_id in self.shogi: 41 | return self.shogi[channel_id] 42 | else: 43 | return None 44 | 45 | def is_exists(self, channel_id): 46 | return channel_id in self.shogi 47 | 48 | def clear(self, channel_id): 49 | if channel_id in self.shogi: 50 | del self.shogi[channel_id] 51 | 52 | 53 | class ShogiInput: 54 | manager = ShogiManager() 55 | 56 | @staticmethod 57 | def init(channel_id, users): 58 | if ShogiInput.creatable_new_shogi(channel_id, users): 59 | shogi = ShogiInput.manager.create(channel_id, users) 60 | return shogi 61 | else: 62 | return None 63 | 64 | @staticmethod 65 | def creatable_new_shogi(channel_id, users): 66 | for user in users: 67 | if user["id"] is None: 68 | return False 69 | if ShogiInput.manager.is_creatable(channel_id): 70 | return True 71 | else: 72 | return False 73 | 74 | @staticmethod 75 | def exists(channel_id): 76 | return ShogiInput.manager.is_exists(channel_id) 77 | 78 | @staticmethod 79 | def koma_is_movable(channel_id, user_id, position, koma, sub_position, promote): 80 | # TODO: check can move with shogi module 81 | return True 82 | 83 | @staticmethod 84 | def clear(channel_id): 85 | ShogiInput.manager.clear(channel_id) 86 | 87 | @staticmethod 88 | def move(movement_str, channel_id, user_id): 89 | shogi = ShogiInput.manager.get_shogi(channel_id) 90 | if not shogi.validate(shogi, user_id): 91 | raise UserDifferentException() 92 | 93 | movement = ParseInput.parse(movement_str, shogi) 94 | if not movement: 95 | raise KomaCannotMoveException() 96 | 97 | from_x, from_y, to_x, to_y, promote, koma = movement 98 | 99 | if from_x == -1 and from_y == -1 and shogi.droppable(koma, to_x, to_y): 100 | shogi.drop(koma, to_x, to_y) 101 | elif shogi.movable(from_x, from_y, to_x, to_y, promote): 102 | shogi.move(from_x, from_y, to_x, to_y, promote) 103 | else: 104 | raise KomaCannotMoveException() 105 | 106 | @staticmethod 107 | def setAllMode(channel_id): 108 | # TODO: is it need validation? 109 | shogi = ShogiInput.manager.get_shogi(channel_id) 110 | shogi.set_validator(AllPassUserValidator()) 111 | 112 | @staticmethod 113 | def basic_move(channel_id, from_x, from_y, to_x, to_y, promote): 114 | shogi = ShogiInput.manager.get_shogi(channel_id) 115 | shogi.move(from_x, from_y, to_x, to_y, promote) 116 | 117 | @staticmethod 118 | def get_shogi_board(channel_id): 119 | shogi = ShogiInput.manager.get_shogi(channel_id) 120 | if shogi is None: 121 | return None 122 | return { 123 | "first": shogi.first_tegoma, 124 | "second": shogi.second_tegoma, 125 | "board": shogi.board, 126 | "info": { 127 | "first": { 128 | "id": shogi.first_user.id, 129 | "name": shogi.first_user.name, 130 | }, 131 | "second": { 132 | "id": shogi.second_user.id, 133 | "name": shogi.second_user.name, 134 | } 135 | }, 136 | "turn": shogi.first, 137 | "_shogi": shogi, 138 | } 139 | 140 | @staticmethod 141 | def matta(channel_id, user_id): 142 | shogi = ShogiInput.manager.get_shogi(channel_id) 143 | if not shogi.validate(shogi, user_id): 144 | raise UserDifferentException() 145 | shogi.matta() 146 | 147 | 148 | class ShogiUser: 149 | 150 | def __init__(self, user_id, user_name): 151 | self.id = user_id 152 | self.name = user_name 153 | 154 | 155 | class Shogi: 156 | 157 | def __init__(self, channel_id, users, validator=BasicUserValidator()): 158 | self._shogi = ShogiModule() 159 | self.channel_id = channel_id 160 | self.user_ids = [x["id"] for x in users] 161 | random.shuffle(users) 162 | self.first_user = ShogiUser(users[0]["id"], users[0]["name"]) 163 | self.second_user = ShogiUser(users[1]["id"], users[1]["name"]) 164 | self.id = uuid.uuid4().hex 165 | self._validator = validator 166 | self.kifu = Kifu() 167 | 168 | def move(self, from_x, from_y, to_x, to_y, promote): 169 | self._shogi.move(from_x, from_y, to_x, to_y, promote) 170 | self.kifu.add(from_x, from_y, to_x, to_y, promote, None) 171 | 172 | def drop(self, koma, to_x, to_y): 173 | self._shogi.drop(koma, to_x, to_y) 174 | self.kifu.add(-1, -1, to_x, to_y, False, koma) 175 | 176 | def movable(self, from_x, from_y, to_x, to_y, promote): 177 | return self._shogi.movable(from_x, from_y, to_x, to_y, promote) 178 | 179 | def droppable(self, koma, to_x, to_y): 180 | return self._shogi.droppable(koma, to_x, to_y) 181 | 182 | def find_koma(self, koma): 183 | return self._shogi.find_koma(koma) 184 | 185 | def validate(self, shogi, user_id): 186 | return self._validator.validate(shogi, user_id) 187 | 188 | def set_validator(self, validator): 189 | self._validator = validator 190 | 191 | def matta(self): 192 | if len(self.kifu.kifu) == 0: 193 | raise KomaCannotMoveException 194 | self.kifu.pop() 195 | self._shogi = ShogiModule() 196 | for kifu in self.kifu.kifu: 197 | from_x, from_y, to_x, to_y, promote, koma = kifu 198 | if koma is None: 199 | self._shogi.move(from_x, from_y, to_x, to_y, promote) 200 | else: 201 | self._shogi.drop(koma, to_x, to_y) 202 | 203 | @property 204 | def first(self): 205 | return self._shogi.first 206 | 207 | @property 208 | def board(self): 209 | return self._shogi.board 210 | 211 | @property 212 | def last_move_x(self): 213 | return self._shogi.last_move_x 214 | 215 | @property 216 | def last_move_y(self): 217 | return self._shogi.last_move_y 218 | 219 | @property 220 | def first_tegoma(self): 221 | return self._shogi.first_tegoma 222 | 223 | @property 224 | def second_tegoma(self): 225 | return self._shogi.second_tegoma 226 | -------------------------------------------------------------------------------- /app/modules/shogi_output.py: -------------------------------------------------------------------------------- 1 | 2 | from app.modules.shogi import Koma 3 | 4 | emoji_prefix = "slackshogisss_" 5 | emoji_separetor = ":" 6 | 7 | koma2emoji = { 8 | Koma.empty: emoji_separetor + emoji_prefix + "mu" + emoji_separetor, 9 | Koma.fu: emoji_separetor + emoji_prefix + "fu" + emoji_separetor, 10 | Koma.kyosha: emoji_separetor + emoji_prefix + "kyou" + emoji_separetor, 11 | Koma.keima: emoji_separetor + emoji_prefix + "kei" + emoji_separetor, 12 | Koma.gin: emoji_separetor + emoji_prefix + "gin" + emoji_separetor, 13 | Koma.kin: emoji_separetor + emoji_prefix + "kin" + emoji_separetor, 14 | Koma.kaku: emoji_separetor + emoji_prefix + "kaku" + emoji_separetor, 15 | Koma.hisha: emoji_separetor + emoji_prefix + "hi" + emoji_separetor, 16 | Koma.gyoku: emoji_separetor + emoji_prefix + "gyoku" + emoji_separetor, 17 | Koma.promoted_fu: emoji_separetor + emoji_prefix + "tokin" + emoji_separetor, 18 | Koma.promoted_kyosha: emoji_separetor + emoji_prefix + "narikyou" + emoji_separetor, 19 | Koma.promoted_keima: emoji_separetor + emoji_prefix + "narikei" + emoji_separetor, 20 | Koma.promoted_gin: emoji_separetor + emoji_prefix + "narigin" + emoji_separetor, 21 | Koma.promoted_kaku: emoji_separetor + emoji_prefix + "uma" + emoji_separetor, 22 | Koma.promoted_hisha: emoji_separetor + emoji_prefix + "ryu" + emoji_separetor, 23 | 24 | Koma.opponent_fu: emoji_separetor + emoji_prefix + "fu_enemy" + emoji_separetor, 25 | Koma.opponent_kyosha: emoji_separetor + emoji_prefix + "kyou_enemy" + emoji_separetor, 26 | Koma.opponent_keima: emoji_separetor + emoji_prefix + "kei_enemy" + emoji_separetor, 27 | Koma.opponent_gin: emoji_separetor + emoji_prefix + "gin_enemy" + emoji_separetor, 28 | Koma.opponent_kin: emoji_separetor + emoji_prefix + "kin_enemy" + emoji_separetor, 29 | Koma.opponent_kaku: emoji_separetor + emoji_prefix + "kaku_enemy" + emoji_separetor, 30 | Koma.opponent_hisha: emoji_separetor + emoji_prefix + "hi_enemy" + emoji_separetor, 31 | Koma.opponent_gyoku: emoji_separetor + emoji_prefix + "ou_enemy" + emoji_separetor, 32 | Koma.opponent_promoted_fu: emoji_separetor + emoji_prefix + "tokin_enemy" + emoji_separetor, 33 | Koma.opponent_promoted_kyosha: emoji_separetor + emoji_prefix + "narikyou_enemy" + emoji_separetor, 34 | Koma.opponent_promoted_keima: emoji_separetor + emoji_prefix + "narikei_enemy" + emoji_separetor, 35 | Koma.opponent_promoted_gin: emoji_separetor + emoji_prefix + "narigin_enemy" + emoji_separetor, 36 | Koma.opponent_promoted_kaku: emoji_separetor + emoji_prefix + "uma_enemy" + emoji_separetor, 37 | Koma.opponent_promoted_hisha: emoji_separetor + emoji_prefix + "ryu_enemy" + emoji_separetor, 38 | } 39 | 40 | x_number2emoji = { 41 | 1: emoji_separetor + emoji_prefix + "iti" + emoji_separetor, 42 | 2: emoji_separetor + emoji_prefix + "ni" + emoji_separetor, 43 | 3: emoji_separetor + emoji_prefix + "san" + emoji_separetor, 44 | 4: emoji_separetor + emoji_prefix + "yon" + emoji_separetor, 45 | 5: emoji_separetor + emoji_prefix + "go" + emoji_separetor, 46 | 6: emoji_separetor + emoji_prefix + "roku" + emoji_separetor, 47 | 7: emoji_separetor + emoji_prefix + "nana" + emoji_separetor, 48 | 8: emoji_separetor + emoji_prefix + "hati" + emoji_separetor, 49 | 9: emoji_separetor + emoji_prefix + "kyu" + emoji_separetor 50 | } 51 | 52 | y_number2emoji = { 53 | 1: emoji_separetor + emoji_prefix + "one" + emoji_separetor, 54 | 2: emoji_separetor + emoji_prefix + "two" + emoji_separetor, 55 | 3: emoji_separetor + emoji_prefix + "three" + emoji_separetor, 56 | 4: emoji_separetor + emoji_prefix + "four" + emoji_separetor, 57 | 5: emoji_separetor + emoji_prefix + "five" + emoji_separetor, 58 | 6: emoji_separetor + emoji_prefix + "six" + emoji_separetor, 59 | 7: emoji_separetor + emoji_prefix + "seven" + emoji_separetor, 60 | 8: emoji_separetor + emoji_prefix + "eight" + emoji_separetor, 61 | 9: emoji_separetor + emoji_prefix + "nine" + emoji_separetor 62 | } 63 | 64 | 65 | def make_user_text(user_name, motigoma, 66 | is_first=True, is_turn=True, reverse=False): 67 | return_text = "" 68 | if is_turn: 69 | return_text = "[手番]" 70 | 71 | if is_first: 72 | return_text += " 先手 {} ".format(user_name) 73 | else: 74 | return_text += " 後手 {} ".format(user_name) 75 | user_info_char_num = len(return_text) # for new line 76 | 77 | if motigoma: 78 | cnt = 0 79 | for cur_koma in motigoma: 80 | if reverse: 81 | cur_koma = cur_koma.go_enemy() 82 | cnt += 1 83 | if cnt == 5: 84 | return_text += "\n" + (" " * user_info_char_num) 85 | cnt = 1 86 | return_text += koma2emoji[cur_koma] + " " 87 | else: 88 | return_text += "持ち駒なし" 89 | 90 | return_text += "\n\n" 91 | return return_text 92 | 93 | 94 | def make_board_info_text(board_info, reverse=False): 95 | return_text = "" 96 | x_labels = [1, 2, 3, 4, 5, 6, 7, 8, 9] 97 | y_labels = [9, 8, 7, 6, 5, 4, 3, 2, 1] 98 | 99 | loop_list = [0, 1, 2, 3, 4, 5, 6, 7, 8] 100 | if reverse: 101 | loop_list.reverse() 102 | 103 | if not reverse: 104 | for y_label in y_labels: 105 | return_text += y_number2emoji[y_label] 106 | return_text += "\n" 107 | for y in loop_list: 108 | if reverse: 109 | # because reverse mode, use x_number 110 | return_text += x_number2emoji[list(reversed(y_labels))[y]] 111 | for x in loop_list: 112 | cur_koma = board_info["board"][y][x] 113 | if reverse and cur_koma != Koma.empty: 114 | cur_koma = cur_koma.go_enemy() 115 | 116 | if x == board_info["_shogi"].last_move_x and \ 117 | y == board_info["_shogi"].last_move_y: 118 | return_text += koma2emoji[cur_koma].replace( 119 | emoji_prefix, 120 | emoji_prefix + "last_") 121 | else: 122 | return_text += koma2emoji[cur_koma] 123 | if not reverse: 124 | return_text += x_number2emoji[x_labels[y]] 125 | return_text += "\n" 126 | 127 | if reverse: 128 | return_text += "{}{}blank{}".format(emoji_separetor, 129 | emoji_prefix, 130 | emoji_separetor) 131 | for x_label in x_labels: 132 | # because reverse mode, use y_number 133 | return_text += y_number2emoji[x_label] 134 | return_text += "\n" 135 | 136 | return return_text 137 | 138 | 139 | class ShogiOutput: 140 | @staticmethod 141 | def make_board_emoji(board_info): 142 | out_text = "" 143 | 144 | out_text += make_user_text(board_info["info"]["second"]["name"], 145 | board_info["second"], 146 | is_first=False, 147 | is_turn=not board_info["turn"]) 148 | 149 | # board 150 | out_text += make_board_info_text(board_info, 151 | reverse=False) 152 | 153 | # first koma 154 | out_text += make_user_text(board_info["info"]["first"]["name"], 155 | board_info["first"], 156 | is_first=True, 157 | is_turn=board_info["turn"]) 158 | 159 | return out_text 160 | 161 | @staticmethod 162 | def make_board_emoji_reverse(board_info): 163 | out_text = "" 164 | 165 | # first koma 166 | out_text += make_user_text(board_info["info"]["first"]["name"], 167 | board_info["first"], 168 | is_first=True, 169 | is_turn=board_info["turn"], 170 | reverse=True) 171 | 172 | # board 173 | out_text += make_board_info_text(board_info, 174 | reverse=True) 175 | 176 | # socond koma 177 | out_text += make_user_text(board_info["info"]["second"]["name"], 178 | board_info["second"], 179 | is_first=False, 180 | is_turn=not board_info["turn"], 181 | reverse=True) 182 | 183 | return out_text 184 | -------------------------------------------------------------------------------- /app/shogi.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | import functools 4 | 5 | from slackbot.bot import respond_to 6 | 7 | from app.modules.shogi_input import ShogiInput, UserDifferentException, KomaCannotMoveException 8 | from app.modules.shogi_output import ShogiOutput 9 | from app.slack_utils.user import User 10 | from app.helper import channel_info, should_exist_shogi 11 | 12 | 13 | @respond_to('start with ?') 14 | @channel_info 15 | def start_shogi(channel, message, opponent_name): 16 | slacker = message._client.webapi 17 | user = User(slacker) 18 | 19 | opponent_id = user.username_to_id(opponent_name) 20 | if opponent_id is None: 21 | # In case of mention. In mention, slack transform username to userid 22 | # like @username to <@UOIFJ83F> 23 | opponent_id = opponent_name 24 | 25 | if not user.user_in_channel(opponent_id, channel.channel_id): 26 | message.reply("Error, sorry. Opponent is not found in this channel") 27 | return 28 | 29 | shogi = ShogiInput.init(channel_id=channel.channel_id, users=[{ 30 | "id": channel.own_id, 31 | "name": user.id_to_username(channel.own_id), 32 | }, { 33 | "id": opponent_id, 34 | "name": user.id_to_username(opponent_id), 35 | }]) 36 | 37 | if shogi is None: 38 | message.reply("Shogi started already by a user. Sorry.\nIf you want to quit shogi which already exists, please say this command `resign`") 39 | else: 40 | message.reply("Shogi started: " + shogi.id) 41 | board = ShogiInput.get_shogi_board(channel.channel_id) 42 | board_str = ShogiOutput.make_board_emoji(board) 43 | message.send(board_str) 44 | 45 | koma_names = [ 46 | "歩兵?", 47 | "と金?", 48 | "成?香車?", 49 | "成?桂馬?", 50 | "成?銀将?", 51 | "金将?", 52 | "角行?", 53 | "馬", 54 | "飛車?", 55 | "龍", 56 | "王将?", 57 | "玉将?", 58 | ] 59 | 60 | koma_names_string_regex = "|".join(koma_names) 61 | 62 | @respond_to("^([一二三四五六七八九123456789123456789]{2})?(同)?(" + koma_names_string_regex + ")([上右下左引寄直打]{1,2})?つ?(成)?") 63 | @channel_info 64 | @should_exist_shogi 65 | def koma_move(channel, message, position, dou, koma, sub_position=None, promote=None): 66 | movement_str = "".join( 67 | [x for x in [position, dou, koma, sub_position, promote] if x is not None]) 68 | 69 | try: 70 | ShogiInput.move(movement_str, channel.channel_id, channel.own_id) 71 | except UserDifferentException: 72 | message.reply("You cannot move this because *it's not your turn*") 73 | except KomaCannotMoveException: 74 | message.reply("You cannot move this with your message *{}*".format(movement_str)) 75 | finally: 76 | board = ShogiInput.get_shogi_board(channel.channel_id) 77 | board_str = ShogiOutput.make_board_emoji(board) 78 | message.send(board_str) 79 | 80 | @respond_to("set (all) mode") 81 | @channel_info 82 | @should_exist_shogi 83 | def set_mode(channel, message, arg): 84 | if arg == "all": 85 | ShogiInput.setAllMode(channel.channel_id) 86 | message.reply("Done! All member can move now!") 87 | 88 | @respond_to("今?.*の?.*状態.*を?教.*え?て?") 89 | @respond_to("now") 90 | @respond_to("局面.*") 91 | @respond_to("board") 92 | @channel_info 93 | @should_exist_shogi 94 | def board_info(channel, message): 95 | board = ShogiInput.get_shogi_board(channel.channel_id) 96 | board_str = ShogiOutput.make_board_emoji(board) 97 | message.send(board_str) 98 | 99 | 100 | @respond_to(".*降参.*") 101 | @respond_to(".*resign.*") 102 | @respond_to(".*負けました.*") 103 | @respond_to(".*まけました.*") 104 | @respond_to(".*まいりました.*") 105 | @respond_to(".*参りました.*") 106 | @respond_to(".*ありません.*") 107 | @channel_info 108 | @should_exist_shogi 109 | def resign(channel, message): 110 | message.send("最終局面") 111 | board = ShogiInput.get_shogi_board(channel.channel_id) 112 | board_str = ShogiOutput.make_board_emoji(board) 113 | message.send(board_str) 114 | ShogiInput.clear(channel.channel_id) 115 | 116 | 117 | @respond_to("待った") 118 | @channel_info 119 | @should_exist_shogi 120 | def matta(channel, message): 121 | try: 122 | ShogiInput.matta(channel.channel_id, channel.own_id) 123 | message.send("mattaed") 124 | except UserDifferentException: 125 | message.reply("You cannot matta because *it's not your turn*") 126 | except KomaCannotMoveException: 127 | message.reply("You cannot matta because koma not moved") 128 | finally: 129 | board = ShogiInput.get_shogi_board(channel.channel_id) 130 | board_str = ShogiOutput.make_board_emoji(board) 131 | message.send(board_str) 132 | 133 | 134 | @respond_to(".*ひふみん[eye, アイ, あい]?") 135 | @respond_to(".*反転.*") 136 | @channel_info 137 | @should_exist_shogi 138 | def hifumin(channel, message): 139 | board = ShogiInput.get_shogi_board(channel.channel_id) 140 | board_str = ShogiOutput.make_board_emoji_reverse(board) 141 | message.send(board_str) 142 | -------------------------------------------------------------------------------- /app/slack_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/app/slack_utils/__init__.py -------------------------------------------------------------------------------- /app/slack_utils/user.py: -------------------------------------------------------------------------------- 1 | 2 | class User: # TODO: rename this class 3 | 4 | def __init__(self, slacker): 5 | self._slacker = slacker 6 | self.users = self._slacker.users.list().body["members"] 7 | 8 | def username_to_id(self, username): 9 | """ return string user_id or None""" 10 | users = self.users 11 | if username[0] == "@": 12 | username = username[1:] 13 | for user in users: 14 | if user["name"] == username: 15 | return user["id"] 16 | return None 17 | 18 | def id_to_username(self, id_): 19 | users = self.users 20 | for user in users: 21 | if user["id"] == id_: 22 | return user["name"] 23 | 24 | def user_in_channel(self, user_id, channel_id): 25 | if channel_id[0] == "G": 26 | users = self._slacker.groups.info(channel_id).body["group"]["members"] 27 | else: 28 | users = self._slacker.channels.info(channel_id).body["channel"]["members"] 29 | for user in users: 30 | if user == user_id: 31 | return True 32 | return False 33 | -------------------------------------------------------------------------------- /app/validator.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from abc import ABCMeta, abstractmethod 4 | 5 | class UserValidator(metaclass=ABCMeta): 6 | @abstractmethod 7 | def validate(self, shogi, user_id): 8 | raise NotImplementedError() 9 | 10 | 11 | class BasicUserValidator(UserValidator): 12 | def validate(self, shogi, user_id): 13 | if shogi.first: 14 | if not shogi.first_user.id == user_id: 15 | return False 16 | else: 17 | if not shogi.second_user.id == user_id: 18 | return False 19 | return True 20 | 21 | class AllPassUserValidator(UserValidator): 22 | def validate(self, shogi, user_id): 23 | return True 24 | 25 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | python: 3 | version: 3.5.0 4 | dependencies: 5 | pre: 6 | - pip install coverage==4.3.4 7 | - pip install codeclimate-test-reporter 8 | test: 9 | override: 10 | - coverage run --include=`pwd`/app/* setup.py test 11 | post: 12 | - coverage html -d $CIRCLE_ARTIFACTS --include=`pwd`/app/* 13 | - codeclimate-test-reporter 14 | 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | 2 | slack-shogi: 3 | build: ./ 4 | volumes: 5 | - ./:/app 6 | 7 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | 2 | # It's for me, sorry 3 | 4 | from fabric.api import * 5 | import slackbot_settings as settings 6 | from urllib import request, parse 7 | 8 | env.hosts = settings.DEPLOY_HOSTS 9 | 10 | 11 | def deploy(): 12 | slack("Deploy Started") 13 | try: 14 | with cd("/var/bot/slack-shogi"): 15 | run("git pull") 16 | run("supervisorctl reload") 17 | slack("Deploy Finished") 18 | except: 19 | slack("Deploy Failed") 20 | 21 | 22 | def slack(text): 23 | if settings.WEBHOOK_URL: 24 | payload = ("payload={\"text\": \"" + parse.quote(text) + 25 | "\", \"username\": \"Mr.deploy\"}").encode("utf-8") 26 | request.urlopen(url=settings.WEBHOOK_URL, data=payload) 27 | 28 | -------------------------------------------------------------------------------- /input_emojis/.python-version: -------------------------------------------------------------------------------- 1 | 2.7.10 2 | -------------------------------------------------------------------------------- /input_emojis/images/eight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/eight.png -------------------------------------------------------------------------------- /input_emojis/images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/example.png -------------------------------------------------------------------------------- /input_emojis/images/five.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/five.png -------------------------------------------------------------------------------- /input_emojis/images/four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/four.png -------------------------------------------------------------------------------- /input_emojis/images/fu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/fu.png -------------------------------------------------------------------------------- /input_emojis/images/fu_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/fu_.png -------------------------------------------------------------------------------- /input_emojis/images/gin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/gin.png -------------------------------------------------------------------------------- /input_emojis/images/gin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/gin_.png -------------------------------------------------------------------------------- /input_emojis/images/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/go.png -------------------------------------------------------------------------------- /input_emojis/images/gyoku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/gyoku.png -------------------------------------------------------------------------------- /input_emojis/images/gyoku_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/gyoku_.png -------------------------------------------------------------------------------- /input_emojis/images/hati.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/hati.png -------------------------------------------------------------------------------- /input_emojis/images/hi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/hi.png -------------------------------------------------------------------------------- /input_emojis/images/hi_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/hi_.png -------------------------------------------------------------------------------- /input_emojis/images/iti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/iti.png -------------------------------------------------------------------------------- /input_emojis/images/kaku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kaku.png -------------------------------------------------------------------------------- /input_emojis/images/kaku_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kaku_.png -------------------------------------------------------------------------------- /input_emojis/images/kei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kei.png -------------------------------------------------------------------------------- /input_emojis/images/kei_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kei_.png -------------------------------------------------------------------------------- /input_emojis/images/kin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kin.png -------------------------------------------------------------------------------- /input_emojis/images/kin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kin_.png -------------------------------------------------------------------------------- /input_emojis/images/kyou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kyou.png -------------------------------------------------------------------------------- /input_emojis/images/kyou_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kyou_.png -------------------------------------------------------------------------------- /input_emojis/images/kyu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/kyu.png -------------------------------------------------------------------------------- /input_emojis/images/last_fu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_fu.png -------------------------------------------------------------------------------- /input_emojis/images/last_fu_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_fu_.png -------------------------------------------------------------------------------- /input_emojis/images/last_gin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_gin.png -------------------------------------------------------------------------------- /input_emojis/images/last_gin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_gin_.png -------------------------------------------------------------------------------- /input_emojis/images/last_gyoku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_gyoku.png -------------------------------------------------------------------------------- /input_emojis/images/last_gyoku_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_gyoku_.png -------------------------------------------------------------------------------- /input_emojis/images/last_hi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_hi.png -------------------------------------------------------------------------------- /input_emojis/images/last_hi_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_hi_.png -------------------------------------------------------------------------------- /input_emojis/images/last_kaku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kaku.png -------------------------------------------------------------------------------- /input_emojis/images/last_kaku_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kaku_.png -------------------------------------------------------------------------------- /input_emojis/images/last_kei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kei.png -------------------------------------------------------------------------------- /input_emojis/images/last_kei_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kei_.png -------------------------------------------------------------------------------- /input_emojis/images/last_kin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kin.png -------------------------------------------------------------------------------- /input_emojis/images/last_kin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kin_.png -------------------------------------------------------------------------------- /input_emojis/images/last_kyou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kyou.png -------------------------------------------------------------------------------- /input_emojis/images/last_kyou_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_kyou_.png -------------------------------------------------------------------------------- /input_emojis/images/last_narigin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_narigin.png -------------------------------------------------------------------------------- /input_emojis/images/last_narigin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_narigin_.png -------------------------------------------------------------------------------- /input_emojis/images/last_narikei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_narikei.png -------------------------------------------------------------------------------- /input_emojis/images/last_narikei_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_narikei_.png -------------------------------------------------------------------------------- /input_emojis/images/last_narikyou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_narikyou.png -------------------------------------------------------------------------------- /input_emojis/images/last_narikyou_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_narikyou_.png -------------------------------------------------------------------------------- /input_emojis/images/last_ou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_ou.png -------------------------------------------------------------------------------- /input_emojis/images/last_ou_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_ou_.png -------------------------------------------------------------------------------- /input_emojis/images/last_ryu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_ryu.png -------------------------------------------------------------------------------- /input_emojis/images/last_ryu_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_ryu_.png -------------------------------------------------------------------------------- /input_emojis/images/last_tokin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_tokin.png -------------------------------------------------------------------------------- /input_emojis/images/last_tokin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_tokin_.png -------------------------------------------------------------------------------- /input_emojis/images/last_uma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_uma.png -------------------------------------------------------------------------------- /input_emojis/images/last_uma_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/last_uma_.png -------------------------------------------------------------------------------- /input_emojis/images/mu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/mu.png -------------------------------------------------------------------------------- /input_emojis/images/nana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/nana.png -------------------------------------------------------------------------------- /input_emojis/images/narigin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/narigin.png -------------------------------------------------------------------------------- /input_emojis/images/narigin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/narigin_.png -------------------------------------------------------------------------------- /input_emojis/images/narikei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/narikei.png -------------------------------------------------------------------------------- /input_emojis/images/narikei_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/narikei_.png -------------------------------------------------------------------------------- /input_emojis/images/narikyou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/narikyou.png -------------------------------------------------------------------------------- /input_emojis/images/narikyou_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/narikyou_.png -------------------------------------------------------------------------------- /input_emojis/images/ni.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/ni.png -------------------------------------------------------------------------------- /input_emojis/images/nine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/nine.png -------------------------------------------------------------------------------- /input_emojis/images/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/one.png -------------------------------------------------------------------------------- /input_emojis/images/origin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/origin.png -------------------------------------------------------------------------------- /input_emojis/images/ou.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/ou.png -------------------------------------------------------------------------------- /input_emojis/images/ou_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/ou_.png -------------------------------------------------------------------------------- /input_emojis/images/roku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/roku.png -------------------------------------------------------------------------------- /input_emojis/images/ryu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/ryu.png -------------------------------------------------------------------------------- /input_emojis/images/ryu_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/ryu_.png -------------------------------------------------------------------------------- /input_emojis/images/san.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/san.png -------------------------------------------------------------------------------- /input_emojis/images/seven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/seven.png -------------------------------------------------------------------------------- /input_emojis/images/six.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/six.png -------------------------------------------------------------------------------- /input_emojis/images/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/three.png -------------------------------------------------------------------------------- /input_emojis/images/tokin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/tokin.png -------------------------------------------------------------------------------- /input_emojis/images/tokin_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/tokin_.png -------------------------------------------------------------------------------- /input_emojis/images/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/two.png -------------------------------------------------------------------------------- /input_emojis/images/uma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/uma.png -------------------------------------------------------------------------------- /input_emojis/images/uma_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/uma_.png -------------------------------------------------------------------------------- /input_emojis/images/yon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/input_emojis/images/yon.png -------------------------------------------------------------------------------- /input_emojis/input_emojis.py: -------------------------------------------------------------------------------- 1 | 2 | import getpass 3 | import time 4 | import sys 5 | 6 | import mechanize 7 | 8 | emoji_prefix = "slackshogisss_" 9 | 10 | emojis = { 11 | "images/mu.png": "mu", 12 | "images/fu.png": "fu", 13 | "images/kyou.png": "kyou", 14 | "images/kei.png": "kei", 15 | "images/gin.png": "gin", 16 | "images/kin.png": "kin", 17 | "images/kaku.png": "kaku", 18 | "images/hi.png": "hi", 19 | "images/tokin.png": "tokin", 20 | "images/narikyou.png": "narikyou", 21 | "images/narikei.png": "narikei", 22 | "images/narigin.png": "narigin", 23 | "images/uma.png": "uma", 24 | "images/ryu.png": "ryu", 25 | "images/gyoku.png": "gyoku", 26 | "images/ou.png": "ou", 27 | "images/fu_.png": "fu_enemy", 28 | "images/kyou_.png": "kyou_enemy", 29 | "images/kei_.png": "kei_enemy", 30 | "images/gin_.png": "gin_enemy", 31 | "images/kin_.png": "kin_enemy", 32 | "images/kaku_.png": "kaku_enemy", 33 | "images/hi_.png": "hi_enemy", 34 | "images/tokin_.png": "tokin_enemy", 35 | "images/narikyou_.png": "narikyou_enemy", 36 | "images/narikei_.png": "narikei_enemy", 37 | "images/narigin_.png": "narigin_enemy", 38 | "images/uma_.png": "uma_enemy", 39 | "images/ryu_.png": "ryu_enemy", 40 | "images/gyoku_.png": "gyoku_enemy", 41 | "images/ou_.png": "ou_enemy", 42 | "images/last_fu.png": "last_fu", 43 | "images/last_kyou.png": "last_kyou", 44 | "images/last_kei.png": "last_kei", 45 | "images/last_gin.png": "last_gin", 46 | "images/last_kin.png": "last_kin", 47 | "images/last_kaku.png": "last_kaku", 48 | "images/last_hi.png": "last_hi", 49 | "images/last_tokin.png": "last_tokin", 50 | "images/last_narikyou.png": "last_narikyou", 51 | "images/last_narikei.png": "last_narikei", 52 | "images/last_narigin.png": "last_narigin", 53 | "images/last_uma.png": "last_uma", 54 | "images/last_ryu.png": "last_ryu", 55 | "images/last_gyoku.png": "last_gyoku", 56 | "images/last_ou.png": "last_ou", 57 | "images/last_fu_.png": "last_fu_enemy", 58 | "images/last_kyou_.png": "last_kyou_enemy", 59 | "images/last_kei_.png": "last_kei_enemy", 60 | "images/last_gin_.png": "last_gin_enemy", 61 | "images/last_kin_.png": "last_kin_enemy", 62 | "images/last_kaku_.png": "last_kaku_enemy", 63 | "images/last_hi_.png": "last_hi_enemy", 64 | "images/last_tokin_.png": "last_tokin_enemy", 65 | "images/last_narikyou_.png": "last_narikyou_enemy", 66 | "images/last_narikei_.png": "last_narikei_enemy", 67 | "images/last_narigin_.png": "last_narigin_enemy", 68 | "images/last_uma_.png": "last_uma_enemy", 69 | "images/last_ryu_.png": "last_ryu_enemy", 70 | "images/last_gyoku_.png": "last_gyoku_enemy", 71 | "images/last_ou_.png": "last_ou_enemy", 72 | "images/one.png": "one", 73 | "images/two.png": "two", 74 | "images/three.png": "three", 75 | "images/four.png": "four", 76 | "images/five.png": "five", 77 | "images/six.png": "six", 78 | "images/seven.png": "seven", 79 | "images/eight.png": "eight", 80 | "images/nine.png": "nine", 81 | "images/iti.png": "iti", 82 | "images/ni.png": "ni", 83 | "images/san.png": "san", 84 | "images/yon.png": "yon", 85 | "images/go.png": "go", 86 | "images/roku.png": "roku", 87 | "images/nana.png": "nana", 88 | "images/hati.png": "hati", 89 | "images/kyu.png": "kyu", 90 | "images/origin.png": "blank" 91 | } 92 | 93 | 94 | def input_emojis(id_, password, team_id, two_factor, force_update=False): 95 | br = mechanize.Browser() 96 | br.set_handle_robots(False) 97 | br.open("https://{}.slack.com/".format(team_id)) 98 | br.select_form(nr=0) 99 | br["email"] = id_ 100 | br["password"] = password 101 | br.submit() 102 | if two_factor: 103 | br.select_form(nr=0) 104 | br["2fa_code"] = two_factor 105 | br.submit() 106 | 107 | count = 0 108 | for file_name in emojis: 109 | emoji_name = emojis[file_name] 110 | response = br.open( 111 | "https://{}.slack.com/customize/emoji".format(team_id)) 112 | if response.read().find(emoji_name) >= 0 and not force_update: 113 | # Simple resume. Does it work? 114 | # FIXME: Use beautiful soup and search it using dom 115 | print("{}/{} skipped(already exists for the name '{}')".format(count, 116 | len(emojis), emoji_name)) 117 | continue 118 | br.select_form(nr=0) 119 | br["name"] = emoji_prefix + emoji_name 120 | br.form.add_file(open(file_name), "images/png", file_name, name="img") 121 | br.submit() 122 | count += 1 123 | print("{}/{} completed".format(count, len(emojis))) 124 | time.sleep(1) 125 | 126 | 127 | def is_force_update(): 128 | args = sys.argv 129 | if not len(args) == 2: 130 | return True 131 | if "-p" in args or "--patch" in args: 132 | return False 133 | return True 134 | 135 | 136 | if __name__ == "__main__": 137 | force_update = is_force_update() 138 | team_id = raw_input("your slack team id: ") 139 | id_ = raw_input("your id: ") 140 | password = getpass.getpass("your password: ") 141 | two_factor = raw_input("authentication code for two factor(If needed) :") 142 | input_emojis(id_, password, team_id, two_factor, force_update) 143 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | 2 | from slackbot.bot import Bot 3 | import app 4 | 5 | 6 | def main(): 7 | bot = Bot() 8 | bot.run() 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup, find_packages 3 | 4 | setup( 5 | name="Slack Shogi", 6 | version="0.1", 7 | packages=find_packages(), 8 | test_suite="test", 9 | install_requires=[ 10 | "slackbot", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /slackbot_settings.py.default: -------------------------------------------------------------------------------- 1 | API_TOKEN = "" 2 | DEPLOY_HOSTS = ["example.com"] 3 | WEBHOOK_URL = "" # For your Incoming webhook URL 4 | 5 | -------------------------------------------------------------------------------- /slackbot_settings.py.docker: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | API_TOKEN = os.environ.get("SLACK_SHOGI_API_TOKEN", "") 5 | DEPLOY_HOSTS = [os.environ.get("SLACK_SHOGI_DEPLOY_HOSTS", "")] 6 | WEBHOOK_URL = os.environ.get("SLACK_SHOGI_WEBHOOK_URL", "") 7 | 8 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/test/__init__.py -------------------------------------------------------------------------------- /test/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/test/modules/__init__.py -------------------------------------------------------------------------------- /test/modules/input_parse_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from app.modules.shogi import Koma 3 | from app.modules.shogi_input import Shogi 4 | from app.modules.parse_input import ParseInput 5 | 6 | def create_shogi(): 7 | return Shogi("channel_id", [ 8 | {"id": "user1", "name": "name1"}, 9 | {"id": "user2", "name": "name2"}, 10 | ]) 11 | 12 | class ShogiTest(unittest.TestCase): 13 | 14 | def setUp(self): 15 | pass 16 | 17 | def test_parse1(self): 18 | shogi = create_shogi() 19 | self.assertEqual(ParseInput.parse("76歩", shogi), 20 | (2, 6, 2, 5, False, Koma.fu)) 21 | self.assertEqual(ParseInput.parse("7六歩", shogi), 22 | (2, 6, 2, 5, False, Koma.fu)) 23 | self.assertEqual(ParseInput.parse("75歩", shogi), False) 24 | self.assertEqual(ParseInput.parse("34歩", shogi), False) 25 | 26 | shogi.move(2, 6, 2, 5, False) 27 | self.assertEqual(ParseInput.parse("34歩", shogi), 28 | (6, 2, 6, 3, False, Koma.opponent_fu)) 29 | self.assertEqual(ParseInput.parse("35歩", shogi), False) 30 | self.assertEqual(ParseInput.parse("75歩", shogi), False) 31 | 32 | def test_parse2(self): 33 | shogi = create_shogi() 34 | self.assertEqual(ParseInput.parse("58金右", shogi), 35 | (5, 8, 4, 7, False, Koma.kin)) 36 | self.assertEqual(ParseInput.parse("58金左", shogi), 37 | (3, 8, 4, 7, False, Koma.kin)) 38 | self.assertEqual(ParseInput.parse("58金直", shogi), False) 39 | self.assertEqual(ParseInput.parse("58金寄", shogi), False) 40 | self.assertEqual(ParseInput.parse("58金引", shogi), False) 41 | shogi.move(4, 8, 5, 7, False) 42 | shogi.move(5, 8, 4, 8, False) 43 | shogi._shogi.first = True 44 | self.assertIn([3, 8], shogi.find_koma(Koma.kin)) 45 | self.assertIn([4, 8], shogi.find_koma(Koma.kin)) 46 | self.assertEqual(ParseInput.parse("58金直", shogi), 47 | (4, 8, 4, 7, False, Koma.kin)) 48 | self.assertEqual(ParseInput.parse("58金左", shogi), 49 | (3, 8, 4, 7, False, Koma.kin)) 50 | self.assertEqual(ParseInput.parse("58金右", shogi), False) 51 | self.assertEqual(ParseInput.parse("58金寄", shogi), False) 52 | self.assertEqual(ParseInput.parse("58金引", shogi), False) 53 | 54 | def test_parse3(self): 55 | shogi = create_shogi() 56 | shogi.move(2, 6, 2, 5, False) # 76歩 57 | shogi.move(6, 2, 6, 3, False) # 34歩 58 | self.assertEqual(ParseInput.parse("22角成", shogi), 59 | (1, 7, 7, 1, True, Koma.kaku)) 60 | self.assertEqual(ParseInput.parse("22角不成", shogi), 61 | (1, 7, 7, 1, False, Koma.kaku)) 62 | self.assertEqual(ParseInput.parse("22角", shogi), 63 | (1, 7, 7, 1, False, Koma.kaku)) 64 | self.assertEqual(ParseInput.parse("11角成", shogi), False) 65 | 66 | shogi.move(1, 7, 7, 1, True) 67 | self.assertEqual(ParseInput.parse("22同銀", shogi), 68 | (6, 0, 7, 1, False, Koma.opponent_gin)) 69 | self.assertEqual(ParseInput.parse("22銀", shogi), 70 | (6, 0, 7, 1, False, Koma.opponent_gin)) 71 | self.assertEqual(ParseInput.parse("22銀成", shogi), False) 72 | 73 | def test_parse_drop(self): 74 | shogi = create_shogi() 75 | shogi.board[6][0] = Koma.empty 76 | shogi._shogi.first_tegoma = [Koma.fu] 77 | shogi._shogi.first = True 78 | self.assertEqual(ParseInput.parse("95歩打", shogi), 79 | (-1, -1, 0, 4, False, Koma.fu)) 80 | self.assertEqual(ParseInput.parse("95歩打成", shogi), False) 81 | 82 | def test_parse_same(self): 83 | shogi = create_shogi() 84 | shogi.move(2, 6, 2, 5, False) # 76歩 85 | shogi.move(2, 2, 2, 3, False) # 74歩 86 | shogi.move(2, 5, 2, 4, False) # 75歩 87 | self.assertEqual(ParseInput.parse("同歩", shogi), 88 | (2, 3, 2, 4, False, Koma.opponent_fu)) 89 | 90 | shogi = create_shogi() 91 | shogi.move(5, 8, 4, 1, False) # 49の金を一気に52へ 92 | self.assertEqual(ParseInput.parse("同金右", shogi), 93 | (3, 0, 4, 1, False, Koma.opponent_kin)) 94 | 95 | def test_parse_sugu(self): 96 | shogi = create_shogi() 97 | shogi.move(5, 8, 4, 7, False) # 58金 98 | shogi.move(0, 2, 0, 3, False) # 94歩 99 | shogi.move(3, 6, 3, 5, False) # 66歩 100 | shogi.move(0, 3, 0, 4, False) # 95歩 101 | shogi.move(4, 7, 3, 6, False) # 67金 102 | shogi.move(0, 4, 0, 5, False) # 96歩 103 | self.assertEqual(ParseInput.parse("68金直", shogi), 104 | (3, 8, 3, 7, False, Koma.kin)) 105 | 106 | @unittest.skip("see https://github.com/setokinto/slack-shogi/issues/19") 107 | def test_parse_ambiguous(self): 108 | shogi = create_shogi() 109 | self.assertEqual(ParseInput.parse("58金", shogi), False) 110 | 111 | -------------------------------------------------------------------------------- /test/modules/shogi_input_test.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | from app.modules.shogi_input import ShogiInput, UserDifferentException, KomaCannotMoveException 4 | from app.modules.shogi import Koma 5 | 6 | 7 | class ShogiTest(unittest.TestCase): 8 | 9 | def setUp(self): 10 | pass 11 | 12 | def test_shogi_input_is_initable(self): 13 | shogi = ShogiInput.init("channel_id", [{ 14 | "id": "user1", 15 | "name": "user1name", 16 | }, { 17 | "id": "user2", 18 | "name": "user2name", 19 | } 20 | ]) 21 | self.assertEqual(shogi.channel_id, "channel_id") 22 | 23 | shogi = ShogiInput.init("channel_id", [{ 24 | "id": "user1", 25 | "name": "user1name", 26 | }, { 27 | "id": "user2", 28 | "name": "user2name", 29 | } 30 | ]) 31 | self.assertIsNone(shogi) 32 | 33 | ShogiInput.clear("channel_id") 34 | shogi = ShogiInput.init("channel_id", [{ 35 | "id": "user1", 36 | "name": "user1name", 37 | }, { 38 | "id": "user2", 39 | "name": "user2name", 40 | } 41 | ]) 42 | self.assertEqual(shogi.channel_id, "channel_id") 43 | 44 | def test_clear_for_non_exists_channnel(self): 45 | self.assertIsNone(ShogiInput.clear("channel_id_non_exists")) 46 | 47 | def test_move_method_should_work(self): 48 | channel_id = "test_move_method_should_work" 49 | shogi = ShogiInput.init(channel_id, [{ 50 | "id": "user1", 51 | "name": "user1name", 52 | }, { 53 | "id": "user2", 54 | "name": "user2name", 55 | }]) 56 | 57 | ShogiInput.move("76歩", channel_id, shogi.first_user.id) 58 | self.assertEqual(shogi.board[5][2], Koma.fu) 59 | 60 | def test_move_method_should_raise_UserDifferentException(self): 61 | channel_id = "test_move_method_should_raise_UserDifferentException" 62 | shogi = ShogiInput.init(channel_id, [{ 63 | "id": "user1", 64 | "name": "user1name", 65 | }, { 66 | "id": "user2", 67 | "name": "user2name", 68 | }]) 69 | 70 | with self.assertRaises(UserDifferentException): 71 | ShogiInput.move("76歩", channel_id, shogi.second_user.id) 72 | with self.assertRaises(UserDifferentException): 73 | ShogiInput.move("76歩", channel_id, shogi.second_user.id) 74 | 75 | def test_move_method_should_raise_KomaCannotMoveException(self): 76 | channel_id = "test_move_method_should_raise_KomaCannotMoveException" 77 | shogi = ShogiInput.init(channel_id, [{ 78 | "id": "user1", 79 | "name": "user1name", 80 | }, { 81 | "id": "user2", 82 | "name": "user2name", 83 | }]) 84 | 85 | with self.assertRaises(KomaCannotMoveException): 86 | ShogiInput.move("75歩", channel_id, shogi.first_user.id) 87 | with self.assertRaises(KomaCannotMoveException): 88 | ShogiInput.move("34歩", channel_id, shogi.first_user.id) 89 | with self.assertRaises(KomaCannotMoveException): 90 | ShogiInput.move("15151歩", channel_id, shogi.first_user.id) 91 | with self.assertRaises(KomaCannotMoveException): 92 | ShogiInput.move("Wow, it's great.", channel_id, shogi.first_user.id) 93 | 94 | def test_set_any_user_validator(self): 95 | channel_id = "test_set_validotr" 96 | shogi = ShogiInput.init(channel_id, [{ 97 | "id": "user1", 98 | "name": "user1name", 99 | }, { 100 | "id": "user2", 101 | "name": "user2name", 102 | }]) 103 | ShogiInput.move("76歩", channel_id, shogi.first_user.id) 104 | with self.assertRaises(UserDifferentException): 105 | ShogiInput.move("34歩", channel_id, shogi.first_user.id) 106 | ShogiInput.setAllMode(channel_id) 107 | ShogiInput.move("34歩", channel_id, shogi.first_user.id) 108 | 109 | def test_matta(self): 110 | channel_id = "test_matta" 111 | shogi = ShogiInput.init(channel_id, [{ 112 | "id": "user1", 113 | "name": "user1name", 114 | }, { 115 | "id": "user2", 116 | "name": "user2name", 117 | }]) 118 | ShogiInput.move("76歩", channel_id, shogi.first_user.id) 119 | self.assertEqual(shogi.board[5][2], Koma.fu) 120 | ShogiInput.matta(channel_id, shogi.second_user.id) 121 | self.assertEqual(shogi.board[5][2], Koma.empty) 122 | ShogiInput.move("76歩", channel_id, shogi.first_user.id) 123 | self.assertEqual(shogi.board[5][2], Koma.fu) 124 | 125 | def test_matta_for_UserDifferentException(self): 126 | channel_id = "test_matta_for_UserDifferentException" 127 | shogi = ShogiInput.init(channel_id, [{ 128 | "id": "user1", 129 | "name": "user1name", 130 | }, { 131 | "id": "user2", 132 | "name": "user2name", 133 | }]) 134 | ShogiInput.move("76歩", channel_id, shogi.first_user.id) 135 | self.assertEqual(shogi.board[5][2], Koma.fu) 136 | with self.assertRaises(UserDifferentException): 137 | ShogiInput.matta(channel_id, shogi.first_user.id) 138 | ShogiInput.move("34歩", channel_id, shogi.second_user.id) 139 | with self.assertRaises(UserDifferentException): 140 | ShogiInput.matta(channel_id, shogi.second_user.id) 141 | 142 | def test_matta_for_KomaCannotMoveException(self): 143 | channel_id = "test_matta_for_KomaCannotMoveException" 144 | shogi = ShogiInput.init(channel_id, [{ 145 | "id": "user1", 146 | "name": "user1name", 147 | }, { 148 | "id": "user2", 149 | "name": "user2name", 150 | }]) 151 | with self.assertRaises(KomaCannotMoveException): 152 | ShogiInput.matta(channel_id, shogi.first_user.id) 153 | 154 | def test_matta_for_drop_komas(self): 155 | channel_id = "test_matta_for_da_komas" 156 | shogi = ShogiInput.init(channel_id, [{ 157 | "id": "user1", 158 | "name": "user1name", 159 | }, { 160 | "id": "user2", 161 | "name": "user2name", 162 | }]) 163 | ShogiInput.move("76歩", channel_id, shogi.first_user.id) 164 | ShogiInput.move("34歩", channel_id, shogi.second_user.id) 165 | ShogiInput.move("22角", channel_id, shogi.first_user.id) 166 | ShogiInput.move("同銀", channel_id, shogi.second_user.id) 167 | ShogiInput.move("55角打", channel_id, shogi.first_user.id) 168 | ShogiInput.move("33角打", channel_id, shogi.second_user.id) 169 | ShogiInput.matta(channel_id, shogi.first_user.id) 170 | ShogiInput.matta(channel_id, shogi.second_user.id) 171 | ShogiInput.move("55角打", channel_id, shogi.first_user.id) 172 | ShogiInput.move("33角打", channel_id, shogi.second_user.id) 173 | 174 | self.assertEqual(shogi.board[4][4], Koma.kaku) 175 | self.assertEqual(shogi.board[2][6], Koma.opponent_kaku) 176 | 177 | def test_try_to_get_shogi_board(self): 178 | channel_id = "test_try_to_get_shogi_board" 179 | shogi = ShogiInput.init(channel_id, [{ 180 | "id": "user1", 181 | "name": "user1name", 182 | }, { 183 | "id": "user2", 184 | "name": "user2name", 185 | }]) 186 | ShogiInput.get_shogi_board(channel_id) 187 | 188 | -------------------------------------------------------------------------------- /test/modules/shogi_output_test.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | from app.modules.shogi_input import ShogiInput 4 | from app.modules.shogi_output import ShogiOutput 5 | 6 | 7 | class ShogiTest(unittest.TestCase): 8 | def setUp(self): 9 | pass 10 | 11 | def test_shogi_output_format(self): 12 | # for syntax error and runtime error 13 | channel_id = "channel_id" 14 | shogi = ShogiInput.init(channel_id, [{"id": "user1", "name": "user1name"}, {"id": "user2", "name": "user2name"}]) 15 | board = ShogiInput.get_shogi_board(channel_id) 16 | board_str = ShogiOutput.make_board_emoji(board) 17 | self.assertIsNotNone(board_str) 18 | 19 | def test_shogi_output_reverse_format(self): 20 | # for syntax error and runtime error 21 | channel_id = "channel_id" 22 | shogi = ShogiInput.init(channel_id, [{"id": "user1", "name": "user1name"}, {"id": "user2", "name": "user2name"}]) 23 | board = ShogiInput.get_shogi_board(channel_id) 24 | board_str = ShogiOutput.make_board_emoji_reverse(board) 25 | self.assertIsNotNone(board_str) 26 | -------------------------------------------------------------------------------- /test/modules/shogi_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from app.modules.shogi import Shogi, Koma 3 | 4 | 5 | class ShogiTest(unittest.TestCase): 6 | 7 | def setUp(self): 8 | pass 9 | 10 | def test_move_76fu(self): 11 | shogi = Shogi() 12 | shogi.move(2, 6, 2, 5, False) 13 | self.assertEqual(shogi.board[5][2], Koma.fu) 14 | self.assertEqual(shogi.board[6][2], Koma.empty) 15 | 16 | def test_movable_for_empty(self): 17 | shogi = Shogi() 18 | movable = shogi.movable(5, 5, 5, 6, False) 19 | self.assertFalse(movable) 20 | 21 | def test_movable_for_enemy(self): 22 | shogi = Shogi() 23 | movable = shogi.movable(0, 0, 0, 1, False) 24 | self.assertFalse(movable) 25 | movable = shogi.movable(0, 1, 0, 2, False) 26 | self.assertFalse(movable) 27 | shogi.move(2, 6, 2, 5, False) 28 | movable = shogi.movable(0, 0, 0, 1, False) 29 | self.assertTrue(movable) 30 | 31 | def test_movable_for_kyo(self): 32 | shogi = Shogi() 33 | shogi.move(8, 6, 0, 0, False) 34 | shogi.first = True 35 | movable = shogi.movable(8, 8, 8, 4, False) 36 | self.assertTrue(movable) 37 | movable = shogi.movable(8, 8, 8, 3, False) 38 | self.assertTrue(movable) 39 | movable = shogi.movable(8, 8, 8, 2, False) 40 | self.assertTrue(movable) 41 | movable = shogi.movable(8, 8, 8, 1, False) 42 | self.assertFalse(movable) 43 | 44 | def test_movable_for_fu(self): 45 | shogi = Shogi() 46 | movable = shogi.movable(0, 6, 0, 4, False) 47 | self.assertFalse(movable) 48 | movable = shogi.movable(0, 6, 0, 7, False) 49 | self.assertFalse(movable) 50 | movable = shogi.movable(0, 6, 1, 5, False) 51 | self.assertFalse(movable) 52 | movable = shogi.movable(0, 2, 0, 3, False) 53 | self.assertFalse(movable) 54 | 55 | # 96fu 56 | movable = shogi.movable(0, 6, 0, 5, False) 57 | self.assertTrue(movable) 58 | shogi.move(0, 6, 0, 5, False) 59 | 60 | # 94fu 61 | movable = shogi.movable(0, 2, 0, 3, False) 62 | self.assertTrue(movable) 63 | shogi.move(0, 2, 0, 3, False) 64 | 65 | # 95fu 66 | movable = shogi.movable(0, 5, 0, 4, False) 67 | self.assertTrue(movable) 68 | shogi.move(0, 5, 0, 4, False) 69 | 70 | # 95 do fu 71 | movable = shogi.movable(0, 3, 0, 4, False) 72 | self.assertTrue(movable) 73 | shogi.move(0, 3, 0, 4, False) 74 | 75 | # moving opponent koma 76 | movable = shogi.movable(0, 4, 0, 5, False) 77 | self.assertFalse(movable) 78 | 79 | def test_movable_for_kaku(self): 80 | shogi = Shogi() 81 | movable = shogi.movable(1, 7, 3, 5, False) 82 | self.assertFalse(movable) 83 | 84 | # 66 fu 85 | movable = shogi.movable(2, 6, 2, 5, False) 86 | self.assertTrue(movable) 87 | shogi.move(2, 6, 2, 5, False) 88 | 89 | # 34 fu 90 | movable = shogi.movable(6, 2, 6, 3, False) 91 | self.assertTrue(movable) 92 | shogi.move(6, 2, 6, 3, False) 93 | 94 | movable = shogi.movable(1, 7, 8, 0, False) 95 | self.assertFalse(movable) 96 | 97 | # 22 kaku 98 | movable = shogi.movable(1, 7, 7, 1, False) 99 | self.assertTrue(movable) 100 | shogi.move(6, 2, 6, 3, False) 101 | 102 | def test_move_for_promote(self): 103 | shogi = Shogi() 104 | shogi.board = [ 105 | [Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.empty, 106 | Koma.promoted_fu, Koma.hisha, Koma.empty, Koma.kin], 107 | [Koma.fu, Koma.kyosha, Koma.empty, Koma.empty, Koma.empty, 108 | Koma.empty, Koma.empty, Koma.empty, Koma.gyoku], 109 | [Koma.opponent_fu, Koma.empty, Koma.empty, Koma.keima, 110 | Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.empty], 111 | [Koma.empty, Koma.empty, Koma.gin, Koma.empty, Koma.empty, 112 | Koma.empty, Koma.empty, Koma.empty, Koma.empty], 113 | [Koma.empty, Koma.kyosha, Koma.opponent_kyosha, Koma.empty, 114 | Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.empty], 115 | [Koma.empty, Koma.empty, Koma.opponent_gin, Koma.empty, 116 | Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.empty], 117 | [Koma.fu, Koma.empty, Koma.empty, Koma.opponent_keima, 118 | Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.empty], 119 | [Koma.opponent_fu, Koma.opponent_kyosha, Koma.empty, Koma.empty, 120 | Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.opponent_gyoku], 121 | [Koma.empty, Koma.empty, Koma.empty, Koma.empty, Koma.empty, 122 | Koma.opponent_promoted_fu, Koma.opponent_hisha, Koma.empty, Koma.opponent_kin], 123 | ] 124 | 125 | shogi.first = True 126 | # 91 fu narazu 127 | movable = shogi.movable(0, 1, 0, 0, False) 128 | self.assertFalse(movable) 129 | 130 | # 91 fu naru 131 | movable = shogi.movable(0, 1, 0, 0, True) 132 | self.assertTrue(movable) 133 | 134 | shogi.first = False 135 | # 99 fu narazu 136 | movable = shogi.movable(0, 7, 0, 8, False) 137 | self.assertFalse(movable) 138 | 139 | # 99 fu naru 140 | movable = shogi.movable(0, 7, 0, 8, True) 141 | self.assertTrue(movable) 142 | 143 | shogi.first = True 144 | # 71 kei narazu 145 | movable = shogi.movable(3, 2, 2, 0, False) 146 | self.assertFalse(movable) 147 | # 71 kei naru 148 | movable = shogi.movable(3, 2, 2, 0, True) 149 | self.assertTrue(movable) 150 | 151 | shogi.first = False 152 | # 79 kei narazu 153 | movable = shogi.movable(3, 6, 2, 8, False) 154 | self.assertFalse(movable) 155 | # 79 kei naru 156 | movable = shogi.movable(3, 6, 2, 8, True) 157 | self.assertTrue(movable) 158 | 159 | shogi.first = True 160 | # 81 kyo narazu 161 | movable = shogi.movable(1, 1, 1, 0, False) 162 | self.assertFalse(movable) 163 | 164 | # 81 kyo naru 165 | movable = shogi.movable(1, 1, 1, 0, True) 166 | self.assertTrue(movable) 167 | 168 | shogi.first = False 169 | # 89 kyo narazu 170 | movable = shogi.movable(1, 7, 1, 8, False) 171 | self.assertFalse(movable) 172 | 173 | # 89 kyo naru 174 | movable = shogi.movable(1, 7, 1, 8, True) 175 | self.assertTrue(movable) 176 | 177 | shogi.first = True 178 | # 96 fu naru 179 | movable = shogi.movable(0, 6, 0, 5, True) 180 | self.assertFalse(movable) 181 | 182 | # 96 fu narazu 183 | movable = shogi.movable(0, 6, 0, 5, False) 184 | self.assertTrue(movable) 185 | 186 | shogi.first = False 187 | # 94 fu naru 188 | movable = shogi.movable(0, 2, 0, 3, True) 189 | self.assertFalse(movable) 190 | 191 | # 94 fu narazu 192 | movable = shogi.movable(0, 2, 0, 3, False) 193 | self.assertTrue(movable) 194 | 195 | shogi.first = True 196 | # 73 gin naru and narazu 197 | movable = shogi.movable(2, 3, 2, 2, True) 198 | self.assertTrue(movable) 199 | 200 | movable = shogi.movable(2, 3, 2, 2, False) 201 | self.assertTrue(movable) 202 | 203 | shogi.first = False 204 | # 77 gin naru and narazu 205 | movable = shogi.movable(2, 5, 2, 6, True) 206 | self.assertTrue(movable) 207 | 208 | movable = shogi.movable(2, 5, 2, 6, False) 209 | self.assertTrue(movable) 210 | 211 | # kin and gyoku can't promote 212 | shogi.first = False 213 | movable = shogi.movable(8, 0, 8, 1, True) 214 | self.assertFalse(movable) 215 | movable = shogi.movable(7, 0, 7, 1, True) 216 | self.assertFalse(movable) 217 | shogi.first = True 218 | movable = shogi.movable(1, 0, 1, 1, True) 219 | self.assertFalse(movable) 220 | movable = shogi.movable(0, 0, 0, 1, True) 221 | self.assertFalse(movable) 222 | 223 | # back promoteTrue 224 | shogi.first = True 225 | movable = shogi.movable(6, 0, 6, 5, True) 226 | self.assertTrue(movable) 227 | movable = shogi.movable(6, 0, 6, 7, True) 228 | self.assertTrue(movable) 229 | 230 | shogi.first = False 231 | movable = shogi.movable(6, 8, 6, 5, True) 232 | self.assertTrue(movable) 233 | movable = shogi.movable(6, 8, 6, 1, True) 234 | self.assertTrue(movable) 235 | 236 | # promoted' koma try to promote 237 | shogi.first = True 238 | movable = shogi.movable(5, 0, 5, 1, True) 239 | self.assertFalse(movable) 240 | shogi.first = False 241 | movable = shogi.movable(5, 8, 5, 7, True) 242 | self.assertFalse(movable) 243 | 244 | def test_drop(self): 245 | shogi = Shogi() 246 | shogi.first_tegoma = [Koma.kin] 247 | shogi.second_tegoma = [Koma.opponent_kin] 248 | shogi.drop(Koma.kin, 4, 4) 249 | self.assertEqual(shogi.board[4][4], Koma.kin) 250 | shogi.drop(Koma.opponent_kin, 3, 3) 251 | self.assertEqual(shogi.board[3][3], Koma.opponent_kin) 252 | 253 | def test_droppable(self): 254 | shogi = Shogi() 255 | shogi.first_tegoma = [Koma.kin] 256 | shogi.second_tegoma = [Koma.opponent_kin] 257 | 258 | shogi.first = True 259 | # have 260 | droppable = shogi.droppable(Koma.kin, 4, 4) 261 | self.assertTrue(droppable) 262 | # opponent 263 | droppable = shogi.droppable(Koma.opponent_kin, 4, 4) 264 | self.assertFalse(droppable) 265 | # not have 266 | droppable = shogi.droppable(Koma.gin, 4, 4) 267 | self.assertFalse(droppable) 268 | 269 | shogi.first = False 270 | droppable = shogi.droppable(Koma.opponent_kin, 4, 4) 271 | self.assertTrue(droppable) 272 | # opponent 273 | droppable = shogi.droppable(Koma.kin, 4, 4) 274 | self.assertFalse(droppable) 275 | # not have 276 | droppable = shogi.droppable(Koma.opponent_gin, 4, 4) 277 | self.assertFalse(droppable) 278 | 279 | def test_droppable_nifu(self): 280 | shogi = Shogi() 281 | shogi.first_tegoma = [Koma.fu] 282 | shogi.second_tegoma = [Koma.opponent_fu] 283 | 284 | shogi.first = True 285 | droppable = shogi.droppable(Koma.fu, 4, 4) 286 | self.assertFalse(droppable) 287 | shogi.first = False 288 | droppable = shogi.droppable(Koma.opponent_fu, 4, 4) 289 | self.assertFalse(droppable) 290 | 291 | def test_droppable_fu_kyo_kei(self): 292 | shogi = Shogi() 293 | shogi.board[0][0] = Koma.empty 294 | shogi.board[2][0] = Koma.empty 295 | shogi.board[6][0] = Koma.empty 296 | shogi.board[8][0] = Koma.empty 297 | shogi.first_tegoma = [Koma.fu, Koma.kyosha, Koma.keima] 298 | shogi.second_tegoma = [Koma.opponent_fu, 299 | Koma.opponent_kyosha, Koma.opponent_keima] 300 | shogi.first = True 301 | # keima 302 | droppable = shogi.droppable(Koma.keima, 0, 1) 303 | self.assertFalse(droppable) 304 | droppable = shogi.droppable(Koma.keima, 0, 2) 305 | self.assertTrue(droppable) 306 | droppable = shogi.droppable(Koma.opponent_keima, 1, 2) 307 | self.assertFalse(droppable) 308 | droppable = shogi.droppable(Koma.keima, 0, 3) 309 | self.assertTrue(droppable) 310 | # fu 311 | droppable = shogi.droppable(Koma.fu, 0, 0) 312 | self.assertFalse(droppable) 313 | droppable = shogi.droppable(Koma.fu, 0, 1) 314 | self.assertTrue(droppable) 315 | # kyo 316 | droppable = shogi.droppable(Koma.kyosha, 0, 0) 317 | self.assertFalse(droppable) 318 | droppable = shogi.droppable(Koma.kyosha, 0, 1) 319 | self.assertTrue(droppable) 320 | 321 | shogi.first = False 322 | # keima 323 | droppable = shogi.droppable(Koma.opponent_keima, 0, 7) 324 | self.assertFalse(droppable) 325 | droppable = shogi.droppable(Koma.opponent_keima, 0, 6) 326 | self.assertTrue(droppable) 327 | droppable = shogi.droppable(Koma.keima, 1, 6) 328 | self.assertFalse(droppable) 329 | droppable = shogi.droppable(Koma.opponent_keima, 0, 5) 330 | self.assertTrue(droppable) 331 | # fu 332 | droppable = shogi.droppable(Koma.opponent_fu, 0, 7) 333 | self.assertTrue(droppable) 334 | droppable = shogi.droppable(Koma.opponent_fu, 0, 8) 335 | self.assertFalse(droppable) 336 | droppable = shogi.droppable(Koma.opponent_fu, 0, 0) 337 | self.assertTrue(droppable) 338 | droppable = shogi.droppable(Koma.opponent_fu, 0, 1) 339 | self.assertTrue(droppable) 340 | # kyosha 341 | droppable = shogi.droppable(Koma.opponent_kyosha, 0, 7) 342 | self.assertTrue(droppable) 343 | droppable = shogi.droppable(Koma.opponent_kyosha, 0, 8) 344 | self.assertFalse(droppable) 345 | 346 | def test_find_koma(self): 347 | shogi = Shogi() 348 | koma_positions = shogi.find_koma(Koma.kin) 349 | self.assertIn([3, 8], koma_positions) 350 | self.assertIn([5, 8], koma_positions) 351 | 352 | koma_positions = shogi.find_koma(Koma.opponent_kin) 353 | self.assertIn([3, 0], koma_positions) 354 | self.assertIn([5, 0], koma_positions) 355 | 356 | koma_positions = shogi.find_koma(Koma.hisha) 357 | self.assertIn([7, 7], koma_positions) 358 | 359 | def test_last_move(self): 360 | shogi = Shogi() 361 | shogi.second_tegoma = [Koma.opponent_fu] 362 | shogi.move(0, 6, 0, 5, False) 363 | self.assertEqual(shogi.last_move_x, 0) 364 | self.assertEqual(shogi.last_move_y, 5) 365 | shogi.drop(Koma.opponent_fu, 5, 5) 366 | self.assertEqual(shogi.last_move_x, 5) 367 | self.assertEqual(shogi.last_move_y, 5) 368 | -------------------------------------------------------------------------------- /test/slack_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/setokinto/slack-shogi/a026fdb5e0acb5573230e26ec08e6cf9a8759f5e/test/slack_utils/__init__.py -------------------------------------------------------------------------------- /test/slack_utils/user_test.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest 3 | 4 | from app.slack_utils.user import User 5 | 6 | 7 | class MockedSlacker: 8 | 9 | @property 10 | def users(self): 11 | return MockedUser() 12 | 13 | @property 14 | def groups(self): 15 | return MockedGroup() 16 | 17 | @property 18 | def channels(self): 19 | return MockedChannel() 20 | 21 | 22 | class MockedUser: 23 | 24 | def list(self): 25 | return MockedBody({ 26 | "ok": True, 27 | "members": [ 28 | { 29 | "id": "U023BECGF", 30 | "name": "bobby", 31 | "deleted": False, 32 | "color": "9f69e7", 33 | "profile": { 34 | "first_name": "Bobby", 35 | "last_name": "Tables", 36 | "real_name": "Bobby Tables", 37 | "email": "bobby@slack.com", 38 | "skype": "my-skype-name", 39 | "phone": "+1 (123) 456 7890", 40 | "image_24": "https:\/\/...", 41 | "image_32": "https:\/\/...", 42 | "image_48": "https:\/\/...", 43 | "image_72": "https:\/\/...", 44 | "image_192": "https:\/\/..." 45 | }, 46 | "is_admin": True, 47 | "is_owner": True, 48 | "has_2fa": True, 49 | "has_files": False 50 | }, 51 | { 52 | "id": "U023BECGA", 53 | "name": "bobby2", 54 | "deleted": False, 55 | "color": "9f69e2", 56 | "profile": { 57 | "first_name": "Bobby2", 58 | "last_name": "Tables2", 59 | "real_name": "Bobby Table2s", 60 | "email": "bobby2@slack.com", 61 | "skype": "my-skype-name2", 62 | "phone": "+1 (123) 456 7891", 63 | "image_24": "https:\/\/...", 64 | "image_32": "https:\/\/...", 65 | "image_48": "https:\/\/...", 66 | "image_72": "https:\/\/...", 67 | "image_192": "https:\/\/..." 68 | }, 69 | "is_admin": True, 70 | "is_owner": True, 71 | "has_2fa": False, 72 | "has_files": True 73 | }, 74 | ] 75 | } 76 | ) 77 | 78 | class MockedGroup: 79 | 80 | def info(self, group_id): 81 | if not group_id[0] == "G": 82 | raise Exception() 83 | return MockedBody( 84 | { 85 | "ok": True, 86 | "group": { 87 | "id": "G023BECGF", 88 | "name": "fun", 89 | "created": 1360782804, 90 | "creator": "U024BE7LH", 91 | "is_archived": False, 92 | "is_general": False, 93 | "is_member": True, 94 | "is_starred": True, 95 | "members": [ 96 | "U023BECGA", 97 | ], 98 | "topic": {}, 99 | "purpose": {}, 100 | "last_read": "1401383885.000061", 101 | "latest": {}, 102 | "unread_count": 0, 103 | "unread_count_display": 0 104 | } 105 | } 106 | ) 107 | 108 | class MockedChannel: 109 | 110 | def info(self, channel_id): 111 | if not channel_id[0] == "C": 112 | raise Exception() 113 | 114 | return MockedBody( 115 | { 116 | "ok": True, 117 | "channel": { 118 | "id": "C023BECGA", 119 | "name": "fun", 120 | "created": 1360782804, 121 | "creator": "U024BE7LH", 122 | "is_archived": False, 123 | "is_general": False, 124 | "is_member": True, 125 | "is_starred": True, 126 | "members": [ 127 | "U023BECGA", 128 | ], 129 | "topic": {}, 130 | "purpose": {}, 131 | "last_read": "1401383885.000061", 132 | "latest": {}, 133 | "unread_count": 0, 134 | "unread_count_display": 0 135 | } 136 | } 137 | ) 138 | 139 | 140 | class MockedBody: 141 | 142 | def __init__(self, body): 143 | self._body = body 144 | 145 | @property 146 | def body(self): 147 | return self._body 148 | 149 | 150 | class UserTest(unittest.TestCase): 151 | 152 | def setUp(self): 153 | self.user = User(MockedSlacker()) 154 | 155 | def test_find_userid_from_username(self): 156 | user_id = self.user.username_to_id("bobby") 157 | self.assertEqual(user_id, "U023BECGF") 158 | 159 | user_id2 = self.user.username_to_id("bobby2") 160 | self.assertEqual(user_id2, "U023BECGA") 161 | 162 | def test_find_username_from_userid(self): 163 | username = self.user.id_to_username("U023BECGF") 164 | self.assertEqual(username, "bobby") 165 | 166 | username = self.user.id_to_username("U023BECGA") 167 | self.assertEqual(username, "bobby2") 168 | 169 | def test_return_None_non_exists_user_name(self): 170 | user_id = self.user.username_to_id("not_bobby") 171 | self.assertEqual(user_id, None) 172 | 173 | def test_find_userid_with_atmark_prefix(self): 174 | user_id = self.user.username_to_id("@bobby") 175 | self.assertEqual(user_id, "U023BECGF") 176 | 177 | user_id2 = self.user.username_to_id("@bobby2") 178 | self.assertEqual(user_id2, "U023BECGA") 179 | 180 | def test_user_in_channel_return_True_when_user_exists(self): 181 | exists = self.user.user_in_channel("U023BECGA", "C023BECGA") 182 | self.assertTrue(exists) 183 | 184 | def test_user_in_channel_return_False_when_user_not_exists(self): 185 | notexists = self.user.user_in_channel("UNONONONONO", "C023BECGA") 186 | self.assertFalse(notexists) 187 | 188 | def test_user_in_privatechannel_return_True_when_user_exists(self): 189 | exists = self.user.user_in_channel("U023BECGA", "G023BECGF") 190 | self.assertTrue(exists) 191 | 192 | def test_user_in_private_channel_return_False_when_user_not_exists(self): 193 | notexists = self.user.user_in_channel("UNONONONONO", "G023BECGF") 194 | self.assertFalse(notexists) 195 | 196 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from app.for_test import ForTest 3 | 4 | 5 | class TestTest(unittest.TestCase): 6 | 7 | def setUp(self): 8 | pass 9 | 10 | def test_test(self): 11 | self.assertTrue(True) 12 | 13 | def test_return_1(self): 14 | self.assertEqual(ForTest().return1(), 1) 15 | --------------------------------------------------------------------------------