지식iN 답변을 하다가 잠깐 만들어 본 가위바위보 게임입니다. 기획이라고 할 만한 건 없고, 유저폼에 버튼 몇 개와 레이블 두 개만 올려서 돌아가게 했습니다.
폼 구성은 이렇습니다. CommandButton1 이 새 게임 시작, CommandButton2~4 가 각각 가위·바위·보 버튼, Label1 에 승패, Label2 에 컴퓨터가 낸 것을 표시하고, 폼 제목줄(Caption)에 몇 판째인지와 승률이 나옵니다.
Option Explicit
Private Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long
Private Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, ByVal nIDEvent As Long) As Long
Const TIMER_INTERVAL As Long = 100
Const TIMER_ID As Long = 3100
Private rndValues As Variant
Private userValue As Integer
Private comIndex As Integer
Private myValue As Integer
Private Sub tmMethod()
'INTERVAL = 100 이므로 10회 당 약 1초
'// 타이머의 정확도는 INTERVAL과 tmMethod 진입 횟수로 조절
End Sub
Private Sub okMethod()
Stop_Timer
Dim comValue As Integer
Dim ckValue As Integer
comIndex = comIndex + 1
comValue = rndValues(comIndex)
Select Case userValue
Case 1
Select Case comValue
Case 1: ckValue = 1
Case 2: ckValue = 3
Case 3: ckValue = 2
End Select
Case 2
Select Case comValue
Case 1: ckValue = 2
Case 2: ckValue = 1
Case 3: ckValue = 3
End Select
Case 3
Select Case comValue
Case 1: ckValue = 3
Case 2: ckValue = 2
Case 3: ckValue = 1
End Select
End Select
If ckValue = 2 Then myValue = myValue + 1
Label1.Caption = Choose(ckValue, "무", "승", "패")
Label2.Caption = Choose(comValue, "가위", "바위", "보")
Caption = comIndex & "회 승률 " & Format((myValue / comIndex) * 100, "0.00""%""")
cmdEnabled
End Sub
Private Sub CommandButton1_Click()
ReDim rndValues(1 To 45)
Dim i As Integer, n As Integer, x As Integer
For i = 1 To 3
For n = 1 To 15
x = x + 1
rndValues(x) = i
Next
Next
rndValues = RandData(rndValues): rndValues = RandData(rndValues)
comIndex = 0: myValue = 0: cmdEnabled
' Start_Timer '// 사용자의 입력을 체크할려면 타이머를 이용
End Sub
Private Sub cmdEnabled()
CommandButton2.Enabled = comIndex < 45
CommandButton3.Enabled = comIndex < 45
CommandButton4.Enabled = comIndex < 45
End Sub
Function RandData(ByVal dv As Variant) As Variant
Dim j As Long, t As Variant, i As Long
For i = UBound(dv) To LBound(dv) Step -1
j = Application.RandBetween(LBound(dv), UBound(dv))
t = dv(i): dv(i) = dv(j): dv(j) = t
Next
RandData = dv
End Function
Private Sub CommandButton2_Click(): userValue = 1: okMethod: End Sub
Private Sub CommandButton3_Click(): userValue = 2: okMethod: End Sub
Private Sub CommandButton4_Click(): userValue = 3: okMethod: End Sub
Sub Start_Timer()
Stop_Timer
SetTimer Application.hWnd, TIMER_ID, TIMER_INTERVAL, AddressOf tmMethod
End Sub
Sub Stop_Timer()
KillTimer Application.hWnd, TIMER_ID
Application.StatusBar = False
End Sub
컴퓨터 패를 미리 섞어 두는 이유
컴퓨터가 낼 것을 판마다 Rnd 로 뽑지 않고, 시작 버튼을 누를 때 45칸 배열에 가위(1)·바위(2)·보(3)를 15개씩 채운 뒤 섞어 둡니다. 이렇게 하면 한 게임 안에서 컴퓨터가 셋을 정확히 같은 횟수만큼 내기 때문에, 어쩌다 한쪽으로 몰려서 승률이 비정상적으로 튀는 일이 없습니다. 45판이 끝나면 cmdEnabled 가 세 버튼을 비활성화해서 게임이 끝납니다.
섞는 건 RandData 함수입니다. 배열 끝에서부터 내려오면서 임의 위치의 값과 자리를 바꾸는데, 임의 위치를 배열 전체 범위에서 고르기 때문에 엄밀한 피셔-예이츠 방식과는 조금 다릅니다. 그래서 두 번 연달아 호출해 섞임을 보강해 두었습니다. 정석대로 하려면 j 를 LBound(dv) 부터 i 사이에서 고르면 한 번으로 충분합니다.
승패 판정
okMethod 는 사용자가 버튼을 누를 때마다 다음 컴퓨터 패를 하나 꺼내서 비교합니다. 결과 코드는 1이 무승부, 2가 승리, 3이 패배이고 Choose 로 바로 글자로 바꿔 표시합니다. 이중 Select Case 가 길어 보이지만 3×3 표를 그대로 옮긴 것이라 읽기는 쉽습니다. 수식으로 줄이고 싶다면 (userValue - comValue + 3) Mod 3 이 0이면 무승부, 1이면 승리, 2면 패배가 되는 성질을 쓰면 됩니다.
승률은 이긴 판 수를 지금까지 한 판 수로 나눠 Format(..., "0.00""%""") 로 찍습니다. 형식 문자열 안의 ""%"" 는 퍼센트 기호를 글자 그대로 붙이는 것이라 값에 100을 곱해 줍니다.
타이머 부분과 주의점
SetTimer·KillTimer 선언과 tmMethod 는 "몇 초 안에 안 누르면 패배" 같은 제한 시간 기능을 넣으려고 자리만 잡아 둔 것이고, 실제 호출은 주석 처리돼 있습니다. 쓰려면 두 가지를 알아 둬야 합니다. 첫째, AddressOf 는 표준 모듈에 있는 프로시저만 가리킬 수 있어서 tmMethod 를 유저폼 모듈에 두면 컴파일 오류가 납니다. 타이머 콜백은 표준 모듈로 옮기세요. 둘째, 64비트 오피스에서는 Declare 문에 PtrSafe 를 붙이고 핸들 인자를 LongPtr 로 바꿔야 합니다. 타이머가 도는 중에 폼을 닫거나 코드를 중지하면 엑셀이 멈출 수 있으니, 폼의 QueryClose 이벤트에서 Stop_Timer 를 꼭 불러 주는 게 좋습니다.