Skip to content

Commit

Permalink
Merge pull request #86 from gamultong/feature/board-on-disk
Browse files Browse the repository at this point in the history
디스크에 보드 올리기
  • Loading branch information
onee-only authored Dec 27, 2024
2 parents 38db097 + 2375ba1 commit f79fccc
Show file tree
Hide file tree
Showing 19 changed files with 289 additions and 181 deletions.
2 changes: 2 additions & 0 deletions .dev.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
MINE_KILL_DURATION_SECONDS=60
BOARD_DATABASE_PATH="/tmp/gamulpung-board-db"
182 changes: 76 additions & 106 deletions board/data/handler/internal/board.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,62 @@
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.set(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:
# 반환할 데이터 공간 미리 할당
out_width, out_height = (end.x - start.x + 1), (start.y - end.y + 1)
out = bytearray(out_width * out_height)

# TODO: 새로운 섹션과의 관계로 경계값이 바뀔 수 있음.
# 이를 fetch 결과에 적용시킬 수 있도록 미리 다 만들어놓고 fetch를 시작해야 함.
# 현재는 섹션이 메모리 내부 레퍼런스로 저장되기 때문에 이렇게 미리 받아놓고 할 수 있음.
# 나중에는 다시 섹션을 가져와야 함.
sections = []
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)
section = BoardHandler._get_or_create_section(Point(sec_x, sec_y))

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
)
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)

Expand All @@ -81,7 +72,7 @@ def open_tile(p: Point) -> Tile:
tiles.data[0] = tile.data

section.update(data=tiles, start=inner_p)
BoardHandler._save_section(section)
SectionStorage.set(section)

return tile

Expand All @@ -95,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.x, new_p.y)

new_section = BoardHandler._get_or_create_section(sec_p.x, sec_p.y)
return new_section

def get_section(p: Point) -> tuple[Section, Point]:
sec_p = Point(
x=p.x // Section.LENGTH,
Expand All @@ -123,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(
Expand Down Expand Up @@ -195,7 +173,7 @@ def get_section(p: Point) -> tuple[Section, Point]:

# 섹션 변경사항 모두 저장
for section in sections:
BoardHandler._save_section(section)
SectionStorage.set(section)

start_p = Point(min_x, max_y)
end_p = Point(max_x, min_y)
Expand All @@ -216,7 +194,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.set(section)

return tile

Expand All @@ -229,7 +207,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,
Expand All @@ -238,9 +216,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:
"""
Expand All @@ -250,23 +225,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)
Expand All @@ -281,50 +247,54 @@ 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)
def _get_or_create_section(p: Point) -> Section:
"""
p에 해당하는 섹션을 가져온다. 만약 섹션이 존재하지 않으면 새로 만든다.
완전한(주변 섹션과의 관계가 모두 적용된) 섹션을 반환한다.
"""
section = BoardHandler._get_section_or_none(p)

BoardHandler.sections[y] = {}
is_complete_section = True

if x not in BoardHandler.sections[y]:
BoardHandler.max_x = max(BoardHandler.max_x, x)
BoardHandler.min_x = min(BoardHandler.min_x, x)
if section is None:
is_complete_section = False
section = Section.create(p)

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), # 좌상 우상 좌하 우하
]

# (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 not None:
continue

# 주변 섹션과 새로운 섹션의 인접 타일을 서로 적용시킨다.
for dx, dy in delta:
nx, ny = x+dx, y+dy
neighbor = BoardHandler._get_section_or_none(nx, ny)
# 주변 섹션이 없을 수 있음.
if neighbor is None:
continue
# 주변 섹션이 존재하지 않으면 새로 생성 및 section에 적용.
neighbor = Section.create(np)
is_complete_section = False

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:
section.apply_neighbor_diagonal(neighbor)
elif dx != 0:
section.apply_neighbor_horizontal(neighbor)
elif dy != 0:
section.apply_neighbor_vertical(neighbor)

BoardHandler.sections[ny][nx] = neighbor
SectionStorage.set(neighbor)

BoardHandler.sections[y][x] = new_section
# 모서리 적용이 안 되어 있었다면 적용된 버전의 섹션을 저장
if not is_complete_section:
SectionStorage.set(section)

return BoardHandler.sections[y][x]
return 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:
Expand Down
21 changes: 16 additions & 5 deletions board/data/handler/test/board_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = \
[
Expand Down Expand Up @@ -37,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"]

Expand Down Expand Up @@ -70,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))
Expand Down Expand Up @@ -128,8 +138,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.set(sec)

for _ in range(10):
point = BoardHandler.get_random_open_position()
Expand Down
51 changes: 0 additions & 51 deletions board/data/handler/test/fixtures.py

This file was deleted.

Loading

0 comments on commit f79fccc

Please sign in to comment.