一斉にあみだくじ

前回は作成したあみだくじで、選択した任意の参加者を自動でゴールまで辿らせてみた。
infoment.hatenablog.com

今回は、あみだくじの作成から結果の表示までを、自動で一気にやってみる。
f:id:Infoment:20190804203629p:plain

あみだくじで、一人ずつゴールまで辿ると、全ての結果が出るには

一人分の所要時間 × 人数

が必要となる。大人数ともなれば、待ちきれなくなることも多い。

そこで今回は、人数に関係なく凡そ同じぐらいの所要時間で結果が出るよう、一斉にあみだくじをスタートさせてみた。

' あみだくじの梯子部分を作成
Sub MakeLadderLine(target_range As Range)
    ' 指定範囲に縦線を描画。
    Dim BordersIndex As Variant
        For Each BordersIndex In Array(xlEdgeRight, xlInsideVertical)
            target_range.Borders(BordersIndex).LineStyle = xlContinuous
        Next
    
    ' 指定範囲の各セルについて、描画OKの場合に無作為に、
    ' セルの下辺に線を引く。
    Dim r As Range
        For Each r In target_range
            If LineCriteria(target_range, r) Then
                Dim rndIndex As Long
                ' 0か1を無作為に決定。
                rndIndex = WorksheetFunction.RandBetween(0, 1)
                ' 無作為の決定内容に従い、線を引くか引かないかが決定する。
                r.Borders.Item(xlEdgeBottom).LineStyle = (xlContinuous - xlNone) * rndIndex + xlNone
            End If
        Next
End Sub

'線を引くか引かないかの抽選
Function LineCriteria(all_range As Range, target_range As Range) As Boolean
    ' 指定範囲の1列目NG。
    ' ※参加者氏名を線の左側に書く都合上。
    If target_range.Column = all_range(1).Column Then
    ' 指定範囲の1番下辺には線を引かない。
    ' ※ゴールに到達できなくなるため。
    ElseIf all_range(1).Row + all_range.Rows.Count - 1 = target_range.Row Then
    ' セルの上辺にも線が有る場合NG。
    ' ※ある程度線の間隔を確保するため。
    ElseIf target_range.Borders.Item(xlEdgeTop).LineStyle <> xlNone Then
    ' 左隣に線が無ければOK。
    ' ※同じ高さで線が並ぶと、どこで下に降りるか判断できなくなるため。
    ElseIf target_range.Offset(, -1).Borders.Item(xlEdgeBottom).LineStyle = xlNone Then
        LineCriteria = True
    End If
End Function

'参加者全員を一斉スタートさせ、
'同時に一段ずつ下りながらゴールを目指す。
Sub Solution(start_range As Range)
    ' 各参加者の名前がセットされたセル範囲。
    Dim myRng() As Range
    ReDim myRng(1 To start_range.Columns.Count)
    
    ' 各参加者の名前に紐づく色。
    Dim PersonColor() As Long
    ReDim PersonColor(1 To UBound(myRng))
    
    Dim i As Long
        For i = 1 To UBound(myRng)
            Set myRng(i) = start_range(i).Resize(2, 2)
            
            ' 参加者別に、無作為に色をセット。
            PersonColor(i) = WorksheetFunction.RandBetween(150 ^ 3, 255 ^ 3)
            myRng(i).Cells(0, 1).Interior.Color = PersonColor(i)
            
            ' 濃い色がセットされると見難くなるので、少し薄くしておく。
            myRng(i).Cells(0, 1).Interior.TintAndShade = 0.6
        Next i
    Dim counter As Long: counter = 1
        Do Until myRng(1) Is Nothing
        
            ' 一段下げる毎に、上の段の色を消す。
            ' 一行目の名前の塗りつぶしを消さないよう、2回目から消している。
            If counter >= 2 Then
                start_range.Rows(counter - 1).Interior.Color = xlNone
            End If
            
            ' 各参加者について、次に進むべきセル範囲を取得する。
            For i = 1 To UBound(myRng)
                Set myRng(i) = GetNextRange(myRng(i), PersonColor(i))
            Next
            
            counter = counter + 1
            Application.Wait [Now() + "00:00:00.5"]
        Loop
End Sub

' あみだくじの梯子に沿い、引数で与えたRangeから
' 次に進むRangeを取得する。
Function GetNextRange(source_range As Range, source_color As Long) As Range

    '2行2列の左上セルの右側が無ければ、あみだくじを抜けきったことになる。
    With source_range.Item(1)
        If .Borders(xlEdgeRight).LineStyle = xlNone Then
            Set GetNextRange = Nothing
            .Interior.Color = source_color
            .Interior.TintAndShade = 0.6
            Exit Function
        End If
    End With

    ' 道筋が分かるよう、たどったセルを塗りつぶす。
    source_range.Item(1).Interior.Color = source_color
    source_range.Item(1).Interior.TintAndShade = 0.6
    With source_range.Item(1).Borders.Item(xlEdgeRight)
        .Color = source_color
        .Weight = xlMedium
        .LineStyle = xlDouble
    End With
    
    ' 各セルの底線を配列に格納することで、以降のチェックを
    ' ループ内で実行できるようにしている。
    Dim Border_Bottom(1 To 2) As Border
    Dim i As Long
        For i = 1 To UBound(Border_Bottom)
            Set Border_Bottom(i) = source_range.Item(i).Borders(xlEdgeBottom)
        Next
    
    ' 進行方向の確認。
    ' 底線が無い場合、まっすぐ下に進む(⇒ dx=0のまま)。
    Dim dx As Long: dx = 0
        For i = 1 To 2
            If Border_Bottom(i).LineStyle <> xlNone Then
                dx = 2 * i - 3
                Border_Bottom(i).Color = source_color
                Border_Bottom(i).Weight = xlMedium
                Border_Bottom(i).LineStyle = xlDouble
                Exit For
            End If
        Next
        
    Set GetNextRange = source_range.Offset(1, dx)
End Function

' 当選者をセット。
Sub WinnerSet(source_range As Range, winners_number As Long)
    Dim LastRow As Range
    Set LastRow = source_range.Rows(source_range.Rows.Count).Offset(1)
    
    Dim r As Range
        Do
            For Each r In LastRow.Cells
                r = WorksheetFunction.RandBetween(0, 1)
            Next
        Loop Until WorksheetFunction.Sum(LastRow) = winners_number
        LastRow.Replace 0, vbNullString
        LastRow.Replace 1, "当選"
End Sub

それでは、こちらで確認してみよう。
f:id:Infoment:20190804213653p:plain

Sub test()

    ' 参加者の氏名を取得。
    Dim NameArray() As Variant
        NameArray = Range("A5:A11")
    
    ' 参加者をあみだくじにセット。
        Range("D5").Resize(, UBound(NameArray)) = WorksheetFunction.Transpose(NameArray)
    
    ' 当選人数を取得。
    Dim WinnersNumber As Long
        WinnersNumber = Range("B2").Value
    
    ' あみだくじを作成する範囲を設定。
    Dim LadderRange As Range
    Set LadderRange = Range("D6").Resize(UBound(NameArray) * 2, UBound(NameArray))
    
    ' あみだくじの梯子部分を描画。
    Call MakeLadderLine(LadderRange)
    
    ' あみだくじの当選者をセット。
    Call WinnerSet(LadderRange, WinnersNumber)
    
    ' 抽選開始。
    Call Solution(LadderRange)
    
End Sub

結果、このようになった。
f:id:Infoment:20190804213926g:plain

「似通った色がセットされてしまうと判別しにくい」など、まだまだ直すべき点は多い。でも今日は、取り敢えずここまでとします。

参考まで。