From 0c4571b6b18044aa0119c283aa09a4cd7ea6ec4c Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 00:33:10 +0000 Subject: [PATCH 1/7] =?UTF-8?q?point=EC=97=90=20bytes=20marsha/unmarshal?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- board/data/internal/point.py | 11 +++++++++++ board/data/test/__init__.py | 1 + board/data/test/point_test.py | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 board/data/test/point_test.py diff --git a/board/data/internal/point.py b/board/data/internal/point.py index fb8bc3e..fd4e6b6 100644 --- a/board/data/internal/point.py +++ b/board/data/internal/point.py @@ -8,3 +8,14 @@ class Point: def copy(self): return Point(self.x, self.y) + + def marshal_bytes(self) -> bytes: + x_b = self.x.to_bytes(length=8, signed=True) + y_b = self.y.to_bytes(length=8, signed=True) + return x_b + y_b + + @staticmethod + def unmarshal_bytes(b: bytes): + x = int.from_bytes(bytes=b[:8], signed=True) + y = int.from_bytes(bytes=b[8:], signed=True) + return Point(x, y) diff --git a/board/data/test/__init__.py b/board/data/test/__init__.py index c8d0f10..50d0983 100644 --- a/board/data/test/__init__.py +++ b/board/data/test/__init__.py @@ -3,6 +3,7 @@ from .section_test import SectionTestCase, SectionApplyNeighborTestCase from .tile_test import TileTestCase from .tiles_test import TilesTestCase +from .point_test import PointTestCase if __name__ == "__main__": unittest.main() diff --git a/board/data/test/point_test.py b/board/data/test/point_test.py new file mode 100644 index 0000000..da83b95 --- /dev/null +++ b/board/data/test/point_test.py @@ -0,0 +1,25 @@ +import unittest +from board.data import Point + + +class PointTestCase(unittest.TestCase): + def test_marshal_bytes(self): + p = Point(5, -1) + b = p.marshal_bytes() + + self.assertEqual(len(b), 16) + + x = int.from_bytes(bytes=b[:8], signed=True) + self.assertEqual(x, p.x) + + y = int.from_bytes(bytes=b[8:], signed=True) + self.assertEqual(y, p.y) + + def test_unmarshal_bytes(self): + expected = Point(5, -1) + + # 5, -1 + data = b'\x00\x00\x00\x00\x00\x00\x00\x05\xff\xff\xff\xff\xff\xff\xff\xff' + + p = Point.unmarshal_bytes(data) + self.assertEqual(expected, p) From bcbeae63c073309f5a54b031345ab7e495021573 Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 01:39:43 +0000 Subject: [PATCH 2/7] =?UTF-8?q?section=20storage=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- board/data/storage/__init__.py | 1 + .../data/storage/internal/section_storage.py | 28 ++++++++++++ board/data/storage/test/__init__.py | 1 + .../data/storage/test/section_storage_test.py | 43 +++++++++++++++++++ tests/__init__.py | 1 + 5 files changed, 74 insertions(+) create mode 100644 board/data/storage/__init__.py create mode 100644 board/data/storage/internal/section_storage.py create mode 100644 board/data/storage/test/__init__.py create mode 100644 board/data/storage/test/section_storage_test.py diff --git a/board/data/storage/__init__.py b/board/data/storage/__init__.py new file mode 100644 index 0000000..9d5d1ab --- /dev/null +++ b/board/data/storage/__init__.py @@ -0,0 +1 @@ +from .internal.section_storage import SectionStorage diff --git a/board/data/storage/internal/section_storage.py b/board/data/storage/internal/section_storage.py new file mode 100644 index 0000000..983fc50 --- /dev/null +++ b/board/data/storage/internal/section_storage.py @@ -0,0 +1,28 @@ +from board.data import Point, Section +import lmdb + +env = lmdb.Environment(path="./database", max_dbs=1) + + +class SectionStorage: + def get(p: Point) -> Section | None: + with env.begin() as tx: + p_key = p.marshal_bytes() + + data = tx.get(p_key) + if data is not None: + return Section(p=p, data=bytearray(data)) + + return None + + def create(section: Section): + with env.begin(write=True) as tx: + p_key = section.p.marshal_bytes() + + tx.put(key=p_key, value=section.data, overwrite=False) + + def update(section: Section): + with env.begin(write=True) as tx: + p_key = section.p.marshal_bytes() + + tx.put(key=p_key, value=section.data, overwrite=True) \ No newline at end of file diff --git a/board/data/storage/test/__init__.py b/board/data/storage/test/__init__.py new file mode 100644 index 0000000..46585f1 --- /dev/null +++ b/board/data/storage/test/__init__.py @@ -0,0 +1 @@ +from .section_storage_test import SectionStorageTestCase diff --git a/board/data/storage/test/section_storage_test.py b/board/data/storage/test/section_storage_test.py new file mode 100644 index 0000000..77117da --- /dev/null +++ b/board/data/storage/test/section_storage_test.py @@ -0,0 +1,43 @@ +from board.data import Point, Section +from board.data.storage import SectionStorage +from board.data.storage.internal.section_storage import env + +import unittest + + +class SectionStorageTestCase(unittest.TestCase): + def tearDown(self): + with env.begin(write=True) as txn: + db = env.open_db() + txn.drop(db) + + def test_create(self): + sec = Section.create(Point(0, 0)) + + SectionStorage.create(sec) + + def test_get_not_exits(self): + section = SectionStorage.get(Point(0, 0)) + self.assertIsNone(section) + + def test_get(self): + sec = Section.create(Point(1, -1)) + SectionStorage.create(sec) + + got = SectionStorage.get(sec.p) + self.assertIsNotNone(got) + self.assertEqual(type(got), Section) + self.assertEqual(got.p, sec.p) + self.assertEqual(got.data, sec.data) + + def test_update(self): + sec = Section.create(Point(0, 0)) + SectionStorage.create(sec) + + sec.data[0] = 0b11111111 + + SectionStorage.update(sec) + + updated = SectionStorage.get(sec.p) + self.assertIsNotNone(updated) + self.assertEqual(updated.data[0], 0b11111111) diff --git a/tests/__init__.py b/tests/__init__.py index 00d728d..ec31d5b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -9,6 +9,7 @@ # board from board.data.test import * + from board.data.storage.test import * from board.data.handler.test import * from board.event.handler.test import * From f9fb74a589dc03fd3f4ad9857832f08e17428b4c Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 04:42:20 +0000 Subject: [PATCH 3/7] =?UTF-8?q?section=20storage=20board=20handler?= =?UTF-8?q?=EC=97=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- board/data/handler/internal/board.py | 140 ++++++++---------- board/data/handler/test/board_test.py | 9 +- board/data/handler/test/fixtures.py | 51 ------- .../data/storage/internal/section_storage.py | 17 ++- board/data/storage/test/fixtures.py | 48 ++++++ .../data/storage/test/section_storage_test.py | 14 +- .../event/handler/test/board_handler_test.py | 2 +- server_test.py | 2 +- 8 files changed, 141 insertions(+), 142 deletions(-) delete mode 100644 board/data/handler/test/fixtures.py create mode 100644 board/data/storage/test/fixtures.py diff --git a/board/data/handler/internal/board.py b/board/data/handler/internal/board.py index f348744..61d4029 100644 --- a/board/data/handler/internal/board.py +++ b/board/data/handler/internal/board.py @@ -1,30 +1,28 @@ import random from board.data import Point, Section, Tile, Tiles +from board.data.storage import SectionStorage from cursor.data import Color -def init_first_section() -> dict[int, dict[int, Section]]: - section_0_0 = Section.create(Point(0, 0)) +def init_board(): + # 맵 초기화 + sec_0_0 = SectionStorage.get(Point(0, 0)) + if sec_0_0 is None: + section_0_0 = Section.create(Point(0, 0)) - tiles = section_0_0.fetch(Point(0, 0)) + tiles = section_0_0.fetch(Point(0, 0)) + t = Tile.from_int(tiles.data[0]) + t.is_open = True - t = Tile.from_int(tiles.data[0]) - t.is_open = True + section_0_0.update(Tiles(data=[t.data]), Point(0, 0)) - section_0_0.update(Tiles(data=[t.data]), Point(0, 0)) + SectionStorage.create(section_0_0) - return {0: {0: section_0_0}} +init_board() -class BoardHandler: - # sections[y][x] - sections: dict[int, dict[int, Section]] = init_first_section() - # 맵의 각 끝단 섹션 위치 - max_x: int = 0 - min_x: int = 0 - max_y: int = 0 - min_y: int = 0 +class BoardHandler: @staticmethod def fetch(start: Point, end: Point) -> Tiles: @@ -32,17 +30,18 @@ def fetch(start: Point, end: Point) -> Tiles: out_width, out_height = (end.x - start.x + 1), (start.y - end.y + 1) out = bytearray(out_width * out_height) - # TODO: 새로운 섹션과의 관계로 경계값이 바뀔 수 있음. + # 새로운 섹션과의 관계로 경계값이 바뀔 수 있음. # 이를 fetch 결과에 적용시킬 수 있도록 미리 다 만들어놓고 fetch를 시작해야 함. - # 현재는 섹션이 메모리 내부 레퍼런스로 저장되기 때문에 이렇게 미리 받아놓고 할 수 있음. - # 나중에는 다시 섹션을 가져와야 함. - sections = [] + sec_points = [] for sec_y in range(start.y // Section.LENGTH, end.y // Section.LENGTH - 1, - 1): for sec_x in range(start.x // Section.LENGTH, end.x // Section.LENGTH + 1): - section = BoardHandler._get_or_create_section(sec_x, sec_y) - sections.append(section) + sec_p = Point(sec_x, sec_y) + BoardHandler._get_or_create_section(sec_p) + sec_points.append(sec_p) + + for sec_p in sec_points: + section = BoardHandler._get_or_create_section(sec_p) - for section in sections: inner_start = Point( x=max(start.x, section.abs_x) - (section.abs_x), y=min(start.y, section.abs_y + Section.LENGTH-1) - section.abs_y @@ -81,7 +80,7 @@ def open_tile(p: Point) -> Tile: tiles.data[0] = tile.data section.update(data=tiles, start=inner_p) - BoardHandler._save_section(section) + SectionStorage.update(section) return tile @@ -103,9 +102,9 @@ def fetch_section(sec_p: Point) -> Section: ] for dx, dy in delta: new_p = Point(x=sec_p.x+dx, y=sec_p.y+dy) - _ = BoardHandler._get_or_create_section(new_p.x, new_p.y) + _ = BoardHandler._get_or_create_section(new_p) - new_section = BoardHandler._get_or_create_section(sec_p.x, sec_p.y) + new_section = BoardHandler._get_or_create_section(sec_p) return new_section def get_section(p: Point) -> tuple[Section, Point]: @@ -195,7 +194,7 @@ def get_section(p: Point) -> tuple[Section, Point]: # 섹션 변경사항 모두 저장 for section in sections: - BoardHandler._save_section(section) + SectionStorage.update(section) start_p = Point(min_x, max_y) end_p = Point(max_x, min_y) @@ -216,7 +215,7 @@ def set_flag_state(p: Point, state: bool, color: Color | None = None) -> Tile: tiles.data[0] = tile.data section.update(data=tiles, start=inner_p) - BoardHandler._save_section(section) + SectionStorage.update(section) return tile @@ -229,7 +228,7 @@ def _get_section_from_abs_point(abs_p: Point) -> tuple[Section, Point]: y=abs_p.y // Section.LENGTH ) - section = BoardHandler._get_or_create_section(sec_p.x, sec_p.y) + section = BoardHandler._get_or_create_section(sec_p) inner_p = Point( x=abs_p.x - section.abs_x, @@ -238,9 +237,6 @@ def _get_section_from_abs_point(abs_p: Point) -> tuple[Section, Point]: return section, inner_p - def _save_section(section: Section): - BoardHandler.sections[section.p.y][section.p.x] = section - @staticmethod def get_random_open_position() -> Point: """ @@ -250,23 +246,14 @@ def get_random_open_position() -> Point: # 이미 방문한 섹션들 visited = set() - sec_x_range = (BoardHandler.min_x, BoardHandler.max_x) - sec_y_range = (BoardHandler.min_y, BoardHandler.max_y) - while True: - rand_p = Point( - x=random.randint(sec_x_range[0], sec_x_range[1]), - y=random.randint(sec_y_range[0], sec_y_range[1]) - ) - + rand_p = SectionStorage.get_random_sec_point() if (rand_p.x, rand_p.y) in visited: continue visited.add((rand_p.x, rand_p.y)) - chosen_section = BoardHandler._get_section_or_none(rand_p.x, rand_p.y) - if chosen_section is None: - continue + chosen_section = BoardHandler._get_section_or_none(rand_p) # 섹션 내부의 랜덤한 열린 타일 위치를 찾는다. inner_point = randomly_find_open_tile(chosen_section) @@ -281,50 +268,43 @@ def get_random_open_position() -> Point: return open_point @staticmethod - def _get_or_create_section(x: int, y: int) -> Section: - if y not in BoardHandler.sections: - BoardHandler.max_y = max(BoardHandler.max_y, y) - BoardHandler.min_y = min(BoardHandler.min_y, y) - - BoardHandler.sections[y] = {} - - if x not in BoardHandler.sections[y]: - BoardHandler.max_x = max(BoardHandler.max_x, x) - BoardHandler.min_x = min(BoardHandler.min_x, x) - - new_section = Section.create(Point(x, y)) - - # (x, y) - delta = [ - (0, 1), (0, -1), (-1, 0), (1, 0), # 상하좌우 - (-1, 1), (1, 1), (-1, -1), (1, -1), # 좌상 우상 좌하 우하 - ] - - # 주변 섹션과 새로운 섹션의 인접 타일을 서로 적용시킨다. - for dx, dy in delta: - nx, ny = x+dx, y+dy - neighbor = BoardHandler._get_section_or_none(nx, ny) - # 주변 섹션이 없을 수 있음. - if neighbor is None: - continue + def _get_or_create_section(p: Point) -> Section: + section = BoardHandler._get_section_or_none(p) + if section is not None: + return section + + new_section = Section.create(p) + + # (x, y) + delta = [ + (0, 1), (0, -1), (-1, 0), (1, 0), # 상하좌우 + (-1, 1), (1, 1), (-1, -1), (1, -1), # 좌상 우상 좌하 우하 + ] + + # 주변 섹션과 새로운 섹션의 인접 타일을 서로 적용시킨다. + for dx, dy in delta: + np = Point(p.x+dx, p.y+dy) + neighbor = BoardHandler._get_section_or_none(np) + # 주변 섹션이 없을 수 있음. + if neighbor is None: + continue - if dx != 0 and dy != 0: - neighbor.apply_neighbor_diagonal(new_section) - elif dx != 0: - neighbor.apply_neighbor_horizontal(new_section) - elif dy != 0: - neighbor.apply_neighbor_vertical(new_section) + if dx != 0 and dy != 0: + neighbor.apply_neighbor_diagonal(new_section) + elif dx != 0: + neighbor.apply_neighbor_horizontal(new_section) + elif dy != 0: + neighbor.apply_neighbor_vertical(new_section) - BoardHandler.sections[ny][nx] = neighbor + SectionStorage.update(neighbor) - BoardHandler.sections[y][x] = new_section + SectionStorage.create(new_section) - return BoardHandler.sections[y][x] + return new_section @staticmethod - def _get_section_or_none(x: int, y: int) -> Section | None: - if y in BoardHandler.sections and x in BoardHandler.sections[y]: - return BoardHandler.sections[y][x] + def _get_section_or_none(p: Point) -> Section | None: + return SectionStorage.get(p) def randomly_find_open_tile(section: Section) -> Point | None: diff --git a/board/data/handler/test/board_test.py b/board/data/handler/test/board_test.py index fa7ff86..2fec725 100644 --- a/board/data/handler/test/board_test.py +++ b/board/data/handler/test/board_test.py @@ -4,7 +4,9 @@ from board.data import Point, Tile, Section from board.data.handler import BoardHandler from cursor.data import Color -from .fixtures import setup_board + +from board.data.storage import SectionStorage +from board.data.storage.test.fixtures import setup_board, teardown_board FETCH_CASE = \ [ @@ -128,8 +130,9 @@ def test_get_random_open_position(self): self.assertTrue(tile.is_open) def test_get_random_open_position_one_section_one_open(self): - sec = BoardHandler.sections[-1][0] - BoardHandler.sections = {-1: {0: sec}} + sec = SectionStorage.get(Point(-1, 0)) + teardown_board() + SectionStorage.create(sec) for _ in range(10): point = BoardHandler.get_random_open_position() diff --git a/board/data/handler/test/fixtures.py b/board/data/handler/test/fixtures.py deleted file mode 100644 index 88a11ec..0000000 --- a/board/data/handler/test/fixtures.py +++ /dev/null @@ -1,51 +0,0 @@ -from board.data import Section, Point -from board.data.handler import BoardHandler - - -def setup_board(): - """ - /docs/example-map-state.png - """ - - Section.LENGTH = 4 - - tile_state_1 = bytearray([ - 0b00000000, 0b00000000, 0b00000000, 0b00000000, - 0b00000001, 0b00000001, 0b00000001, 0b00000000, - 0b10000001, 0b01110000, 0b00000001, 0b00000000, - 0b10000001, 0b00000001, 0b00000001, 0b00000000 - ]) - tile_state_2 = bytearray([ - 0b00000001, 0b00000010, 0b00000010, 0b00000001, - 0b00000001, 0b01000000, 0b01000000, 0b00000001, - 0b00000001, 0b00000010, 0b10000010, 0b10000001, - 0b00000001, 0b00100001, 0b10000001, 0b10000000 - ]) - tile_state_3 = bytearray([ - 0b00000001, 0b01000000, 0b10000010, 0b10000001, - 0b10000001, 0b10000001, 0b10000011, 0b01101000, - 0b10000000, 0b10000000, 0b10000010, 0b01000000, - 0b11000000, 0b10000000, 0b10000001, 0b00000001 - ]) - tile_state_4 = bytearray([ - 0b10000001, 0b00111001, 0b00000001, 0b00000001, - 0b00000011, 0b00000010, 0b01000000, 0b00000001, - 0b00000011, 0b01000000, 0b00000010, 0b00000001, - 0b00000010, 0b00000001, 0b00000001, 0b00000000 - ]) - - BoardHandler.sections = \ - { - 0: { - 0: Section(Point(0, 0), tile_state_1), - -1: Section(Point(-1, 0), tile_state_2) - }, - -1: { - 0: Section(Point(0, -1), tile_state_4), - -1: Section(Point(-1, -1), tile_state_3) - } - } - BoardHandler.max_x = 0 - BoardHandler.min_x = -1 - BoardHandler.max_y = 0 - BoardHandler.min_y = -1 diff --git a/board/data/storage/internal/section_storage.py b/board/data/storage/internal/section_storage.py index 983fc50..1f81d42 100644 --- a/board/data/storage/internal/section_storage.py +++ b/board/data/storage/internal/section_storage.py @@ -1,10 +1,23 @@ from board.data import Point, Section import lmdb +import random env = lmdb.Environment(path="./database", max_dbs=1) class SectionStorage: + def get_random_sec_point() -> Point: + """ + 주의: 한개 이상의 섹션이 존재해야 함 + TODO: 전체 key를 불러오고 있음. + """ + with env.begin() as tx: + with tx.cursor() as cursor: + keys = list(cursor.iternext(keys=True, values=False)) + rand_key = random.choice(keys) + + return Point.unmarshal_bytes(rand_key) + def get(p: Point) -> Section | None: with env.begin() as tx: p_key = p.marshal_bytes() @@ -21,8 +34,8 @@ def create(section: Section): tx.put(key=p_key, value=section.data, overwrite=False) - def update(section: Section): + def update(section: Section): with env.begin(write=True) as tx: p_key = section.p.marshal_bytes() - tx.put(key=p_key, value=section.data, overwrite=True) \ No newline at end of file + tx.put(key=p_key, value=section.data, overwrite=True) diff --git a/board/data/storage/test/fixtures.py b/board/data/storage/test/fixtures.py new file mode 100644 index 0000000..4e9e850 --- /dev/null +++ b/board/data/storage/test/fixtures.py @@ -0,0 +1,48 @@ +from board.data import Section, Point +from board.data.storage.internal.section_storage import env +from board.data.storage import SectionStorage + + +def setup_board(): + """ + /docs/example-map-state.png + """ + teardown_board() + + Section.LENGTH = 4 + + sections = [ + Section(Point(0, 0), bytearray([ + 0b00000000, 0b00000000, 0b00000000, 0b00000000, + 0b00000001, 0b00000001, 0b00000001, 0b00000000, + 0b10000001, 0b01110000, 0b00000001, 0b00000000, + 0b10000001, 0b00000001, 0b00000001, 0b00000000 + ])), + Section(Point(-1, 0), bytearray([ + 0b00000001, 0b00000010, 0b00000010, 0b00000001, + 0b00000001, 0b01000000, 0b01000000, 0b00000001, + 0b00000001, 0b00000010, 0b10000010, 0b10000001, + 0b00000001, 0b00100001, 0b10000001, 0b10000000 + ])), + Section(Point(-1, -1), bytearray([ + 0b00000001, 0b01000000, 0b10000010, 0b10000001, + 0b10000001, 0b10000001, 0b10000011, 0b01101000, + 0b10000000, 0b10000000, 0b10000010, 0b01000000, + 0b11000000, 0b10000000, 0b10000001, 0b00000001 + ])), + Section(Point(0, -1), bytearray([ + 0b10000001, 0b00111001, 0b00000001, 0b00000001, + 0b00000011, 0b00000010, 0b01000000, 0b00000001, + 0b00000011, 0b01000000, 0b00000010, 0b00000001, + 0b00000010, 0b00000001, 0b00000001, 0b00000000 + ])) + ] + + for section in sections: + SectionStorage.create(section) + + +def teardown_board(): + with env.begin(write=True) as txn: + db = env.open_db() + txn.drop(db) diff --git a/board/data/storage/test/section_storage_test.py b/board/data/storage/test/section_storage_test.py index 77117da..6a616b9 100644 --- a/board/data/storage/test/section_storage_test.py +++ b/board/data/storage/test/section_storage_test.py @@ -1,15 +1,13 @@ from board.data import Point, Section from board.data.storage import SectionStorage -from board.data.storage.internal.section_storage import env +from .fixtures import teardown_board import unittest class SectionStorageTestCase(unittest.TestCase): def tearDown(self): - with env.begin(write=True) as txn: - db = env.open_db() - txn.drop(db) + teardown_board() def test_create(self): sec = Section.create(Point(0, 0)) @@ -41,3 +39,11 @@ def test_update(self): updated = SectionStorage.get(sec.p) self.assertIsNotNone(updated) self.assertEqual(updated.data[0], 0b11111111) + + def test_get_random_sec_point(self): + sec = Section.create(Point(0, 0)) + SectionStorage.create(sec) + + p = SectionStorage.get_random_sec_point() + + self.assertEqual(p, sec.p) diff --git a/board/event/handler/test/board_handler_test.py b/board/event/handler/test/board_handler_test.py index 42ac6aa..ea73e36 100644 --- a/board/event/handler/test/board_handler_test.py +++ b/board/event/handler/test/board_handler_test.py @@ -3,7 +3,7 @@ from board.data import Point, Tile, Tiles from board.event.handler import BoardEventHandler from board.data.handler import BoardHandler -from board.data.handler.test.fixtures import setup_board +from board.data.storage.test.fixtures import setup_board from message import Message from message.payload import ( FetchTilesPayload, diff --git a/server_test.py b/server_test.py index 5d713b9..1d67adc 100644 --- a/server_test.py +++ b/server_test.py @@ -4,7 +4,7 @@ from server import app from message import Message from message.payload import FetchTilesPayload, TilesPayload, TilesEvent, NewConnEvent -from board.data.handler.test.fixtures import setup_board +from board.data.storage.test.fixtures import setup_board from board.event.handler import BoardEventHandler from board.data import Point, Tile, Tiles from event import EventBroker From 8fe229c27fd191e6c4c45aa7aa76297cc381e414 Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 04:58:12 +0000 Subject: [PATCH 4/7] =?UTF-8?q?update=20&=20create=20->=20set=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- board/data/handler/internal/board.py | 12 ++++++------ board/data/handler/test/board_test.py | 2 +- board/data/storage/internal/section_storage.py | 10 ++-------- board/data/storage/test/fixtures.py | 2 +- board/data/storage/test/section_storage_test.py | 14 +++++++------- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/board/data/handler/internal/board.py b/board/data/handler/internal/board.py index 61d4029..c8c441d 100644 --- a/board/data/handler/internal/board.py +++ b/board/data/handler/internal/board.py @@ -16,7 +16,7 @@ def init_board(): section_0_0.update(Tiles(data=[t.data]), Point(0, 0)) - SectionStorage.create(section_0_0) + SectionStorage.set(section_0_0) init_board() @@ -80,7 +80,7 @@ def open_tile(p: Point) -> Tile: tiles.data[0] = tile.data section.update(data=tiles, start=inner_p) - SectionStorage.update(section) + SectionStorage.set(section) return tile @@ -194,7 +194,7 @@ def get_section(p: Point) -> tuple[Section, Point]: # 섹션 변경사항 모두 저장 for section in sections: - SectionStorage.update(section) + SectionStorage.set(section) start_p = Point(min_x, max_y) end_p = Point(max_x, min_y) @@ -215,7 +215,7 @@ def set_flag_state(p: Point, state: bool, color: Color | None = None) -> Tile: tiles.data[0] = tile.data section.update(data=tiles, start=inner_p) - SectionStorage.update(section) + SectionStorage.set(section) return tile @@ -296,9 +296,9 @@ def _get_or_create_section(p: Point) -> Section: elif dy != 0: neighbor.apply_neighbor_vertical(new_section) - SectionStorage.update(neighbor) + SectionStorage.set(neighbor) - SectionStorage.create(new_section) + SectionStorage.set(new_section) return new_section diff --git a/board/data/handler/test/board_test.py b/board/data/handler/test/board_test.py index 2fec725..0d48294 100644 --- a/board/data/handler/test/board_test.py +++ b/board/data/handler/test/board_test.py @@ -132,7 +132,7 @@ def test_get_random_open_position(self): def test_get_random_open_position_one_section_one_open(self): sec = SectionStorage.get(Point(-1, 0)) teardown_board() - SectionStorage.create(sec) + SectionStorage.set(sec) for _ in range(10): point = BoardHandler.get_random_open_position() diff --git a/board/data/storage/internal/section_storage.py b/board/data/storage/internal/section_storage.py index 1f81d42..59f2c70 100644 --- a/board/data/storage/internal/section_storage.py +++ b/board/data/storage/internal/section_storage.py @@ -28,14 +28,8 @@ def get(p: Point) -> Section | None: return None - def create(section: Section): + def set(section: Section): with env.begin(write=True) as tx: p_key = section.p.marshal_bytes() - tx.put(key=p_key, value=section.data, overwrite=False) - - def update(section: Section): - with env.begin(write=True) as tx: - p_key = section.p.marshal_bytes() - - tx.put(key=p_key, value=section.data, overwrite=True) + tx.put(key=p_key, value=section.data) diff --git a/board/data/storage/test/fixtures.py b/board/data/storage/test/fixtures.py index 4e9e850..4ec99ff 100644 --- a/board/data/storage/test/fixtures.py +++ b/board/data/storage/test/fixtures.py @@ -39,7 +39,7 @@ def setup_board(): ] for section in sections: - SectionStorage.create(section) + SectionStorage.set(section) def teardown_board(): diff --git a/board/data/storage/test/section_storage_test.py b/board/data/storage/test/section_storage_test.py index 6a616b9..6e31f53 100644 --- a/board/data/storage/test/section_storage_test.py +++ b/board/data/storage/test/section_storage_test.py @@ -9,10 +9,10 @@ class SectionStorageTestCase(unittest.TestCase): def tearDown(self): teardown_board() - def test_create(self): + def test_set_create(self): sec = Section.create(Point(0, 0)) - SectionStorage.create(sec) + SectionStorage.set(sec) def test_get_not_exits(self): section = SectionStorage.get(Point(0, 0)) @@ -20,7 +20,7 @@ def test_get_not_exits(self): def test_get(self): sec = Section.create(Point(1, -1)) - SectionStorage.create(sec) + SectionStorage.set(sec) got = SectionStorage.get(sec.p) self.assertIsNotNone(got) @@ -28,13 +28,13 @@ def test_get(self): self.assertEqual(got.p, sec.p) self.assertEqual(got.data, sec.data) - def test_update(self): + def test_set_update(self): sec = Section.create(Point(0, 0)) - SectionStorage.create(sec) + SectionStorage.set(sec) sec.data[0] = 0b11111111 - SectionStorage.update(sec) + SectionStorage.set(sec) updated = SectionStorage.get(sec.p) self.assertIsNotNone(updated) @@ -42,7 +42,7 @@ def test_update(self): def test_get_random_sec_point(self): sec = Section.create(Point(0, 0)) - SectionStorage.create(sec) + SectionStorage.set(sec) p = SectionStorage.get_random_sec_point() From e62ba4cea6916019f5be2f3eed493fac21a94d48 Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 05:25:29 +0000 Subject: [PATCH 5/7] =?UTF-8?q?section=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0=20=EC=A0=84=20=EC=A3=BC=EB=B3=80=20=EC=84=B9=EC=85=98?= =?UTF-8?q?=20=EB=AA=A8=EB=91=90=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- board/data/handler/internal/board.py | 92 ++++++++++++--------------- board/data/handler/test/board_test.py | 12 +++- 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/board/data/handler/internal/board.py b/board/data/handler/internal/board.py index c8c441d..02fc2db 100644 --- a/board/data/handler/internal/board.py +++ b/board/data/handler/internal/board.py @@ -30,41 +30,33 @@ def fetch(start: Point, end: Point) -> Tiles: out_width, out_height = (end.x - start.x + 1), (start.y - end.y + 1) out = bytearray(out_width * out_height) - # 새로운 섹션과의 관계로 경계값이 바뀔 수 있음. - # 이를 fetch 결과에 적용시킬 수 있도록 미리 다 만들어놓고 fetch를 시작해야 함. - sec_points = [] for sec_y in range(start.y // Section.LENGTH, end.y // Section.LENGTH - 1, - 1): for sec_x in range(start.x // Section.LENGTH, end.x // Section.LENGTH + 1): - sec_p = Point(sec_x, sec_y) - BoardHandler._get_or_create_section(sec_p) - sec_points.append(sec_p) + section = BoardHandler._get_or_create_section(Point(sec_x, sec_y)) - for sec_p in sec_points: - section = BoardHandler._get_or_create_section(sec_p) + inner_start = Point( + x=max(start.x, section.abs_x) - (section.abs_x), + y=min(start.y, section.abs_y + Section.LENGTH-1) - section.abs_y + ) + inner_end = Point( + x=min(end.x, section.abs_x + Section.LENGTH-1) - section.abs_x, + y=max(end.y, section.abs_y) - section.abs_y + ) - inner_start = Point( - x=max(start.x, section.abs_x) - (section.abs_x), - y=min(start.y, section.abs_y + Section.LENGTH-1) - section.abs_y - ) - inner_end = Point( - x=min(end.x, section.abs_x + Section.LENGTH-1) - section.abs_x, - y=max(end.y, section.abs_y) - section.abs_y - ) + fetched = section.fetch(start=inner_start, end=inner_end) - fetched = section.fetch(start=inner_start, end=inner_end) + x_gap, y_gap = (inner_end.x - inner_start.x + 1), (inner_start.y - inner_end.y + 1) - x_gap, y_gap = (inner_end.x - inner_start.x + 1), (inner_start.y - inner_end.y + 1) + # start로부터 떨어진 거리 + out_x = (section.abs_x + inner_start.x) - start.x + out_y = start.y - (section.abs_y + inner_start.y) - # start로부터 떨어진 거리 - out_x = (section.abs_x + inner_start.x) - start.x - out_y = start.y - (section.abs_y + inner_start.y) + for row_num in range(y_gap): + out_idx = (out_width * (out_y + row_num)) + out_x + data_idx = row_num * x_gap - for row_num in range(y_gap): - out_idx = (out_width * (out_y + row_num)) + out_x - data_idx = row_num * x_gap - - data = fetched.data[data_idx:data_idx+x_gap] - out[out_idx:out_idx+x_gap] = data + data = fetched.data[data_idx:data_idx+x_gap] + out[out_idx:out_idx+x_gap] = data return Tiles(data=out) @@ -94,19 +86,6 @@ def open_tiles_cascade(p: Point) -> tuple[Point, Point, Tiles]: # 탐색하며 발견한 섹션들 sections: list[Section] = [] - def fetch_section(sec_p: Point) -> Section: - # 가져오는 데이터의 일관성을 위해 주변 섹션을 미리 만들어놓기 - delta = [ - (0, 1), (0, -1), (-1, 0), (1, 0), # 상하좌우 - (-1, 1), (1, 1), (-1, -1), (1, -1), # 좌상 우상 좌하 우하 - ] - for dx, dy in delta: - new_p = Point(x=sec_p.x+dx, y=sec_p.y+dy) - _ = BoardHandler._get_or_create_section(new_p) - - new_section = BoardHandler._get_or_create_section(sec_p) - return new_section - def get_section(p: Point) -> tuple[Section, Point]: sec_p = Point( x=p.x // Section.LENGTH, @@ -122,7 +101,7 @@ def get_section(p: Point) -> tuple[Section, Point]: # 새로 가져오기 if section is None: - section = fetch_section(sec_p) + section = BoardHandler._get_or_create_section(sec_p) sections.append(section) inner_p = Point( @@ -269,11 +248,17 @@ def get_random_open_position() -> Point: @staticmethod def _get_or_create_section(p: Point) -> Section: + """ + p에 해당하는 섹션을 가져온다. 만약 섹션이 존재하지 않으면 새로 만든다. + 완전한(주변 섹션과의 관계가 모두 적용된) 섹션을 반환한다. + """ section = BoardHandler._get_section_or_none(p) - if section is not None: - return section - new_section = Section.create(p) + is_complete_section = True + + if section is None: + is_complete_section = False + section = Section.create(p) # (x, y) delta = [ @@ -285,22 +270,27 @@ def _get_or_create_section(p: Point) -> Section: for dx, dy in delta: np = Point(p.x+dx, p.y+dy) neighbor = BoardHandler._get_section_or_none(np) - # 주변 섹션이 없을 수 있음. - if neighbor is None: + if neighbor is not None: continue + # 주변 섹션이 존재하지 않으면 새로 생성 및 section에 적용. + neighbor = Section.create(np) + is_complete_section = False + if dx != 0 and dy != 0: - neighbor.apply_neighbor_diagonal(new_section) + section.apply_neighbor_diagonal(neighbor) elif dx != 0: - neighbor.apply_neighbor_horizontal(new_section) + section.apply_neighbor_horizontal(neighbor) elif dy != 0: - neighbor.apply_neighbor_vertical(new_section) + section.apply_neighbor_vertical(neighbor) SectionStorage.set(neighbor) - SectionStorage.set(new_section) + # 모서리 적용이 안 되어 있었다면 적용된 버전의 섹션을 저장 + if not is_complete_section: + SectionStorage.set(section) - return new_section + return section @staticmethod def _get_section_or_none(p: Point) -> Section | None: diff --git a/board/data/handler/test/board_test.py b/board/data/handler/test/board_test.py index 0d48294..e6574a1 100644 --- a/board/data/handler/test/board_test.py +++ b/board/data/handler/test/board_test.py @@ -39,7 +39,15 @@ def setUp(self): setup_board() @cases(FETCH_CASE) - def test_fetch(self, data, expect): + @patch("board.data.Section.create") + def test_fetch(self, mock: MagicMock, data, expect): + def stub_section_create(p: Point) -> Section: + return Section( + data=bytearray([0b00000000 for _ in range(Section.LENGTH ** 2)]), + p=p + ) + mock.side_effect = stub_section_create + start_p = data["start_p"] end_p = data["end_p"] @@ -72,7 +80,7 @@ def stub_section_create(p: Point) -> Section: start_p, end_p, tiles = BoardHandler.open_tiles_cascade(p) - self.assertEqual(len(create_seciton_mock.mock_calls), 20) + self.assertEqual(len(create_seciton_mock.mock_calls), 21) self.assertEqual(start_p, Point(-1, 3)) self.assertEqual(end_p, Point(3, -1)) From 75e3649472cd9c1a1fdc4c3c48b205caae938aee Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 06:29:12 +0000 Subject: [PATCH 6/7] =?UTF-8?q?env=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dev.env | 2 ++ board/data/storage/internal/section_storage.py | 3 ++- config.py | 10 ++++++++++ .../handler/internal/cursor_event_handler.py | 4 ++-- tools/deploy.sh | 18 +++--------------- 5 files changed, 19 insertions(+), 18 deletions(-) create mode 100644 .dev.env create mode 100644 config.py diff --git a/.dev.env b/.dev.env new file mode 100644 index 0000000..e79d817 --- /dev/null +++ b/.dev.env @@ -0,0 +1,2 @@ +MINE_KILL_DURATION_SECONDS=60 +BOARD_DATABASE_PATH="/tmp/gamulpung-board-db" \ No newline at end of file diff --git a/board/data/storage/internal/section_storage.py b/board/data/storage/internal/section_storage.py index 59f2c70..41b196d 100644 --- a/board/data/storage/internal/section_storage.py +++ b/board/data/storage/internal/section_storage.py @@ -1,8 +1,9 @@ from board.data import Point, Section import lmdb import random +from config import BOARD_DATABASE_PATH -env = lmdb.Environment(path="./database", max_dbs=1) +env = lmdb.Environment(path=BOARD_DATABASE_PATH) class SectionStorage: diff --git a/config.py b/config.py new file mode 100644 index 0000000..d4c4c0b --- /dev/null +++ b/config.py @@ -0,0 +1,10 @@ +import os + +from dotenv import load_dotenv + +if os.environ.get("ENV") != "prod": + load_dotenv(".dev.env") + + +MINE_KILL_DURATION_SECONDS: int = int(os.environ.get("MINE_KILL_DURATION_SECONDS")) +BOARD_DATABASE_PATH: str = os.environ.get("BOARD_DATABASE_PATH") diff --git a/cursor/event/handler/internal/cursor_event_handler.py b/cursor/event/handler/internal/cursor_event_handler.py index 5013857..f6b860e 100644 --- a/cursor/event/handler/internal/cursor_event_handler.py +++ b/cursor/event/handler/internal/cursor_event_handler.py @@ -33,6 +33,7 @@ ErrorPayload, NewCursorCandidatePayload ) +from config import MINE_KILL_DURATION_SECONDS class CursorEventHandler: @@ -344,8 +345,7 @@ async def receive_single_tile_opened(message: Message[SingleTileOpenedPayload]): nearby_cursors = CursorHandler.exists_range(start=start_p, end=end_p) if len(nearby_cursors) > 0: - # TODO: 하드코딩 없애기 - revive_at = datetime.now() + timedelta(minutes=3) + revive_at = datetime.now() + timedelta(seconds=MINE_KILL_DURATION_SECONDS) for c in nearby_cursors: c.revive_at = revive_at diff --git a/tools/deploy.sh b/tools/deploy.sh index ff5a3ef..d8f0f17 100644 --- a/tools/deploy.sh +++ b/tools/deploy.sh @@ -14,20 +14,10 @@ then sudo apt install -y docker-ce fi -# # docker-compose가 없다면 docker-compose 설치 -# if ! type docker-compose > /dev/null -# then -# echo "docker-compose does not exist" -# echo "Start installing docker-compose" -# sudo curl -L "https://github.com/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose -# sudo chmod +x /usr/local/bin/docker-compose -# fi - - -# echo "start docker-compose up: ubuntu" -# sudo docker-compose -f /home/ubuntu/srv/ubuntu/docker-compose.yaml down +IMAGE_NAME="dojini/minesweeper:latest" CONTAINER_NAME="minesweeper" +ENV_FILE_PATH=".env" # 컨테이너가 존재하는지 확인 if sudo docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$" @@ -35,11 +25,9 @@ then sudo docker rm -f $CONTAINER_NAME fi -IMAGE_NAME="dojini/minesweeper:latest" - if sudo docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^${IMAGE_NAME}$" then sudo docker rmi $IMAGE_NAME fi -sudo docker run -it -d -p 80:8000 --name $CONTAINER_NAME $IMAGE_NAME +sudo docker run -it -d -p 80:8000 --env-file $ENV_FILE_PATH --name $CONTAINER_NAME $IMAGE_NAME From 2375ba121d6a8ae9f2c647555f2c55728a646a2f Mon Sep 17 00:00:00 2001 From: onee-only Date: Thu, 26 Dec 2024 07:27:14 +0000 Subject: [PATCH 7/7] =?UTF-8?q?requirements.txt=EC=97=90=20lmdb=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/requirements.txt b/requirements.txt index 113af72..b9858f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,15 +4,20 @@ certifi==2024.8.30 click==8.1.7 colorama==0.4.6 fastapi==0.115.5 +gitdb==4.0.11 +GitPython==3.1.41 h11==0.14.0 httpcore==1.0.7 httptools==0.6.4 httpx==0.27.2 idna==3.10 +lmdb==1.5.1 pydantic==2.9.2 pydantic_core==2.23.4 python-dotenv==1.0.1 PyYAML==6.0.2 +setuptools==69.0.3 +smmap==5.0.1 sniffio==1.3.1 starlette==0.41.2 typing_extensions==4.12.2