메이플스토리 인벤

메이플스토리 인벤

로그인하고
출석보상 받으세요!

 

헥사스텟 돌깍 최적화 + 기댓값 계산기

안녕하세요. 이번에 헥사스탯 최적 강화 전략 탐색기를 만들고, 기댓값을 계산해서 이를 공유하고 검증하기 위해 친구 아이디를 빌려서 글을 올립니다.

기댓값만 궁금하신분들은 2번만 보시면되고
3번은 계산 코드에 관한 내용입니다.

1. 헥사스탯 강화란 무엇인가 + 강화전략 최적화란?
우선 헥사스탯 강화에 대해서는 https://namu.wiki/w/HEXA%20%EB%A7%A4%ED%8A%B8%EB%A6%AD%EC%8A%A4#s-2.2 을 참조하시기 바랍니다.
헥사스탯 강화를 간단히 설명 드리겠습니다.
헥사스탯은 메인스탯 하나와 부가스탯 2개로 이루어집니다. 각각은 최대 10레벨까지 강화 될 수 있습니다.
총 20번의 강화가 진행되고 각각의 강화마다 메인스탯과 부가스탯 2개 중 하나가 랜덤으로 강화됩니다.
메인스탯이 부가스탯보다 많이 오르기 때문에 메인스탯 10레벨을 찍는 것이 가장 좋습니다.
10등급 이상부터는(총 강화 횟수가 10번 이상) 1000만 메소를 내고 스탯을 초기화 할 수 있습니다.
제가 한 최적화는 어떠한 경우에 초기화를 해야지 가장 좋은지 계산한 것입니다.
예를 들어 10번 강화했는데 메인스탯에 한 번도 붙지 않으면 초기화를 진행합니다.

2. 그래서 기댓값이 얼마 나오는데? + 어떻게 강화해야 메소 가장 적게 쓰는데?
옆의 레벨은 목표로 하는 메인스탯의 레벨을 의미합니다.
최적화X는 목표 스탯에 도달할 가능성이 존재만 한다면 초기화 하지 않는 전략을 사용한 경우입니다.
(ex : 목표스탯이 10이고 15강했는데 4강 붙었으면 나머지 다 붙어도 10이 될 수 없으므로 초기화)
최적화O는 최적 초기화 방법을 사용한 경우입니다.
(조각 0메소일 때 표가 잘못되어있는데 이 경우 기댓값은 초기화 횟수*0.1 억입니다.)
초기화 비용에 비해 조각의 가격이 너무 비싸기에 최적화가 조각 소모량만 최소화 하는 방향으로 진행되어 조각이 100만, 300만, 1000만 메소인 경우 모두 동일한 조각 개수와 초기화 회수를 가지는 것을 볼 수 있습니다.

아래는 목표 메인레벨 스탯별로 메소를 가장 적게쓰는 최적 초기화 전략입니다.

3. 사용한 코드
코드는 python 기반 코드를 사용하였습니다. 주석을 읽어보시면 어떤 방식으로 짰는지 이해하실 수 있으실 겁니다.
혹시 잘못된 점 찾으시면 말씀 부탁드립니다.

##헥사스텟 강화 최적 전략+기댓값 탐색기

import numpy as np
import itertools

# n은 현재 몇 번의 강화가 진행 되었는지를 의미함. 최대 20
# m은 현재 main stat 레벨을 의미함. 최대 10

##### 1. 기본 상수들
#p[m]은 main stat이 m레벨인 상태에서 강화를 했을 때 main stat level에 붙을 확률을 의미함. m은 0부터 10
p=np.array([0.35, 0.35, 0.35, 0.2, 0.2, 0.2, 0.2, 0.15, 0.1, 0.05, 0])
#s[m]은 m번 강화된 상태에서 강화를 했을 때 stat 강화를 위해 필요한 솔에르다 조각의 개수를 의미함. m은 0부터 10
#나무위키에 m=10일 때 값이 안 나와서 일단 임의로 50 넣었음. 추후 수정
s=np.array([10, 10, 10, 20, 20, 20, 20, 30, 40, 50, 50])
#cr은 초기화 비용을 의미함. 단위는 만 메소임. 즉 sr은 천만 메소
cr=1000

##### 2. 입력상수
#cs는 솔에르다조각 하나의 메소가격을 의미함. 단위는 만 메소임.
cs=1000
#o는 목표로하는 main stat 강화 레벨을 의미함. 어차피 5이상은 볼거니까 5부터 10까지 넣으면 됨. 예외처리 귀찮아....
o=10

##### 3 강화 전략 정의
#전략은 다음과 같은 방식으로 결정됨. n번 강화했을 때 main stat 강화수치 m<=r[n]이면 강화를 중단하고 처음부터 다시시작함
#r[n]의 조건들은 아래와 같음.
    #3.1.1 -1<=r[n] // m은 항상 0이상이기때문에 -1보다 작은 r[n]은 전부 똑같음.
    #                  그냥 n번 강화 까지는 절대 포기안할거야 라는 말이 r[n]=-1임. 초기화가 n>=10부터 가능하므로 r[0~10]=-1로 가정함
    #                  아래 3.1.2~3.1.5는 n>=10이상인 경우에만 해당함.
    #3.1.2 r[n]<o // 강화가 목표 수치인 o에 도달하면 초기화할 필요가 없음
    #3.1.3 (o+n-21)<=r[n] // 앞으로 강화횟수가 (20-n)번 남았는데 다붙어도 목표인 o에 도달할 수 없기 때문/ 위 조건과 합쳐져 r[20]=0-1
    #3.1.4 If n1<n2, then r[n1]<=r[n2] // 10번중 5번 붙으면 포기할건데 11번중 4번 붙은건 포기안하면 이상하니까
    #3.1.5 r[n+1]-r[n]=0 or 1 // 10번중 4번 붙은건 포기안하는데 11번중 5번 붙은건 포기할거라는게 말이안됨

#위 조건들을 만족하는 r[n]을 중복없이 모두 고르는 방법은 다음과 같음
    #3.1.7 우선 r[10]값을 고름 이 값을 r10이라고 하자. r10의 범위는 -1<=r10<o 임.
    #3.1.7 10부터19까지의 숫자중 중복없이 순서에 상관없이 o-1-r10개의 숫자를 선택함. 이 숫자들에 해당하는 r[n+1]-r[n]=1로 만들거임.
    #3.1.8 그 숫자를 크기가 작은순으로 N[10],...,N[o+8-r10]라고 가정하자.
    #3.1.9 r[0]~r[N[10]-10]=r10 && r[N[i]+1]~r[N[i+1]]=i+1+r10 for i=10,...,o-3-r10 && r[N[o-2-r10]+1]~r[10]=o-1

# 위에서 말한대로 nN개의 강화 전략이 만들어짐.

N=[]
for r10 in range(-1,o):
    arr=[n for n in range(10,20)]
    Nm=list(itertools.combinations(arr,o-r10-1))
    N.extend(Nm)
nN=len(N)

##무지성 강화 정의: main stat이 o강에 도달할 가능성이 존재만 한다면 무조건 강화하는 것을 의미(결과비교용)
rm=[o-1-20+n if n>=20-o+1 else -1 for n in range(21)]

##### 4 강화 전략별 기댓값 탐색 시작
##c는 강화전략별 소모메소 기댓값을 의미함. 나중에 최적의 강화전략을 찾기 위해 사용. 우선은 초기화
##rn은 강화전략별 초기화 횟수 기댓값을 의미함.
##rn은 강화전략별 솔 에르다 조각의 소모 기댓값을 의미함.
c=np.zeros(nN)
rn=np.zeros(nN)
sn=np.zeros(nN)
for i in range(nN):
    ##4.1 강화 전략 N[i]로 부터 r[n]을 생성 (3.1.9 참조)
    r10=o-len(N[i])-1
    Ni=np.array(N[i])
    r=np.array([-1 for n in range(21)],dtype=int)
    if Ni.shape[0]==0:
        for j in range(10,21):
            r[j]=r10
    else:       
        for j in range(10,Ni[0]+1):
            r[j]=r10
        for j in range(0,o-2-r10):
            for k in range(Ni[j]+1,Ni[j+1]+1):
                r[k]=r10+j+1
        for j in range(Ni[o-2-r10]+1,21):
            r[j]=o-1
            
    ##본 방법이 무지성 강화에 해당하는지 체크
    if list(r)==rm:
        im=i
    
    ##4.2 강화 전략 N[i]로 목표 o를 달성하는데 소모하는 메소의 기댓값 계산
    #cm[n][m]을 현재 n번강화에서 m번 main stat에 붙은상황이고, 여기서 강화를 진행해 목표를 달성할때까지의 소모메소 기댓값이라 하자.
    #이때 n<=10인경우 0<=m<=n이다.
    #그리고 n>=11인경우 max(0,r[n])<=m<=10이다. 강화전략 상 다른 경우는 이미 초기화해버렸기 때문에 존재할 수 없음
    #위에서 왼쪽 등호는 n이 N[i]의 원소일때만 가능 왜냐하면 10개중 5개를 초기화했다면 11개중 5개인 상황은 나올 수 없기 때문.
    ##r2[n]과 r3[n]은 위 조건들을 고려하여 가능한 (n,m)의 조합이 r2[n]<=m<=r3[n]이 되도록 만든 식이다.
    r2=np.zeros(21,dtype=int)
    r3=np.array([np.min([10,n]) for n in range(21)])
    r2[11:21]=r[11:21]+1
    for j in range(o-1-r10):
        r2[Ni[j]+1]-=1

  
    ##4.3 강화 전략 N[i]로 목표 o를 달성하는데 소모하는 메소의 기댓값 계산
    #세가지경우를 생각하자.
    #(n>10 and m=r[n]인 경우) or (n=10 m<r[10])인 경우)(4.3.1)와 (20,m>=o)인 경우(4.3.2)와 n<19이고 r[n]<m<=r3[n]인 경우(4.3.3)
     #4.3.1 cm[n][m]=cr+cm[0][0] /초기화를 하고 다시 (n,m)=(0,0)인 경우로 가기 때문
     #4.3.2 cm[20][m]=0 /목표를 이미 달성했기때문에 기댓값은 0.
     #4.3.3 cm[n][m]=cs*s[m]+cm[n+1][m+1]*p[m]+cm[n+1][m]*(1-p[m]) /설명은 밑 참조. (cm[n+1][11]은 어차피 p[10]=0이므로 고려안함) 
      #첫번째 term은 강화비용
      #두번째 term은 성공하는 경우
      #세번째 term은 실패하는 경우
    #위 연립 방정식을 연립해서 cm[0][0]을 계산하면 됨.
    #위 연립 방정식을 행렬을 이용해서 풀기 위해 reindexing을 진행하겠음. (n,m)을 k로 대응시켜 cm[n][m]을 cmk[k]로 변환
    #ktonm[k]=[n,m],nmtok[n,m]=k, ks는 총 k의 개수
    #4.3.1, 4.3.2, 4.3.3로 이루어진 연립방정식을 cmk=A*cmk+b로 변환.
    nmtok=np.zeros([21,11],dtype=int)
    ktonm=[]
    kindex=0
    for n in range (0,21):
        for m in range(r2[n],r3[n]+1):
            nmtok[n][m]=kindex
            ktonm.append([n,m])
            kindex+=1
    A=np.zeros([kindex,kindex])
    b=np.zeros([kindex])
    
    
    ##4.4초기화 횟수의 기댓값 계산
    #rnm[n][m]을 메소의 기댓값과 비슷한 방식으로 앞으로 초기화할 횟수의 기댓값이라 가정
    #위와 똑같이 세가지경우를 생각하자.
    #(n>10 and m=r[n]인 경우) or (n=10 m<r[10])인 경우)(4.3.1)와 (20,m>=o)인 경우(4.3.2)와 n<19이고 r[n]<m<=r3[n]인 경우(4.3.3)
     #4.4.1 rnm[n][m]=1+rnm[0][0]
     #4.4.2 rnm[20][m]=0 
     #4.4.3 rnm[n][m]=rnm[n+1][m+1]*p[m]+rnm[n+1][m]*(1-p[m])
    #위 연립 방정식을 연립해서 rnm[0][0]을 계산하면 됨. reindexing 똑같이해서 rnmk도 만듬
    #4.4.1, 4.4.2, 4.4.3로 이루어진 연립방정식을 rnmk=A*rnmk+b2로 변환.  (A행렬은 동일함)
    
    b2=np.zeros([kindex])
    
    ##4.5사용 조각 수의
    #snm[n][m]을 메소의 기댓값과 비슷한 방식으로 앞으로 초기화할 횟수의 기댓값이라 가정
    #위와 똑같이 세가지경우를 생각하자.
    #(n>10 and m=r[n]인 경우) or (n=10 m<r[10])인 경우)(4.3.1)와 (20,m>=o)인 경우(4.3.2)와 n<19이고 r[n]<m<=r3[n]인 경우(4.3.3)
     #4.5.1 snm[n][m]=snm[0][0]
     #4.5.2 snm[20][m]=0 
     #4.5.3 snm[n][m]=s[m]+snm[n+1][m+1]*p[m]+snm[n+1][m]*(1-p[m])
    #위 연립 방정식을 연립해서 snm[0][0]을 계산하면 됨. reindexing 똑같이해서 snmk도 만듬
    #4.5.1, 4.5.2, 4.5.3로 이루어진 연립방정식을 snmk=A*snmk+b3로 변환.  (A행렬은 동일함)  
    
    b3=np.zeros([kindex])
    
    ##A와 b, b2, b3를 작성.
    for n in range (0,21):
        
        ## (4.3.1)
        #(n,m=r[n])이 가능한 경우인지 체크하기 위해 존재함 (0,0)도 가능하지만 어차피 초기화 안할거라 상관없음.
        if nmtok[n][r[n]]!=0:
            if n!=10:
                if r[n]!=-1:
                    A[nmtok[n][r[n]]][0]=1
                    b[nmtok[n][r[n]]]=cr
                    b2[nmtok[n][r[n]]]=1
            else:
                for m in range(0,r10+1):
                    A[nmtok[n][m]][0]=1
                    b[nmtok[n][m]]=cr                    
                    b2[nmtok[n][m]]=1  
        
        ## (4.3.2) 변경할 것 없음.
        
        ## (4.3.3)
        if n!=20:
            for m in range(r[n]+1,r3[n]+1):
                if m!=10:
                    A[nmtok[n][m]][nmtok[n+1][m+1]]=p[m]
                A[nmtok[n][m]][nmtok[n+1][m]]=1-p[m]
                b[nmtok[n][m]]=cs*s[m]
                b3[nmtok[n][m]]=s[m]
                
    #cmk=inv(I-A)*b
    inv=np.linalg.inv(np.eye(kindex)-A)
    cmk=inv.dot(b)
    rnmk=inv.dot(b2)
    snmk=inv.dot(b3)
    c[i]=cmk[0]
    rn[i]=rnmk[0]
    sn[i]=snmk[0]
    #계산 진행 확인용.
#    if i%10000==0:
#        print(round((i/nN)*100,1),'% 계산완료')

####5. 계산 결과를 통해 최적 전략 출력
##무지성 강화란 main stat이 o강에 도달할 가능성이 존재만 한다면 무조건 강화하는 것을 의미
print('목표 도달 가능성만 있으면 무지성 강화시',round(c[im]/10000,1),'억메소')
print('목표 도달 가능성만 있으면 무지성 강화시 초기화 횟수', round(rn[im],2))
print('목표 도달 가능성만 있으면 무지성 강화시 조각 갯수', round(sn[im],2))
print('최적전략 사용시',round(c.min()/10000,1),'억메소')
oi=c.argmin()
print('최적전략 사용시 초기화 횟수', round(rn[oi],2))
print('최적전략 사용시 조각 갯수', round(sn[oi],2))
r10=o-len(N[oi])-1
Ni=np.array(N[oi])
r=np.array([-1 for n in range(21)],dtype=int)
if Ni.shape[0]==0:
    for j in range(10,21):
        r[j]=r10
else:       
    for j in range(10,Ni[0]+1):
        r[j]=r10
    for j in range(0,o-2-r10):
        for k in range(Ni[j]+1,Ni[j+1]+1):
            r[k]=r10+j+1
    for j in range(Ni[o-2-r10]+1,21):
        r[j]=o-1
print('최적전략')
if r[10]!=-1:
    print('10 번 강화시 강화 단계가', r[10], '이하면 초기화')
for i in range(Ni.shape[0]):
    print(Ni[i]+1,'번 강화시 강화 단계가',r[Ni[i]+1],'이면 초기화')

4. 한 줄 요약
조각 너무 비싸다. 조각 드랍률 상향 좀.
레벨
Lv15
경험치
1,807 (3%) / 2,001 ( 다음 레벨까지 194 / 마격까지 183 남음 )
포인트

이니 5,468

베니 65

제니 59

명성
237
획득스킬
  • 1
  • 2

SNS 공유하기

댓글

새로고침
새로고침

메이플스토리 인벤 팁과노하우 게시판 게시판

목록 글쓰기
인증글 10추글 즐겨찾기
메이플스토리 인벤 팁과노하우 게시판
번호 제목 글쓴이 등록일 조회 추천
35122 레벨 아이콘 노을녘 07-28 47,419 58
35116 레벨 아이콘 딸기냥이 07-27 178,350 40
35109 레벨 아이콘 은핵 07-26 43,205 27
35108 레벨 아이콘 아쿠아샌들 07-26 40,780 22
35107 레벨 아이콘 Lake123 07-26 25,641 57
35093 레벨 아이콘 레헬릉 07-25 30,282 62
35088 레벨 아이콘 혀로상향점 07-25 62,581 122
35085 레벨 아이콘 뭐너 07-25 16,905 30
35076 레벨 아이콘 오뚜기카레 07-25 119,305 65
35065 레벨 아이콘 서폿이 07-24 87,082 57
35056 레벨 아이콘 내말좀들어줄 07-23 56,431 22
35055 레벨 아이콘 Gapp 07-23 25,222 21
35049 레벨 아이콘 메애애애앵 07-23 52,434 148
35044 레벨 아이콘 나도껴주지 07-22 28,806 52
35034 레벨 아이콘 갓마왕 07-21 67,926 38
35032 레벨 아이콘 info 07-21 40,400 84
35024 레벨 아이콘 Raven0 07-21 67,146 83
35020 레벨 아이콘 루안씌 07-20 47,874 47
35017 레벨 아이콘 구름고래 07-20 39,156 23
35013 레벨 아이콘 권위 07-20 95,353 41
34998 레벨 아이콘 버려지는2분 07-18 65,458 43
34995 레벨 아이콘 새삼 07-18 58,993 35
34992 레벨 아이콘 비올 07-18 36,353 40
34989 레벨 아이콘 마빡도로시 07-18 75,622 97
34987 레벨 아이콘 꿈의한타 07-18 40,031 63
34968 레벨 아이콘 호아킨나썬팬 07-17 36,418 20
34964 레벨 아이콘 김청하 07-17 29,578 28
34959 레벨 아이콘 갈갹각 07-16 24,153 21
34943 레벨 아이콘 나는라라 07-16 71,640 70
34941 레벨 아이콘 겨울파르페 07-16 76,029 75
목록 글쓰기

명칭: 주식회사 인벤 | 등록번호: 경기 아51514 |
등록연월일: 2009. 12. 14 | 제호: 인벤(INVEN)

발행인: 서형준 | 편집인: 강민우 |
발행소: 경기도 성남시 분당구 구미로 9번길 3-4 한국빌딩 3층

발행연월일: 2004 11. 11 |
전화번호: 02 - 6393 - 7700 | E-mail: help@inven.co.kr

인벤의 콘텐츠 및 기사는 저작권법의 보호를 받으므로, 무단 전재, 복사, 배포 등을 금합니다.

Copyrightⓒ Inven. All rights reserved.

인터넷 신문 위원회 배너

2023.08.26 ~ 2026.08.25

인벤 온라인서비스 운영

(웹진, 커뮤니티, 마켓인벤)