diff --git a/app/modules/shogi.py b/app/modules/shogi.py
index 4c2b930..3373116 100644
--- a/app/modules/shogi.py
+++ b/app/modules/shogi.py
@@ -51,9 +51,11 @@ class Koma(Enum):
         if self.value & 0x10:
             return False
         return True
-
-class ShogiCantMoveException(Exception):
-    pass
+    def promote(self):
+        try:
+            return Koma(self.value | 0x10)
+        except ValueError:
+            return self
 
 class Shogi:
     # TODO: implement komaochi
@@ -61,6 +63,8 @@ class Shogi:
         self.first = True
         self.first_tegoma = []
         self.second_tegoma = []
+        self.last_move_x = None
+        self.last_move_y = None
         self.board = [
             [
                 Koma.opponent_kyosha,
@@ -163,20 +167,21 @@ class Shogi:
             ],
         ]
     def move(self, from_x, from_y, to_x, to_y, promote):
-        """
-            if from_x and from_y is -1, the koma is from komadai
-        """
         koma = self.board[from_y][from_x]
-        # TODO: check for movable
-        if False: # not movable:
-            raise ShogiCantMoveException()
         koma_for_komadai = self.board[to_y][to_x]
         if koma_for_komadai is not Koma.empty:
-            # TODO: move to komadai
-            pass
+            if self.first:
+                self.first_tegoma.append(koma_for_komadai)
+            else:
+                self.second_tegoma.append(koma_for_komadai)
         self.board[from_y][from_x] = Koma.empty
-        self.board[to_y][to_x] = koma # TODO: in case promote is true
+        if promote:
+            self.board[to_y][to_x] = koma.promote()
+        else:
+            self.board[to_y][to_x] = koma
         self.first = not self.first
+        self.last_move_x = to_x
+        self.last_move_y = to_y
 
     def movable(self, from_x, from_y, to_x, to_y, promote):
         board = self.board
@@ -213,6 +218,48 @@ class Shogi:
                         return True
         return False
 
+    def drop(self, koma, to_x, to_y):
+        if self.first:
+            self.first_tegoma.remove(koma)
+        else:
+            self.second_tegoma.remove(koma)
+        koma_for_komadai = self.board[to_y][to_x]
+        self.board[to_y][to_x] = koma
+        if koma_for_komadai is not Koma.empty:
+            if self.first:
+                self.first_tegoma.append(koma_for_komadai)
+            else:
+                self.second_tegoma.append(koma_for_komadai)
+        self.first = not self.first
+        self.last_move_x = to_x
+        self.last_move_y = to_y
+
+    def droppable(self, koma, to_x, to_y):
+        if self.first:
+            tegoma = self.first_tegoma
+        else:
+            tegoma = self.second_tegoma
+        if koma in tegoma and self.board[to_y][to_x] is Koma.empty:
+            if koma is Koma.fu or koma is Koma.opponent_fu:
+                for y_board in self.board:
+                    if y_board[to_x] is koma:
+                        # 2fu
+                        return False
+            if koma is Koma.fu or koma is Koma.kyosha:
+                if to_y == 0:
+                    return False
+            if koma is Koma.keima:
+                if to_y <= 1:
+                    return False
+            if koma is Koma.opponent_fu or koma is Koma.opponent_kyosha:
+                if to_y == 8:
+                    return False
+            if koma is Koma.opponent_keima:
+                if to_y >= 7:
+                    return False
+            return True
+        return False
+
     def checkObstacle(self, from_x, from_y, to_x, to_y):
         if self.board[from_y][from_x].is_keima():
             return True

将棋盤モジュールを作る
将棋盤モジュールを作る

- 入力と入力とそれに対するエラーのインターフェースを作る
- 盤面を表示する
- 将棋盤の盤面を入力に従って管理する
diff --git a/test/modules/shogi_test.py b/test/modules/shogi_test.py
index 9538c50..d768dde 100644
--- a/test/modules/shogi_test.py
+++ b/test/modules/shogi_test.py
@@ -145,10 +145,6 @@ class ShogiTest(unittest.TestCase):
         movable = shogi.movable(3, 6, 2, 8, True)
         self.assertTrue(movable)
 
-
-
-        
-
         shogi.first = True
         # 81 kyo narazu
         movable = shogi.movable(1, 1, 1, 0, False)
@@ -234,6 +230,107 @@ class ShogiTest(unittest.TestCase):
         movable = shogi.movable(5, 8, 5, 7, True)
         self.assertFalse(movable)
 
+    def test_drop(self):
+        shogi = Shogi()
+        shogi.first_tegoma = [Koma.kin]
+        shogi.second_tegoma = [Koma.opponent_kin]
+        shogi.drop(Koma.kin, 4, 4)
+        self.assertEqual(shogi.board[4][4], Koma.kin)
+        shogi.drop(Koma.opponent_kin, 3, 3)
+        self.assertEqual(shogi.board[3][3], Koma.opponent_kin)
+
+    def test_droppable(self):
+        shogi = Shogi()
+        shogi.first_tegoma = [Koma.kin]
+        shogi.second_tegoma = [Koma.opponent_kin]
+
+        shogi.first = True
+        # have
+        droppable = shogi.droppable(Koma.kin, 4, 4)
+        self.assertTrue(droppable)
+        # opponent
+        droppable = shogi.droppable(Koma.opponent_kin, 4, 4)
+        self.assertFalse(droppable)
+        # not have
+        droppable = shogi.droppable(Koma.gin, 4, 4)
+        self.assertFalse(droppable)
+
+        shogi.first = False
+        droppable = shogi.droppable(Koma.opponent_kin, 4, 4)
+        self.assertTrue(droppable)
+        # opponent
+        droppable = shogi.droppable(Koma.kin, 4, 4)
+        self.assertFalse(droppable)
+        # not have
+        droppable = shogi.droppable(Koma.opponent_gin, 4, 4)
+        self.assertFalse(droppable)
+
+    def test_droppable_nifu(self):
+        shogi = Shogi()
+        shogi.first_tegoma = [Koma.fu]
+        shogi.second_tegoma = [Koma.opponent_fu]
+
+        shogi.first = True
+        droppable = shogi.droppable(Koma.fu, 4, 4)
+        self.assertFalse(droppable)
+        shogi.first = False
+        droppable = shogi.droppable(Koma.opponent_fu, 4, 4)
+        self.assertFalse(droppable)
+
+    def test_droppable_fu_kyo_kei(self):
+        shogi = Shogi()
+        shogi.board[0][0] = Koma.empty
+        shogi.board[2][0] = Koma.empty
+        shogi.board[6][0] = Koma.empty
+        shogi.board[8][0] = Koma.empty
+        shogi.first_tegoma = [Koma.fu, Koma.kyosha, Koma.keima]
+        shogi.second_tegoma = [Koma.opponent_fu, Koma.opponent_kyosha, Koma.opponent_keima]
+        shogi.first = True
+        # keima
+        droppable = shogi.droppable(Koma.keima, 0, 1)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.keima, 0, 2)
+        self.assertTrue(droppable)
+        droppable = shogi.droppable(Koma.opponent_keima, 1, 2)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.keima, 0, 3)
+        self.assertTrue(droppable)
+        # fu
+        droppable = shogi.droppable(Koma.fu, 0, 0)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.fu, 0, 1)
+        self.assertTrue(droppable)
+        # kyo
+        droppable = shogi.droppable(Koma.kyosha, 0, 0)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.kyosha, 0, 1)
+        self.assertTrue(droppable)
+
+        shogi.first = False
+        # keima
+        droppable = shogi.droppable(Koma.opponent_keima, 0, 7)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.opponent_keima, 0, 6)
+        self.assertTrue(droppable)
+        droppable = shogi.droppable(Koma.keima, 1, 6)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.opponent_keima, 0, 5)
+        self.assertTrue(droppable)
+        # fu
+        droppable = shogi.droppable(Koma.opponent_fu, 0, 7)
+        self.assertTrue(droppable)
+        droppable = shogi.droppable(Koma.opponent_fu, 0, 8)
+        self.assertFalse(droppable)
+        droppable = shogi.droppable(Koma.opponent_fu, 0, 0)
+        self.assertTrue(droppable)
+        droppable = shogi.droppable(Koma.opponent_fu, 0, 1)
+        self.assertTrue(droppable)
+        # kyosha
+        droppable = shogi.droppable(Koma.opponent_kyosha, 0, 7)
+        self.assertTrue(droppable)
+        droppable = shogi.droppable(Koma.opponent_kyosha, 0, 8)
+        self.assertFalse(droppable)
+
 
     def test_find_koma(self):
         shogi = Shogi()
@@ -247,4 +344,14 @@ class ShogiTest(unittest.TestCase):
 
         koma_positions = shogi.find_koma(Koma.hisha)
         self.assertIn([7, 7], koma_positions)
+    
+    def test_last_move(self):
+        shogi = Shogi()
+        shogi.second_tegoma = [Koma.opponent_fu]
+        shogi.move(0, 6, 0, 5, False)
+        self.assertEqual(shogi.last_move_x, 0)
+        self.assertEqual(shogi.last_move_y, 5)
+        shogi.drop(Koma.opponent_fu, 5, 5)
+        self.assertEqual(shogi.last_move_x, 5)
+        self.assertEqual(shogi.last_move_y, 5)
 

pip install -e .
parse_log_pytest
slackbot
pytest pytest-cov
apt-get update apt-get install -y gcc
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
Brotli @ file:///croot/brotli-split_1736182456865/work
certifi @ file:///croot/certifi_1738623731865/work/certifi
charset-normalizer @ file:///croot/charset-normalizer_1721748349566/work
coverage==7.8.0
exceptiongroup==1.2.2
idna @ file:///croot/idna_1714398848350/work
iniconfig==2.1.0
packaging==24.2
pluggy==1.5.0
PySocks @ file:///tmp/build/80754af9/pysocks_1605305812635/work
pytest==8.3.5
pytest-cov==6.0.0
requests @ file:///croot/requests_1730999120400/work
six @ file:///tmp/build/80754af9/six_1644875935023/work
-e git+https://github.com/setokinto/slack-shogi.git@9f9abc3d50a69627e21274a482a8613f97958ad0#egg=Slack_Shogi
slackbot @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_slackbot_1737572202/work
slacker @ file:///home/conda/feedstock_root/build_artifacts/slacker_1735042418524/work
tomli==2.2.1
urllib3 @ file:///croot/urllib3_1737133630106/work
websocket-client @ file:///tmp/build/80754af9/websocket-client_1614803975924/work

name: slack-shogi
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - brotli-python=1.0.9=py39h6a678d5_9
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2025.1.31=py39h06a4308_0
  - charset-normalizer=3.3.2=pyhd3eb1b0_0
  - idna=3.7=py39h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - pysocks=1.7.1=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - requests=2.32.3=py39h06a4308_1
  - setuptools=75.8.0=py39h06a4308_0
  - six=1.16.0=pyhd3eb1b0_1
  - slackbot=1.0.5=pyh29332c3_0
  - slacker=0.14.0=pyhd8ed1ab_1
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - urllib3=2.3.0=py39h06a4308_0
  - websocket-client=0.58.0=py39h06a4308_4
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - packaging==24.2
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - tomli==2.2.1
prefix: /opt/conda/envs/slack-shogi

test/modules/shogi_test.py::ShogiTest::test_drop test/modules/shogi_test.py::ShogiTest::test_droppable test/modules/shogi_test.py::ShogiTest::test_droppable_fu_kyo_kei test/modules/shogi_test.py::ShogiTest::test_droppable_nifu test/modules/shogi_test.py::ShogiTest::test_last_move
test/modules/shogi_test.py::ShogiTest::test_find_koma test/modules/shogi_test.py::ShogiTest::test_movable_for_empty test/modules/shogi_test.py::ShogiTest::test_movable_for_enemy test/modules/shogi_test.py::ShogiTest::test_movable_for_fu test/modules/shogi_test.py::ShogiTest::test_movable_for_kaku test/modules/shogi_test.py::ShogiTest::test_movable_for_kyo test/modules/shogi_test.py::ShogiTest::test_move_76fu test/modules/shogi_test.py::ShogiTest::test_move_for_promote
app/modules/shogi.py
app/modules/shogi.py