Skip to content
Open
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
Empty file added algorithm/__init__.py
Empty file.
19 changes: 12 additions & 7 deletions algorithm/feature_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
from numpy.typing import NDArray
from dataclasses import dataclass
from game.board import compute_prefix_sum
from game.action import Action
from game.board import Board


@dataclass
class FeatureContext:
board: NDArray[np.int8] # 숫자 λ°°μ—΄μ˜ μ’Œν‘―κ°’
prefix: NDArray # λˆ„μ ν•©
count_by_value: dict[int, int] #μˆ«μžλ³„ λ‚¨μ•„μžˆλŠ” 개수
board: NDArray[np.int8]
prefix: NDArray
count_by_value: dict[int, int]
valid_actions: list[Action]

@classmethod
def from_board(cls, board: NDArray[np.int8]) -> "FeatureContext":
prefix = compute_prefix_sum(board)
counts = {v: int((board == v).sum()) for v in range(1, 10)}
return cls(board=board, prefix = prefix, count_by_value = counts)
def from_board(cls, board_obj: Board) -> "FeatureContext":
grid = board_obj.grid # Board 객체 μ•ˆμ˜ μ‹€μ œ λ°°μ—΄
prefix = compute_prefix_sum(grid) #λˆ„μ ν•©
counts = {v: int((grid == v).sum()) for v in range(1, 10)}
valid_actions = board_obj.get_valid_actions()
return cls(board=grid, prefix=prefix, count_by_value=counts, valid_actions=valid_actions)



Expand Down
125 changes: 122 additions & 3 deletions algorithm/features.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,127 @@
from .feature_context import feature_context
from .feature_context import FeatureContext
from .math_algorithm import sigmoid
from game.action import get_cleared_cells

def feature_remove_nine(ctx: feature_context) -> float:
return

#숫자 편ν–₯ features ex) μ§€μšΈ 수 μžˆλŠ” 'n'μ΄λΌλŠ” μˆ«μžκ°€ λͺ‡ 개 λ‚¨μ•˜λŠ”κ°€ 점수λ₯Ό 올림으둜써 μ§€μš°κ²Œλ” μœ λ„
def feature_remove_nine(ctx: FeatureContext) -> float:
area = ctx.board
if not(area == 9).any(): #격자 μ•ˆμ— 9κ°€ μ—†μ„μ‹œ 0을 returnν•œλ‹€
return 0.0
nine_count = int((area==9).sum()) #areaμ•ˆμ— 9κ°€ μžˆλŠ” 수의 개수
if not _has_nine_one_pair(ctx): #9와 짝 μ§€μ–΄μ§€λŠ” 경우의 μˆ˜κ°€ 판 내에 μ‘΄μž¬ν•˜λŠ”μ§€
return 0.0
return sigmoid(nine_count, k = 0.5, x0 = 3.0) #μΆ”ν›„ 이 값을 μ‘°μ •ν•˜λ©΄μ„œ k κ°’κ³Ό x0 값을 찾아도 됨

def feature_remove_eight(ctx: FeatureContext) -> float:
area = ctx.board
if not(area == 8).any(): #격자 μ•ˆμ— 9κ°€ μ—†μ„μ‹œ 0을 returnν•œλ‹€
return 0.0
eight_count = int((area==8).sum()) #areaμ•ˆμ— 9κ°€ μžˆλŠ” 수의 개수
if not _has_eight_pair(ctx): #9와 짝 μ§€μ–΄μ§€λŠ” 경우의 μˆ˜κ°€ 판 내에 μ‘΄μž¬ν•˜λŠ”μ§€
return 0.0
return sigmoid(eight_count, k = 0.5, x0 = 3.0) #μΆ”ν›„ 이 값을 μ‘°μ •ν•˜λ©΄μ„œ k κ°’κ³Ό x0 값을 찾아도 됨


def feature_remove_seven(ctx: FeatureContext) -> float:
area = ctx.board
if not(area == 7).any(): #격자 μ•ˆμ— 9κ°€ μ—†μ„μ‹œ 0을 returnν•œλ‹€
return 0.0
seven_count = int((area==7).sum()) #areaμ•ˆμ— 9κ°€ μžˆλŠ” 수의 개수
if not _has_seven_pair(ctx): #9와 짝 μ§€μ–΄μ§€λŠ” 경우의 μˆ˜κ°€ 판 내에 μ‘΄μž¬ν•˜λŠ”μ§€
return 0.0
return sigmoid(seven_count, k = 0.5, x0 = 3.0) #μΆ”ν›„ 이 값을 μ‘°μ •ν•˜λ©΄μ„œ k κ°’κ³Ό x0 값을 찾아도 됨

def feature_remove_six(ctx: FeatureContext) -> float:
area = ctx.board
if not(area == 6).any(): #격자 μ•ˆμ— 9κ°€ μ—†μ„μ‹œ 0을 returnν•œλ‹€
return 0.0
six_count = int((area==6).sum()) #areaμ•ˆμ— 9κ°€ μžˆλŠ” 수의 개수
if not _has_six_pair(ctx): #9와 짝 μ§€μ–΄μ§€λŠ” 경우의 μˆ˜κ°€ 판 내에 μ‘΄μž¬ν•˜λŠ”μ§€
return 0.0
return sigmoid(six_count, k = 0.5, x0 = 3.0) #μΆ”ν›„ 이 값을 μ‘°μ •ν•˜λ©΄μ„œ k κ°’κ³Ό x0 값을 찾아도 됨

def feature_remove_five(ctx: FeatureContext) -> float:
area = ctx.board
if not(area == 5).any(): #격자 μ•ˆμ— 9κ°€ μ—†μ„μ‹œ 0을 returnν•œλ‹€
return 0.0
five_count = int((area==5).sum()) #areaμ•ˆμ— 9κ°€ μžˆλŠ” 수의 개수
if not _has_five_pair(ctx): #9와 짝 μ§€μ–΄μ§€λŠ” 경우의 μˆ˜κ°€ 판 내에 μ‘΄μž¬ν•˜λŠ”μ§€
return 0.0
return sigmoid(five_count, k = 0.5, x0 = 3.0) #μΆ”ν›„ 이 값을 μ‘°μ •ν•˜λ©΄μ„œ k κ°’κ³Ό x0 값을 찾아도 됨

def feature_edge_bias(board, action):
"""action으둜 μ§€μ›Œμ§€λŠ” 셀듀이 κ°€μž₯μžλ¦¬μ— κ°€κΉŒμšΈμˆ˜λ‘ 높은 κ°’ (0~1)"""
cells = get_cleared_cells(board, action)
rows, cols = board.rows, board.cols
scores = []
for r, c in cells:
dist_to_edge = min(r, rows - 1 - r, c, cols - 1 - c)
max_dist = min(rows, cols) // 2
scores.append(1 - dist_to_edge / max_dist)
return sum(scores) / len(scores)


def feature_center_bias(board, action):
"""action으둜 μ§€μ›Œμ§€λŠ” 셀듀이 쀑앙에 κ°€κΉŒμšΈμˆ˜λ‘ 높은 κ°’ (0~1)"""
cells = get_cleared_cells(board, action)
rows, cols = board.rows, board.cols
center_r, center_c = (rows - 1) / 2, (cols - 1) / 2
max_dist = (center_r ** 2 + center_c ** 2) ** 0.5
scores = []
for r, c in cells:
dist_to_center = ((r - center_r) ** 2 + (c - center_c) ** 2) ** 0.5
scores.append(1 - dist_to_center / max_dist)
return sum(scores) / len(scores)

#feature_remove seriesλ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•œ 보쑰 ν•¨μˆ˜
#9와 짝이 λ˜λŠ” valid action이 μžˆλŠ”μ§€ μ—¬λΆ€ 확인
def _has_nine_one_pair(ctx : FeatureContext) -> bool:

for action in ctx.valid_actions:
r1, c1 = action.top_left
r2, c2 = action.bottom_right
region = ctx.board[r1:r2+1, c1:c2+1]
if (region == 9).any():
return True
return False
def _has_eight_pair(ctx : FeatureContext) -> bool:

for action in ctx.valid_actions:
r1, c1 = action.top_left
r2, c2 = action.bottom_right
region = ctx.board[r1:r2+1, c1:c2+1]
if (region == 8).any():
return True
return False
def _has_seven_pair(ctx : FeatureContext) -> bool:

for action in ctx.valid_actions:
r1, c1 = action.top_left
r2, c2 = action.bottom_right
region = ctx.board[r1:r2+1, c1:c2+1]
if (region == 7).any():
return True
return False
def _has_six_pair(ctx : FeatureContext) -> bool:

for action in ctx.valid_actions:
r1, c1 = action.top_left
r2, c2 = action.bottom_right
region = ctx.board[r1:r2+1, c1:c2+1]
if (region == 6).any():
return True
return False
def _has_five_pair(ctx : FeatureContext) -> bool:

for action in ctx.valid_actions:
r1, c1 = action.top_left
r2, c2 = action.bottom_right
region = ctx.board[r1:r2+1, c1:c2+1]
if (region == 5).any():
return True
return False




4 changes: 4 additions & 0 deletions algorithm/math_algorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import math

def sigmoid(x: float, k: float = 0.5, x0: float = 3.0) -> float:
return 1 / (1 + math.exp(-k * (x - x0)))
2 changes: 1 addition & 1 deletion algorithm/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def play_game(self, board: Board) -> GameResult:
break

actions = board.get_valid_actions()
best_action = pick_best_action(actions, board.grid, self.weights)
best_action = pick_best_action(actions, board.grid, self.weights)

area = self.get_area(board.grid, best_action)
cleared = int((area != 0).sum()) # μ΄λ²ˆμ— μ§€μš΄ μΉΈ 수
Expand Down
10 changes: 9 additions & 1 deletion game/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@
class Action:
# (row, col)
top_left: tuple[int, int]
bottom_right: tuple[int, int]
bottom_right: tuple[int, int]
def get_cleared_cells(self, board) -> list[tuple[int, int]]:
"""이 action으둜 μ§€μ›Œμ§€λŠ”(0이 μ•„λ‹Œ) μΉΈλ“€μ˜ μ’Œν‘œ 리슀트 λ°˜ν™˜"""
(r1, c1), (r2, c2) = self.top_left, self.bottom_right
area = board[r1:r2+1, c1:c2+1]
return [(r1 + i, c1 + j)
for i in range(area.shape[0])
for j in range(area.shape[1])
if area[i, j] != 0]