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

배포 #81

Merged
merged 35 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b74c518
set-view-size 리시버 cursors 이벤트 발행 조건 추가
onee-only Dec 12, 2024
af25260
Merge pull request #71 from gamultong/enhancement/set-view-size-cursors
onee-only Dec 12, 2024
92574b2
exists_range, view_includes 리팩 및 exclude_range 추가
onee-only Dec 12, 2024
a95ae23
set-view-size 리시버 exclude_range 사용
onee-only Dec 12, 2024
10e45cd
연결 종료 에러처리
onee-only Dec 13, 2024
bb61664
0, 0 타일 덮어씌워지는 버그 수정
onee-only Dec 13, 2024
ece1fb2
리시버 로직 원자적으로 동작하도록 변경
onee-only Dec 16, 2024
f5ca73f
Merge pull request #72 from gamultong/feature/exists_range_exclude_range
onee-only Dec 16, 2024
1c8a6ec
Merge pull request #73 from gamultong/feature/conn-close-exception-ha…
onee-only Dec 16, 2024
64379bb
Merge pull request #74 from gamultong/enhancement/fix-0-0-overwrite-bug
onee-only Dec 16, 2024
7171853
Merge pull request #75 from gamultong/enhancement/async-race-condition
onee-only Dec 16, 2024
baf8714
asyncio.gather 적용
onee-only Dec 17, 2024
ba10aec
열린 지뢰 타일을 새로운 섹션 생성할 때 적용할 수 있도록 수정
onee-only Dec 17, 2024
967daa8
Cursor.create에 width, height, position 전달
onee-only Dec 18, 2024
318da9d
new-cursor-candidate 이벤트 추가
onee-only Dec 18, 2024
6fbcb06
cursor 기존 new-conn 리시버 new-cursor-candidate 리시버로 변경
onee-only Dec 18, 2024
d31e4e9
랜덤 열린 타일 위치 가져오기 구현
onee-only Dec 18, 2024
89bba81
board handler new-conn 리시버 수정
onee-only Dec 18, 2024
673869c
flaky 테스트 마킹
onee-only Dec 18, 2024
f8a966d
BoardHandler.fetch 버그 수정
onee-only Dec 18, 2024
4713a0d
섹션 탐색 x, y 구분
onee-only Dec 19, 2024
1991124
인터랙션 후처리 이벤트 수정
onee-only Dec 19, 2024
b37c6f7
커서 핸들러 single tile opened 이벤트 리시버 추가
onee-only Dec 19, 2024
062bd31
커서 핸들러 flag-set 이벤트 리시버 추가
onee-only Dec 19, 2024
3479cde
tiles-opened 이벤트 리시버 추가 및 view_includes_range 추가
onee-only Dec 19, 2024
edd9f82
board api 새로 구현
onee-only Dec 19, 2024
5fc5ba5
board handler에 새로운 이벤트 적용
onee-only Dec 19, 2024
f2c83f2
연쇄 개방 시 섹션 데이터 일관성을 위해 주변 섹션 생성
onee-only Dec 19, 2024
7d7b4bf
Merge pull request #76 from gamultong/enhancement/apply-asyncio-gather
onee-only Dec 20, 2024
0bf3e4e
open_tiles_cascase 주석 추가
onee-only Dec 20, 2024
6e7f8a6
conn.send시 ConnectionClosed 예외처리
onee-only Dec 20, 2024
1abdf8d
Merge pull request #77 from gamultong/enhancement/apply-open-mine-new…
onee-only Dec 22, 2024
f1694b6
Merge pull request #79 from gamultong/feature/cascading-tile-open
onee-only Dec 22, 2024
2bc14db
Merge pull request #80 from gamultong/enhancement/send-conn-closed
onee-only Dec 22, 2024
1a9c1d0
Merge pull request #78 from gamultong/feature/random-spawn-location
onee-only Dec 22, 2024
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
317 changes: 290 additions & 27 deletions board/data/handler/internal/board.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import random
from board.data import Point, Section, Tile, Tiles
from cursor.data import Color


def init_first_section() -> dict[int, dict[int, Section]]:
Expand All @@ -18,65 +20,278 @@ 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

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

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

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)

@staticmethod
def update_tile(p: Point, tile: Tile):
tiles = Tiles(data=bytearray([tile.data]))
def open_tile(p: Point) -> Tile:
section, inner_p = BoardHandler._get_section_from_abs_point(p)

tiles = section.fetch(inner_p)

tile = Tile.from_int(tiles.data[0])
tile.is_open = True

tiles.data[0] = tile.data

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

return tile

@staticmethod
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,
y=p.y // Section.LENGTH
)

section = None
for sec in sections:
# 이미 가지고 있으면 반환
if sec.p == sec_p:
section = sec
break

# 새로 가져오기
if section is None:
section = fetch_section(sec_p)
sections.append(section)

inner_p = Point(
x=p.x - section.abs_x,
y=p.y - section.abs_y
)

return section, inner_p

queue = []
queue.append(p)

visited = set()
visited.add((p.x, p.y))

# 추후 fetch 범위
min_x, min_y = p.x, p.y
max_x, max_y = p.x, p.y

while len(queue) > 0:
p = queue.pop(0)

# 범위 업데이트
min_x, min_y = min(min_x, p.x), min(min_y, p.y)
max_x, max_y = max(max_x, p.x), max(max_y, p.y)

sec, inner_p = get_section(p)

# TODO: section.fetch_one(point) 같은거 만들어야 할 듯
tile = Tile.from_int(sec.fetch(inner_p).data[0])

# 타일 열어주기
tile.is_open = True
tile.is_flag = False
tile.color = None

sec.update(Tiles(data=bytearray([tile.data])), inner_p)

if tile.number is not None:
# 빈 타일 주변 number까지만 열어야 함.
continue

# (x, y) 순서
delta = [
(0, 1), (0, -1), (-1, 0), (1, 0), # 상하좌우
(-1, 1), (1, 1), (-1, -1), (1, -1), # 좌상 우상 좌하 우하
]

# 큐에 추가될 포인트 리스트
temp_list = []

for dx, dy in delta:
np = Point(x=p.x+dx, y=p.y+dy)

if (np.x, np.y) in visited:
continue
visited.add((np.x, np.y))

sec, inner_p = get_section(np)

nearby_tile = Tile.from_int(sec.fetch(inner_p).data[0])
if nearby_tile.is_open:
# 이미 연 타일, 혹은 이전에 존재하던 열린 number 타일
continue

temp_list.append(np)

queue.extend(temp_list)

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

start_p = Point(min_x, max_y)
end_p = Point(max_x, min_y)
tiles = BoardHandler.fetch(start_p, end_p)

return start_p, end_p, tiles

@staticmethod
def set_flag_state(p: Point, state: bool, color: Color | None = None) -> Tile:
section, inner_p = BoardHandler._get_section_from_abs_point(p)

tiles = section.fetch(inner_p)

tile = Tile.from_int(tiles.data[0])
tile.is_flag = state
tile.color = color

sec_p = Point(x=p.x // Section.LENGTH, y=p.y // Section.LENGTH)
section = BoardHandler.sections[sec_p.y][sec_p.x]
tiles.data[0] = tile.data

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

return tile

def _get_section_from_abs_point(abs_p: Point) -> tuple[Section, Point]:
"""
절대 좌표 abs_p를 포함하는 섹션, 그리고 abs_p의 섹션 내부 좌표를 반환한다.
"""
sec_p = Point(
x=abs_p.x // Section.LENGTH,
y=abs_p.y // Section.LENGTH
)

section = BoardHandler._get_or_create_section(sec_p.x, sec_p.y)

inner_p = Point(
x=p.x - section.abs_x,
y=p.y - section.abs_y
x=abs_p.x - section.abs_x,
y=abs_p.y - section.abs_y
)

section.update(data=tiles, start=inner_p)
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:
"""
전체 맵에서 랜덤한 열린 타일 위치를 하나 찾는다.
섹션이 하나 이상 존재해야한다.
"""
# 이미 방문한 섹션들
visited = set()

# 지금은 안 해도 되긴 할텐데 일단 해 놓기
BoardHandler.sections[sec_p.y][sec_p.x] = section
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])
)

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

# 섹션 내부의 랜덤한 열린 타일 위치를 찾는다.
inner_point = randomly_find_open_tile(chosen_section)
if inner_point is None:
continue

open_point = Point(
x=chosen_section.abs_x + inner_point.x,
y=chosen_section.abs_y + inner_point.y
)

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)
Expand Down Expand Up @@ -110,3 +325,51 @@ def _get_or_create_section(x: int, y: int) -> Section:
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 randomly_find_open_tile(section: Section) -> Point | None:
"""
섹션 안에서 랜덤한 열린 타일 위치를 찾는다.
시작 위치, 순회 방향의 순서를 무작위로 잡아 탐색한다.
만약 열린 타일이 존재하지 않는다면 None.
"""

# (증감값, 한계값)
directions = [
(1, Section.LENGTH - 1), (-1, 0) # 순방향, 역방향
]
random.shuffle(directions)

x_start = random.randint(0, Section.LENGTH - 1)
y_start = random.randint(0, Section.LENGTH - 1)

pointers = [0, 0] # first, second
start_values = [0, 0]

x_first = random.choice([True, False])
x_pointer = 0 if x_first else 1
y_pointer = 1 if x_first else 0

start_values[x_pointer] = x_start
start_values[y_pointer] = y_start

# second 양방향 탐색
for num, limit in directions:
for second in range(start_values[1], limit + num, num):
pointers[1] = second

# first 양방향 탐색
for num, limit in directions:
for first in range(start_values[0], limit + num, num):
pointers[0] = first

x = pointers[x_pointer]
y = pointers[y_pointer]

idx = y * Section.LENGTH + x

tile = Tile.from_int(section.data[idx])
if tile.is_open:
# 좌표계에 맞게 y 반전
y = Section.LENGTH - y - 1
return Point(x, y)
Loading
Loading