Repository: setokinto/slack-shogi

Issue:
inputモジュールを作る
botからinputするモジュールを作るよーーー

- @shogibot: start with kouki_dan みたいなコマンドを受け付けて将棋盤モジュール.startみたいなのをやる
- @shogibot: 23金右成 とかを受け付け、将棋盤モジュールにinputする
- ↑がもしエラーになった場合にいい感じに返す


Discussion:

Patch:
diff --git a/app/modules/shogi.py b/app/modules/shogi.py
index 3373116..78da9de 100644
--- a/app/modules/shogi.py
+++ b/app/modules/shogi.py
@@ -56,6 +56,16 @@ class Koma(Enum):
             return Koma(self.value | 0x10)
         except ValueError:
             return self
+    def unpromote(self):
+        try:
+            return Koma(self.value & 0x2F)
+        except ValueError:
+            return self
+    def go_enemy(self):
+        if self.is_first():
+            return Koma(self.value + 0x20)
+        else:
+            return Koma(self.value - 0x20)
 
 class Shogi:
     # TODO: implement komaochi
@@ -170,6 +180,7 @@ class Shogi:
         koma = self.board[from_y][from_x]
         koma_for_komadai = self.board[to_y][to_x]
         if koma_for_komadai is not Koma.empty:
+            koma_for_komadai = koma_for_komadai.unpromote().go_enemy()
             if self.first:
                 self.first_tegoma.append(koma_for_komadai)
             else:
@@ -226,6 +237,7 @@ class Shogi:
         koma_for_komadai = self.board[to_y][to_x]
         self.board[to_y][to_x] = koma
         if koma_for_komadai is not Koma.empty:
+            koma_for_komadai = koma_for_komadai.unpromote().go_enemy()
             if self.first:
                 self.first_tegoma.append(koma_for_komadai)
             else:
diff --git a/app/modules/shogi_input.py b/app/modules/shogi_input.py
index 262f32a..f110571 100644
--- a/app/modules/shogi_input.py
+++ b/app/modules/shogi_input.py
@@ -1,7 +1,11 @@
 
 import uuid
-from app.slack_utils.user import User
+import random
+
+from app.slack_utils.user import User as UserFinder
 from app.modules.shogi import Shogi as ShogiModule
+from app.modules.parse_input import ParseInput
+
 
 class ShogiManager:
     def __init__(self):
@@ -11,9 +15,9 @@ class ShogiManager:
             return False
         else:
             return True
-    def create(self, channel_id, user_ids):
+    def create(self, channel_id, users):
         if self.is_creatable(channel_id):
-            shogi = Shogi(channel_id, user_ids)
+            shogi = Shogi(channel_id, users)
             self.shogi[channel_id] = shogi
             return shogi
         else:
@@ -32,16 +36,16 @@ class ShogiManager:
 class ShogiInput:
     manager = ShogiManager()
     @staticmethod
-    def init(channel_id, user_ids):
-        if ShogiInput.creatable_new_shogi(channel_id, user_ids):
-            shogi = ShogiInput.manager.create(channel_id, user_ids)
+    def init(channel_id, users):
+        if ShogiInput.creatable_new_shogi(channel_id, users):
+            shogi = ShogiInput.manager.create(channel_id, users)
             return shogi
         else:
             return None
     @staticmethod
-    def creatable_new_shogi(channel_id, user_ids):
-        for user_id in user_ids:
-            if user_id is None:
+    def creatable_new_shogi(channel_id, users):
+        for user in users:
+            if user["id"] is None:
                 return False
         if ShogiInput.manager.is_creatable(channel_id):
             return True
@@ -58,41 +62,76 @@ class ShogiInput:
     def clear(channel_id):
         ShogiInput.manager.clear(channel_id)
     @staticmethod
-    def move(position, koma, sub_position, promote):
-        # TODO:
-        return False
+    def move(movement_str, channel_id, user_id):
+        shogi = ShogiInput.manager.get_shogi(channel_id)
+        if shogi.first:
+            if not shogi.first_user_id == user_id:
+                return False # TODO: DifferentUserException
+        else:
+            if not shogi.second_user_id == user_id:
+                return False # TODO: DifferentUserException
+        movement = ParseInput.parse(movement_str, shogi.shogi) # TODO: use Shogi object in this file and test
+        if movement == False:
+            return False
+        else:
+            from_x, from_y, to_x, to_y, promote, koma = movement
+
+        if from_x == -1 and from_y == -1 and shogi.droppable(koma, to_x, to_y):
+            shogi.drop(koma, to_x, to_y)
+            return True
+        elif shogi.movable(from_x, from_y, to_x, to_y, promote):
+            shogi.move(from_x, from_y, to_x, to_y, promote)
+            return True
+        else:
+            return False
     @staticmethod
     def basic_move(channel_id, from_x, from_y, to_x, to_y, promote):
         shogi = ShogiInput.manager.get_shogi(channel_id)
         shogi.move(from_x, from_y, to_x, to_y, promote)
     @staticmethod
     def get_shogi_board(channel_id):
-        # TODO:
         shogi = ShogiInput.manager.get_shogi(channel_id)
         if shogi is None:
             return None
         return {
-                   "first":[],
-                   "second": [],
+                   "first": shogi.shogi.first_tegoma,
+                   "second": shogi.shogi.second_tegoma,
                    "board": shogi.board,
                    "info": {
                        "first": {
-                           "name": "millay",
+                           "id": shogi.first_user_id,
+                           "name": shogi.first_user_name,
                        },
                        "second": {
-                           "name": "not millay",
+                           "id": shogi.second_user_id,
+                           "name": shogi.second_user_name,
                        }
-                   }
+                   },
+                   "_shogi": shogi,
                }
 
 class Shogi:
-    def __init__(self, channel_id, user_ids):
+    def __init__(self, channel_id, users):
         self.shogi = ShogiModule()
         self.channel_id = channel_id
-        self.user_ids = user_ids
+        self.user_ids = [ x["id"] for x in users]
+        random.shuffle(users)
+        self.first_user_id = users[0]["id"]
+        self.first_user_name = users[0]["name"]
+        self.second_user_id = users[1]["id"]
+        self.second_user_name = users[1]["name"]
         self.id = uuid.uuid4().hex
     def move(self, from_x, from_y, to_x, to_y, promote):
         self.shogi.move(from_x, from_y, to_x, to_y, promote)
+    def drop(self, koma, to_x, to_y):
+        self.shogi.drop(koma, to_x, to_y)
+    def movable(self, from_x, from_y, to_x, to_y, promote):
+        return self.shogi.movable(from_x, from_y, to_x, to_y, promote)
+    def droppable(self, koma, to_x, to_y):
+        return self.shogi.droppable(koma, to_x, to_y)
+    @property
+    def first(self):
+        return self.shogi.first
     @property
     def board(self):
         return self.shogi.board
diff --git a/app/shogi.py b/app/shogi.py
index 92ca51f..2b7d78c 100644
--- a/app/shogi.py
+++ b/app/shogi.py
@@ -27,15 +27,22 @@ def start_shogi(message, opponent_name):
         message.reply("Error, sorry. Opponent is not found in this channel")
         return
 
-    shogi = ShogiInput.init(channel_id=channel_id, user_ids=[
-        own_id,
-        opponent_id,
+    shogi = ShogiInput.init(channel_id=channel_id, users=[{
+            "id": own_id,
+            "name": user.id_to_username(own_id),
+            }, {
+            "id": opponent_id,
+            "name": user.id_to_username(opponent_id),
+        }
     ])
 
     if shogi is None:
         message.reply("Error, sorry")
     else:
         message.reply("Shogi started: " + shogi.id)
+        board = ShogiInput.get_shogi_board(channel_id)
+        board_str = ShogiOutput.make_board_emoji(board)
+        message.send(board_str)
 
 koma_names = [
     "歩",
@@ -66,19 +73,48 @@ koma_names += list(map(lambda n: "成"+n, koma_names))
 koma_names_string_regex = "|".join(koma_names)
 
 @respond_to("([一二三四五六七八九123456789１２３４５６７８９]{2})("+koma_names_string_regex+")([上右下左]{1,2})?(成)?")
-def koma_move(message, position, koma, sub_position, promote):
+def koma_move(message, position, koma, sub_position=None, promote=None):
+    movement_str = "".join([x for x in [position, koma, sub_position, promote] if x is not None])
     channel_id = message.body["channel"]
     if not ShogiInput.exists(channel_id):
         message.reply("start withから初めてね")
         return
     own_id = message.body["user"]
-    if ShogiInput.koma_is_movable(channel_id, own_id, position, koma, sub_position, promote):
-        ShogiInput.move(position, koma, sub_position, promote)
+
+    if ShogiInput.move(movement_str, channel_id, own_id):
         board = ShogiInput.get_shogi_board(channel_id)
         board_str = ShogiOutput.make_board_emoji(board)
         message.send(board_str)
     else:
         message.reply("You cannot move this!!")
+        board = ShogiInput.get_shogi_board(channel_id)
+        board_str = ShogiOutput.make_board_emoji(board)
+        message.send(board_str)
+
+@respond_to("今?.*の?.*状態.*を?教.*え?て?")
+@respond_to("現局面.*")
+@respond_to("局面.*")
+@respond_to("board")
+def board_info(message):
+    channel_id = message.body["channel"]
+    board = ShogiInput.get_shogi_board(channel_id)
+    board_str = ShogiOutput.make_board_emoji(board)
+    message.send(board_str)
+
+@respond_to(".*降参.*")
+@respond_to(".*resign.*")
+@respond_to(".*負けました.*")
+@respond_to(".*まけました.*")
+@respond_to(".*まいりました.*")
+@respond_to(".*参りました.*")
+@respond_to(".*ありません.*")
+def resign(message):
+    channel_id = message.body["channel"]
+    message.send("最終局面")
+    board = ShogiInput.get_shogi_board(channel_id)
+    board_str = ShogiOutput.make_board_emoji(board)
+    message.send(board_str)
+    ShogiInput.clear(channel_id)
 
 @respond_to("([123456789][123456789][123456789][123456789]成?)")
 def koma_move_basic(message, movement):
diff --git a/app/slack_utils/user.py b/app/slack_utils/user.py
index 1e8febd..0a69c19 100644
--- a/app/slack_utils/user.py
+++ b/app/slack_utils/user.py
@@ -1,17 +1,23 @@
 
-class User:
+class User: # TODO: rename this class
     def __init__(self, slacker):
         self._slacker = slacker
+        self.users = self._slacker.users.list().body["members"] 
 
     def username_to_id(self, username):
         """ return string user_id or None"""
-        users = self._slacker.users.list().body["members"] # TODO: cache
+        users = self.users
         if username[0] == "@":
             username = username[1:]
         for user in users:
             if user["name"] == username:
                 return user["id"]
         return None
+    def id_to_username(self, id_):
+        users = self.users
+        for user in users:
+            if user["id"] == id_:
+                return user["name"]
     def user_in_channel(self, user_id, channel_id):
         users = self._slacker.channels.info(channel_id).body["channel"]["members"]
         for user in users:


Test patch:
diff --git a/test/modules/shogi_input_test.py b/test/modules/shogi_input_test.py
index c4ea1d7..3647d9b 100644
--- a/test/modules/shogi_input_test.py
+++ b/test/modules/shogi_input_test.py
@@ -6,14 +6,35 @@ class ShogiTest(unittest.TestCase):
         pass
 
     def test_shogi_input_is_initable(self):
-        shogi = ShogiInput.init("channel_id", ["user1", "user2"])
+        shogi = ShogiInput.init("channel_id", [{
+                "id": "user1",
+                "name": "user1name",
+            }, {
+                "id": "user2",
+                "name": "user2name",
+            }
+        ])
         self.assertEqual(shogi.channel_id, "channel_id")
 
-        shogi = ShogiInput.init("channel_id", ["user1", "user2"])
+        shogi = ShogiInput.init("channel_id", [{
+                "id": "user1",
+                "name": "user1name",
+            }, {
+                "id": "user2",
+                "name": "user2name",
+            }
+        ])
         self.assertIsNone(shogi)
 
         ShogiInput.clear("channel_id")
-        shogi = ShogiInput.init("channel_id", ["user1", "user2"])
+        shogi = ShogiInput.init("channel_id",[{
+                "id": "user1",
+                "name": "user1name",
+            }, {
+                "id": "user2",
+                "name": "user2name",
+            }
+        ])
         self.assertEqual(shogi.channel_id, "channel_id")
 
 
diff --git a/test/slack_utils/user_test.py b/test/slack_utils/user_test.py
index 5053243..d5168de 100644
--- a/test/slack_utils/user_test.py
+++ b/test/slack_utils/user_test.py
@@ -110,6 +110,13 @@ class UserTest(unittest.TestCase):
         user_id2 = self.user.username_to_id("bobby2")
         self.assertEqual(user_id2, "U023BECGA")
 
+    def test_find_username_from_userid(self):
+        username = self.user.id_to_username("U023BECGF")
+        self.assertEqual(username, "bobby")
+
+        username = self.user.id_to_username("U023BECGA")
+        self.assertEqual(username, "bobby2")
+
     def test_return_None_non_exists_user_name(self):
         user_id = self.user.username_to_id("not_bobby")
         self.assertEqual(user_id, None)