Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

디스크에 보드 올리기 #86

Merged
merged 7 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading