SSブログ

[Algo]DQ1の復活の呪文解析[Page1] [Programming Algorithm]

[はじめに]
 ドラゴンクエストI復活の呪文を解析するツールを
 作ってみました。
 オブジェクト指向言語で開発していますが、
 なるべく構造化プログラミングを意識して実装している為、
 他言語への移植はさほど難しくないと思います。 
開発ツールMicrosoft Visual Studio 2010
プログラム言語Microsoft Visual Basic 2010
.NETバージョン.NET Framework 2.0
形式Windows Forms アプリケーション
[機能]
 復活の呪文からゲーム進行状況のパラメータを解析する。
 ゲーム進行状況のパラメータから復活の呪文を生成する。
[制限事項]
 入力データは、下記を前提としています。
 ・プレイヤーの名前:全角のひらがな(一部除く)、数字
 ・復活の呪文:全角のひらがな(一部除く)
 ・復活の呪文:全角5文字、7文字、5文字、3文字
 ブログに掲載できる容量に制限がある為、入力チェックを省いています。
 入力データが前提以外の場合、アベンドすることがあります。
[実行の方法(VisualStudio2010を想定)]
 ①Windowsフォームアプリのプロジェクトを作成し、
  下記ソースをプロジェクトに追加する。(プロジェクトの名称は何でもいい。) 
ソース機能
FrmDq1.vbメイン画面のロジック
FrmDq1.Designer.vbメイン画面のデザイン(自動生成)
Dq1ConstData.vbアイテム、武器、鎧などのデータ
DataConverter.vb文字列と文字コードの相互変換
Dq1PasswordAnalizer.vb『復活の呪文』の解析/生成
Dq1Password.vb『復活の呪文』を表すクラス(文字列と文字コードを管理)
PlayerName.vbプレイヤーの名前を表すクラス(文字列と文字コードを管理)
PlayerInformation.vbプレイヤーの情報(所持金、経験値、イベントフラグ等)
 ②『FrmDq1.vb』をスタートアップフォームに設定する。
 ③Form1.vbを削除する。(新規プロジェクト作成時のデフォルトの画面)
 ④ビルドする。
 (※開発ツールの利用詳細は割愛)
 また、VisualStudio2010で開発していますが、
 .NET2.0上で動作するので、2005/2008でも利用できると思います。
 (※2005/2008では動作未確認)
[実行画面]
 DQ1 - 復活の呪文解析ツール
[プログラムソース]
ブログの容量制限の為、下記に分けて記述しています。
[Algo]DQ1の復活の呪文解析[Page1]
[Algo]DQ1の復活の呪文解析[Page2]
[Algo]DQ1の復活の呪文解析[Page3]
[Algo]DQ1の復活の呪文解析[Page4]
''' <summary>
''' データ変換クラス(文字列⇔数値配列)
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class DataConverter

#Region "チェック処理"
    ''' <summary>
    ''' 変換可能かチェックする。(数値配列→文字列)
    ''' </summary>
    ''' <param name="targetData">データ</param>
    ''' <param name="convTable">変換テーブル</param>
    ''' <param name="errMessage">エラーメッセージ(OKの場合は空文字列)</param>
    ''' <returns>変換可否</returns>
    ''' <remarks></remarks>
    Public Shared Function CanConvertNumToString( _
        ByVal targetData() As Integer, _
        ByVal convTable() As String, _
        ByRef errMessage As StringAs Boolean

        'エラーメッセージ初期化
        errMessage = ""

        Try
            If targetData Is Nothing Then
                errMessage = "引数不正[targetData]"
                Return False
            End If

            If convTable Is Nothing Then
                errMessage = "引数不正[convTable]"
                Return False
            End If

            If targetData.Length > 4 Then
                errMessage = "データが長すぎます。[targetData]"
                Return False
            End If

            For Each data As Integer In targetData
                If data < 0 OrElse data > convTable.Length - 1 Then
                    errMessage = "データ不正。[targetData]"
                    Return False
                End If
            Next

        Catch ex As Exception
            errMessage = _
                "予期せぬエラー" & _
                vbCrLf & ex.ToString()
            Return False
        End Try

        Return True
    End Function

    ''' <summary>
    ''' 変換可能かチェックする。(文字列→数値配列)
    ''' </summary>
    ''' <param name="targetData">データ</param>
    ''' <param name="convTable">変換テーブル</param>
    ''' <param name="errMessage">エラーメッセージ(OKの場合は空文字列)</param>
    ''' <returns>変換可否</returns>
    ''' <remarks></remarks>
    Public Shared Function CanStrToNum( _
        ByVal targetData As String, _
        ByVal convTable() As String, _
        ByRef errMessage As StringAs Boolean

        'エラーメッセージ初期化
        errMessage = ""

        Try
            If targetData Is Nothing Then
                errMessage = "引数不正[targetData]"
                Return False
            End If

            If convTable Is Nothing Then
                errMessage = "引数不正[convTable]"
                Return False
            End If

            If targetData.Length > 20 Then
                errMessage = "データが長すぎます。[targetData]"
                Return False
            End If

            For Each data As Char In targetData
                If Array.IndexOf(convTable, data.ToString()) = -1 Then
                    errMessage = "指定できる文字は以下のみです。" & vbCrLf & String.Join("、", convTable)
                    Return False
                End If
            Next

        Catch ex As Exception
            errMessage = _
                "予期せぬエラー" & _
                vbCrLf & ex.ToString()
            Return False
        End Try

        Return True
    End Function

#End Region

#Region "変換処理"
    ''' <summary>
    ''' データを変換する。(数値配列→文字列)
    ''' </summary>
    ''' <param name="targetData">データ</param>
    ''' <param name="convTable">変換テーブル</param>
    ''' <returns>変換後データ(変換できない場合はNothing)</returns>
    ''' <remarks></remarks>
    Public Shared Function ConvertNumToString( _
        ByVal targetData() As Integer, _
        ByVal convTable() As StringAs String

        Dim sb As New System.Text.StringBuilder()

        For i As Integer = 0 To targetData.Length - 1
            sb.Append(convTable(targetData(i)))
        Next

        Return sb.ToString()

    End Function


    ''' <summary>
    ''' データを変換する。(文字列→数値配列)
    ''' </summary>
    ''' <param name="targetData">データ</param>
    ''' <param name="convTable">変換テーブル</param>
    ''' <returns>変換後データ(変換できない場合はNothing)</returns>
    ''' <remarks></remarks>
    Public Shared Function ConvertStrToNum( _
        ByVal targetData As String, _
        ByVal convTable() As StringAs Integer()

        Dim rtn As New List(Of Integer)()

        For i As Integer = 0 To targetData.Length - 1
            Dim idx As Integer = Array.IndexOf(convTable, targetData(i).ToString())

            If idx < 0 Then
                Return Nothing
            End If

            rtn.Add(idx)
        Next

        Return rtn.ToArray()

    End Function

#End Region

End Class
[VB.NET]DataConverter.vb


''' <summary>
''' 固定値データ
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class Dq1ConstData

#Region "定数定義"
    ''' <summary>
    ''' 道具一覧
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly ITEM_LIST() As String = New String() { _
         "なし""たいまつ""せいすい""キメラのつばさ", _
         "りゅうのうろこ""ようせいのふえ""せんしのゆびわ""ロトのしるし", _
         "おうじょのあい""のろいのベルト""ぎんのたてごと""しのくびかざり", _
         "たいようのいし""あまぐものつえ""にじのしずく", _
         "(不明)" _
    }

    ''' <summary>
    ''' 武器一覧
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly WEAPON_LIST() As String = New String() { _
        "なし""たけざお""こんぼう""どうのつるぎ", _
        "てつのおの""はがねのつるぎ""ほのおのつるぎ""ロトのつるぎ" _
    }

    ''' <summary>
    ''' 鎧一覧
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly ARMOR_LIST() As String = New String() { _
        "なし""ぬののふく""かわのふく""くさりかたびら", _
        "てつのよろい""はがねのよろい""まほうのよろい""ロトのよろい" _
    }

    ''' <summary>
    ''' 盾一覧
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly SHIELD_LIST() As String = New String() { _
        "なし""かわのたて""てつのたて""みかがみのたて" _
    }


    ''' <summary>
    ''' レベル(経験値)一覧
    ''' Lv 1 の必要経験値 = LEVEL_LIST(0)
    ''' Lv n の必要経験値 = LEVEL_LIST(n-1)
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly LEVEL_LIST() As Integer = New Integer() { _
        0, 7, 23, 47, 110, 220, 450, 800, 1300, 2000, _
        2900, 4000, 5500, 7500, 10000, 13000, 17000, 21000, 25000, 29000, _
        33000, 37000, 41000, 45000, 49000, 53000, 57000, 61000, 65000, 65535 _
    }

#End Region

#Region "メソッド定義"
    ''' <summary>
    ''' 経験値からレベルを取得する。
    ''' </summary>
    ''' <param name="exp">経験値</param>
    ''' <returns>レベル</returns>
    ''' <remarks></remarks>
    Public Shared Function GetLevel(ByVal exp As IntegerAs Integer

        If exp < 0 Then
            Throw New ArgumentOutOfRangeException("経験値の範囲が不正。")
        End If

        For i As Integer = LEVEL_LIST.Length - 1 To 0 Step -1
            If exp >= LEVEL_LIST(i) Then
                Return i + 1
            End If
        Next

        Return 0

    End Function

    ''' <summary>
    ''' 指定したレベルに達する為の経験値を取得する。
    ''' </summary>
    ''' <param name="level">レベル</param>
    ''' <returns>経験値</returns>
    ''' <remarks></remarks>
    Public Shared Function GetExp(ByVal level As IntegerAs Integer

        If level < 1 OrElse level > LEVEL_LIST.Length + 1 Then
            Throw New ArgumentOutOfRangeException("レベルの範囲が不正。")
        End If

        Return LEVEL_LIST(level - 1)

    End Function

#End Region

End Class

[VB.NET]Dq1ConstData.vb


''' <summary>
''' 復活の呪文(DQ1)
''' </summary>
''' <remarks></remarks>
Public Class Dq1Password

#Region "変数定義"
    ''' <summary>
    ''' 復活の呪文(文字列)
    ''' </summary>
    ''' <remarks></remarks>
    Private _pass As String = Nothing

    ''' <summary>
    ''' 復活の呪文(数値配列)
    ''' </summary>
    ''' <remarks></remarks>
    Private _passCode() As Integer = Nothing

    ''' <summary>
    ''' 変換テーブル
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly CONVERT_TABLE() As String = _
        New String() { _
            "あ""い""う""え""お", _
            "か""き""く""け""こ", _
            "さ""し""す""せ""そ", _
            "た""ち""つ""て""と", _
            "な""に""ぬ""ね""の", _
            "は""ひ""ふ""へ""ほ", _
            "ま""み""む""め""も", _
            "や""ゆ""よ", _
            "ら""り""る""れ""ろ", _
            "わ", _
            "が""ぎ""ぐ""げ""ご", _
            "ざ""じ""ず""ぜ""ぞ", _
            "だ""ぢ""づ""で""ど", _
            "ば""び""ぶ""べ""ぼ", _
            "?" _
        }

#End Region

#Region "コンストラクタ"
    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="pass">
    ''' 復活の呪文(文字列)
    ''' (例:「ふるいけや」「かわずとびこむ」「みずのおと」「ばしゃ」)
    ''' </param>
    ''' <remarks></remarks>
    Public Sub New(ByVal ParamArray pass() As String)
        MyClass.New(String.Join("", pass))
    End Sub

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="pass">
    ''' 復活の呪文(文字列)
    ''' (例:「ふるいけやかわずとびこむみずのおとばしゃ」)
    ''' </param>
    ''' <remarks></remarks>
    Public Sub New(ByVal pass As String)
        '復活の呪文(文字列)
        Me._pass = pass

        '復活の呪文(数値配列)
        Me._passCode = DataConverter.ConvertStrToNum(Me._pass, CONVERT_TABLE)

    End Sub

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="passCode">復活の呪文(数値配列)</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal passCode() As Integer)
        '名前(数値配列)
        Me._passCode = passCode

        '名前(文字列)
        Me._pass = DataConverter.ConvertNumToString(Me._passCode, CONVERT_TABLE)

    End Sub

#End Region

#Region "メソッド定義"
    ''' <summary>
    ''' 復活の呪文(文字列)
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Overrides Function ToString() As String
        Return Me._pass
    End Function

#End Region

#Region "アクセッサ定義"
    ''' <summary>
    ''' 復活の呪文(数値配列)
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property PassCode() As Integer()
        Get
            Return Me._passCode
        End Get
    End Property

#End Region

End Class
[VB.NET]Dq1Password.vb


''' <summary>
''' 復活の呪文(DQ1)の解析クラス
''' </summary>
''' <remarks></remarks>
Public Class Dq1PasswordAnalizer

#Region "チェックコード"

    ''' <summary>
    ''' チェックコード
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared ReadOnly CHECK_CODE() As Integer = New Integer() {
        &H88, &HC4, &H62, &H31, &H8, &H84, &H42, &H21, _
        &H98, &HCC, &HE6, &H73, &HA9, &HC4, &H62, &H31, _
        &H5A, &HAD, &HC6, &H63, &HA1, &HC0, &H60, &H30, _
        &H38, &H9C, &H4E, &HA7, &HC3, &HF1, &H68, &HB4, _
        &HD0, &H68, &HB4, &H5A, &H2D, &H6, &H83, &H51, _
        &H20, &H10, &H8, &H84, &H42, &HA1, &H40, &HA0, _
        &HF9, &HEC, &HF6, &H7B, &HAD, &HC6, &HE3, &H61, _
        &H81, &HD0, &H68, &HB4, &HDA, &H6D, &HA6, &HD3, _
        &HB2, &HD9, &HFC, &HFE, &HFF, &HEF, &H67, &H23, _
        &H34, &H1A, &HD, &H96, &H4B, &H35, &H8A, &H45, _
        &HAA, &HD5, &H7A, &H3D, &H8E, &H47, &HB3, &H49, _
        &HA1, &H40, &HA0, &H50, &HA8, &HD4, &HEA, &H75, _
        &HA0, &HD0, &H68, &HB4, &H5A, &HAD, &HC6, &H63, _
        &H7E, &HBF, &HCF, &HF7, &H6B, &HA5, &HC2, &H61 _
    }

#End Region

#Region "プレイヤー情報から復活の呪文を取得する。"
    ''' <summary>
    ''' プレイヤー情報から復活の呪文を取得する。
    ''' </summary>
    ''' <param name="pInfo">プレイヤー情報</param>
    ''' <returns>復活の呪文</returns>
    ''' <remarks></remarks>
    Public Function GetPasswordFromPlayerInfo(ByVal pInfo As PlayerInformation) As Dq1Password

        Dim data() As Integer = New Integer(19) {}
        Dim code() As Integer = New Integer(14) {}

        code(0) = _
                (pInfo.ItemList(1) << 4) Or _
                pInfo.ItemList(0)
        code(1) = _
                (pInfo.Flag1 << 7) Or _
                (pInfo.Name.NameCode(1) << 1) Or _
                (pInfo.Flag2)
        code(2) = (pInfo.Experience >> 8) And &HFF
        code(3) = _
                (pInfo.ItemList(5) << 4) Or _
                pInfo.ItemList(4)
        code(4) = _
                (pInfo.MagicKeyNum << 4) Or _
                pInfo.HerbNum
        code(5) = (pInfo.Gold >> 8) And &HFF
        code(6) = _
                (pInfo.Weapon << 5) Or _
                (pInfo.Armor << 2) Or _
                pInfo.Shild
        code(7) = _
                ((pInfo.CryptKey << 5) And &H80) Or _
                (pInfo.Flag3 << 6) Or _
                pInfo.Name.NameCode(3)
        code(8) = _
                (pInfo.ItemList(7) << 4) Or _
                pInfo.ItemList(6)
        code(9) = _
                (pInfo.Name.NameCode(0) << 2) Or _
                (pInfo.Flag4 << 1) Or _
                ((pInfo.CryptKey >> 1) And &H1)
        code(10) = pInfo.Gold And &HFF
        code(11) = _
                (pInfo.ItemList(3) << 4) Or _
                pInfo.ItemList(2)
        code(12) = _
                ((pInfo.CryptKey << 7) And &H80) Or _
                (pInfo.Flag5 << 6) Or _
                pInfo.Name.NameCode(2)
        code(13) = pInfo.Experience And &HFF
        code(14) = 0

        'チェックコードを計算する
        For i As Integer = 0 To 13
            For j As Integer = 0 To 7
                If (code(i) And (&H80 >> j)) <> 0 AndAlso CHECK_CODE(i * 8 + j) <> &H0 Then
                    code(14) = code(14) Xor CHECK_CODE(i * 8 + j)
                End If
            Next
        Next

        '8bit毎の code() を、 6bit毎の data() に変換
        Dim k As Integer = 0

        For i As Integer = 14 To 0 Step -3
            Dim w As Long = _
                (code(i - 2) << 16) Or _
                (code(i - 1) << 8) Or _
                (code(i))

            data(k) = w And &H3F : k += 1
            data(k) = (w >> 6) And &H3F : k += 1
            data(k) = (w >> 12) And &H3F : k += 1
            data(k) = (w >> 18) And &H3F : k += 1

        Next

        '4を加算し、data() を累計する
        Dim work As Integer = 0
        Dim passCode As New List(Of Integer)()

        For i As Integer = 0 To 19
            passCode.Add((data(i) + work + 4) And &H3F)
            work = passCode(i)
        Next

        '復活の呪文
        Dim pass As New Dq1Password(passCode.ToArray())

        Return pass

    End Function

#End Region

#Region "プレイヤー情報から復活の呪文を取得する。"

    ''' <summary>
    ''' プレイヤー情報から復活の呪文を取得する。
    ''' </summary>
    ''' <param name="pass">復活の呪文</param>
    ''' <returns>プレイヤー情報</returns>
    ''' <remarks></remarks>
    Public Function GetPlayerInfoFromPassword(ByVal pass As Dq1Password) As PlayerInformation
        Return Me.GetPlayerInfoFromPassword(pass.ToString())
    End Function


    ''' <summary>
    ''' プレイヤー情報から復活の呪文を取得する。
    ''' </summary>
    ''' <param name="pass">復活の呪文</param>
    ''' <returns>プレイヤー情報</returns>
    ''' <remarks></remarks>
    Public Function GetPlayerInfoFromPassword(ByVal pass As StringAs PlayerInformation

        Dim data(19) As Integer
        Dim code(14) As Integer

        '復活の呪文の文字コード
        Dim passCode() As Integer = DataConverter.ConvertStrToNum( _
                pass, _
                Dq1Password.CONVERT_TABLE _
            )

        '4 を引き、前後の文字の差分を取る
        For i As Integer = 19 To 1 Step -1
            data(i) = (passCode(i) - passCode(i - 1) - 4) And &H3F
        Next

        data(0) = (passCode(0) - 4) And &H3F

        '6bit毎のdata() を、 8bit毎のcode()に変換
        Dim j As Integer = 0

        For i As Integer = 19 To 0 Step -4
            Dim w As Long = _
                (data(i) << 18) Or _
                (data(i - 1) << 12) Or _
                (data(i - 2) << 6) Or _
                 data(i - 3)
            code(j) = (w >> 16) And &HFF : j += 1
            code(j) = (w >> 8) And &HFF : j += 1
            code(j) = w And &HFF : j += 1
        Next

        'チェックコードを計算する
        For i As Integer = 0 To 13
            For jj As Integer = 0 To 7
                If (code(i) And (&H80 >> jj)) <> 0 AndAlso CHECK_CODE(i * 8 + jj) <> &H0 Then
                    code(14) = code(14) Xor CHECK_CODE(i * 8 + jj)
                End If
            Next
        Next

        Dim pInfo As New PlayerInformation()

        pInfo.CheckCode = code(14)

        pInfo.Name = New PlayerName( _
                New Integer() { _
                    (code(9) >> 2) And &H3F, _
                    (code(1) >> 1) And &H3F, _
                    (code(12)) And &H3F, _
                    (code(7)) And &H3F _
                }
            )

        pInfo.Weapon = (code(6) >> 5) And &H7
        pInfo.Armor = (code(6) >> 2) And &H7
        pInfo.Shild = code(6) And &H3

        pInfo.ItemList(0) = code(0) And &HF
        pInfo.ItemList(1) = (code(0) >> 4) And &HF
        pInfo.ItemList(2) = code(11) And &HF
        pInfo.ItemList(3) = (code(11) >> 4) And &HF
        pInfo.ItemList(4) = code(3) And &HF
        pInfo.ItemList(5) = (code(3) >> 4) And &HF
        pInfo.ItemList(6) = code(8) And &HF
        pInfo.ItemList(7) = (code(8) >> 4) And &HF

        pInfo.MagicKeyNum = (code(4) >> 4) And &HF
        pInfo.HerbNum = code(4) And &HF

        pInfo.Experience = code(2) * 256 + code(13)
        pInfo.Gold = code(5) * 256 + code(10)

        pInfo.Flag1 = (code(1) >> 7) And &H1
        pInfo.Flag2 = code(1) And &H1
        pInfo.Flag3 = (code(7) >> 6) And &H1
        pInfo.Flag4 = (code(9) >> 1) And &H1
        pInfo.Flag5 = (code(12) >> 6) And &H1

        pInfo.CryptKey = _
                ((code(7) >> 5) And &H4) Or _
                ((code(9) << 1) And &H2) Or _
                ((code(12) >> 7) And &H1)

        pInfo.Level = Dq1ConstData.GetLevel(pInfo.Experience)

        '呪文が正しいかどうかをチェック
        If pInfo.CheckCode <> 0 OrElse _
            pInfo.MagicKeyNum > 6 OrElse _
            pInfo.HerbNum > 6 Then
            Return Nothing
        End If

        For i As Integer = 0 To 7
            If pInfo.ItemList(i) >= 15 Then
                Return Nothing
            End If
        Next

        Return pInfo

    End Function

#End Region

End Class
[VB.NET]Dq1PasswordAnalizer.vb


続きはこちらから
[Algo]DQ1の復活の呪文解析[Page2]
nice!(0)  コメント(0)  トラックバック(0) 
共通テーマ:ゲーム

nice! 0

コメント 0

コメントを書く

お名前:[必須]
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。