--- /dev/null
+GameRandomizer/bin/
+GameRandomizer/gen/
+gamedata/*.zip
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry kind="src" path="src"/>\r
+ <classpathentry kind="src" path="gen"/>\r
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>\r
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>\r
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>\r
+ <classpathentry kind="output" path="bin/classes"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>GameRandomizer</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"\r
+ package="jp.sourceforge.gamerandomizer"\r
+ android:versionCode="1"\r
+ android:versionName="1.0" >\r
+\r
+ <uses-sdk\r
+ android:minSdkVersion="8"\r
+ android:targetSdkVersion="18" />\r
+\r
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />\r
+\r
+ <application\r
+ android:allowBackup="true"\r
+ android:icon="@drawable/ic_launcher"\r
+ android:label="@string/app_name"\r
+ android:theme="@style/AppTheme">\r
+ <activity\r
+ android:name="jp.sourceforge.gamerandomizer.MainActivity"\r
+ android:label="@string/app_name" >\r
+ <intent-filter>\r
+ <action android:name="android.intent.action.MAIN" />\r
+ <category android:name="android.intent.category.LAUNCHER" />\r
+ </intent-filter>\r
+ </activity>\r
+ <activity\r
+ android:name="jp.sourceforge.gamerandomizer.CardListActivity"\r
+ android:label="@string/app_name" >\r
+ </activity>\r
+ <activity\r
+ android:name="jp.sourceforge.gamerandomizer.MoreActivity"\r
+ android:label="@string/app_name" >\r
+ </activity>\r
+ <activity\r
+ android:name="jp.sourceforge.gamerandomizer.DirectorySelectionActivity"\r
+ android:label="@string/app_name" >\r
+ </activity>\r
+ </application>\r
+\r
+</manifest>\r
--- /dev/null
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
--- /dev/null
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-18
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="新規ドキュメント 1">
+ <defs
+ id="defs4">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4610"
+ id="linearGradient4616"
+ x1="205.87515"
+ y1="348.3223"
+ x2="604.33167"
+ y2="348.3223"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4610">
+ <stop
+ style="stop-color:#ffec32;stop-opacity:1;"
+ offset="0"
+ id="stop4612" />
+ <stop
+ id="stop4622"
+ offset="1"
+ style="stop-color:#ff4c00;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="matrix(0.74191502,0.09513254,-0.10414907,0.67768501,17.593397,-64.991322)"
+ y2="348.3223"
+ x2="604.33167"
+ y1="348.3223"
+ x1="205.87515"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4639"
+ xlink:href="#linearGradient4610"
+ inkscape:collect="always" />
+ <linearGradient
+ gradientTransform="matrix(0.82014277,0.11513058,-0.11513058,0.82014277,54.157498,-164.18479)"
+ y2="348.3223"
+ x2="604.33167"
+ y1="348.3223"
+ x1="205.87515"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4639-5"
+ xlink:href="#linearGradient4610-5"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4610-5">
+ <stop
+ style="stop-color:#f68d9d;stop-opacity:1;"
+ offset="0"
+ id="stop4612-1" />
+ <stop
+ id="stop4622-7"
+ offset="1"
+ style="stop-color:#f90c13;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ y2="348.3223"
+ x2="604.33167"
+ y1="348.3223"
+ x1="205.87515"
+ gradientTransform="matrix(-0.74191502,-0.09513254,0.10414907,-0.67768501,589.94549,564.61424)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4673"
+ xlink:href="#linearGradient4610-5"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4610"
+ id="linearGradient4713"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.74191502,0.09513254,-0.10414907,0.67768501,17.593397,-64.991322)"
+ x1="205.87515"
+ y1="348.3223"
+ x2="604.33167"
+ y2="348.3223" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4610-5"
+ id="linearGradient4715"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-0.74191502,-0.09513254,0.10414907,-0.67768501,589.94549,564.61424)"
+ x1="205.87515"
+ y1="348.3223"
+ x2="604.33167"
+ y2="348.3223" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.71173215"
+ inkscape:cx="283.71855"
+ inkscape:cy="526.18109"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1264"
+ inkscape:window-height="976"
+ inkscape:window-x="1"
+ inkscape:window-y="-2"
+ inkscape:window-maximized="0">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="レイヤー 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g3775"
+ transform="matrix(0.96041202,-0.27858347,0.27858347,0.96041202,27.873055,109.22968)">
+ <rect
+ ry="20"
+ y="72.362183"
+ x="40"
+ height="280"
+ width="200"
+ id="rect2987"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.50300002;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ <rect
+ ry="21.146547"
+ y="90.505798"
+ x="62.315529"
+ height="243.71277"
+ width="155.36894"
+ id="rect2987-1"
+ style="fill:#6454ff;fill-opacity:1;stroke:none" />
+ </g>
+ <g
+ id="g3775-7"
+ transform="matrix(0.99070869,-0.13600105,0.13600105,0.99070869,99.774091,58.776275)">
+ <rect
+ ry="20"
+ y="72.362183"
+ x="40"
+ height="280"
+ width="200"
+ id="rect2987-4"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.50300002;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ <rect
+ ry="21.146547"
+ y="90.505798"
+ x="62.315529"
+ height="243.71277"
+ width="155.36894"
+ id="rect2987-1-0"
+ style="fill:#6454ff;fill-opacity:1;stroke:none" />
+ </g>
+ <g
+ id="g3775-7-9"
+ transform="matrix(0.99962329,0.02744601,-0.02744601,0.99962329,186.21092,20.280014)">
+ <rect
+ ry="20"
+ y="72.362183"
+ x="40"
+ height="280"
+ width="200"
+ id="rect2987-4-4"
+ style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.50300002;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
+ <rect
+ ry="21.146547"
+ y="90.505798"
+ x="62.315529"
+ height="243.71277"
+ width="155.36894"
+ id="rect2987-1-0-8"
+ style="fill:#6454ff;fill-opacity:1;stroke:none" />
+ </g>
+ <g
+ id="g4692"
+ transform="matrix(0.89039871,0,0,0.9280627,33.293524,17.970761)">
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;fill:url(#linearGradient4713);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.72914553;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:block;overflow:visible"
+ d="M 125.57587,267.79528 C 116.10478,151.04856 271.57543,86.164689 362.98834,174.64685 l 33.31628,-28.60916 27.38536,121.48324 -133.58099,-30.29158 34.35739,-29.50315 C 259.63091,140.9191 154.47932,175.94589 125.57587,267.79528 z"
+ id="path1432"
+ sodipodi:nodetypes="ccccccc" />
+ <path
+ inkscape:connector-curvature="0"
+ style="color:#000000;fill:url(#linearGradient4715);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.72914553;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:block;overflow:visible"
+ d="m 481.96302,231.82763 c 9.47109,116.74672 -145.99956,181.6306 -237.41247,93.14843 l -33.31628,28.60916 -27.38536,-121.48324 133.58099,30.29159 -34.35739,29.50314 c 64.83547,66.8071 169.98706,31.78032 198.89051,-60.06908 z"
+ id="path1432-1"
+ sodipodi:nodetypes="ccccccc" />
+ </g>
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >\r
+ \r
+ <padding\r
+ android:top="8dp"\r
+ android:bottom="8dp"\r
+ android:left="8dp"\r
+ android:right="8dp" /> \r
+ <corners\r
+ android:radius="4dp" /> \r
+ <gradient\r
+ android:angle="270"\r
+ android:startColor="#b7d6e5"\r
+ android:endColor="#8ea6b2"\r
+ android:type="linear" />\r
+ <stroke\r
+ android:width="2dp"\r
+ android:color="#3276ff" />\r
+\r
+</shape>\r
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ xmlns:tools="http://schemas.android.com/tools"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="match_parent"\r
+ android:background="#005999"\r
+ android:gravity="center"\r
+ tools:context=".CardListActivity" >\r
+\r
+ <TextView\r
+ android:id="@+id/gameNameTextView2"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_alignParentTop="true"\r
+ android:textSize="24sp"\r
+ android:text="@string/no_game_data"\r
+ android:textColor="#ffffff"\r
+ android:background="#00000000" />\r
+\r
+ <Button\r
+ android:id="@+id/doReselectButton"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentBottom="true"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_alignParentRight="true"\r
+ android:layout_margin="8dp"\r
+ android:onClick="onDoReselectButtonClick"\r
+ android:state_enabled="false"\r
+ android:textColor="#000000"\r
+ android:background="@drawable/shape_select_button"\r
+ android:text="@string/do_reselect" />\r
+\r
+ <ListView\r
+ android:id="@+id/cardListView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_above="@+id/doReselectButton"\r
+ android:layout_below="@+id/gameNameTextView2"\r
+ android:layout_centerHorizontal="true"\r
+ android:divider="#555555"\r
+ android:dividerHeight="1dp"\r
+ android:background="#000000"\r
+ android:choiceMode="multipleChoice"\r
+ android:focusable="true">\r
+ </ListView>\r
+\r
+\r
+</RelativeLayout>\r
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ xmlns:tools="http://schemas.android.com/tools"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="match_parent"\r
+ android:gravity="center"\r
+ tools:context=".DirectorySelectionActivity" >\r
+\r
+ <TextView\r
+ android:id="@+id/directorySelectionTitleTextView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_alignParentTop="true"\r
+ android:layout_alignParentRight="true"\r
+ android:background="#333333"\r
+ android:text="@string/select_data_folder"\r
+ android:textAppearance="?android:attr/textAppearanceLarge" />\r
+\r
+ <TextView\r
+ android:id="@+id/currentDirectoryPathTextView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_below="@+id/directorySelectionTitleTextView"\r
+ android:background="#333333"\r
+ android:text="@string/select_data_folder"\r
+ android:textAppearance="?android:attr/textAppearanceSmall" />\r
+\r
+ <Button\r
+ android:id="@+id/directorySelectionOkButton"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentBottom="true"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_alignParentRight="true"\r
+ android:onClick="onOkButtonClick"\r
+ android:text="@android:string/ok" />\r
+\r
+ <ListView\r
+ android:id="@+id/directoryListView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_above="@+id/directorySelectionOkButton"\r
+ android:layout_below="@+id/currentDirectoryPathTextView"\r
+ android:layout_centerHorizontal="true"\r
+ android:divider="#555555"\r
+ android:dividerHeight="1dp"\r
+ android:focusable="true" >\r
+\r
+ </ListView>\r
+</RelativeLayout>\r
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ xmlns:tools="http://schemas.android.com/tools"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="match_parent"\r
+ android:background="#005999"\r
+ tools:context=".MainActivity" >\r
+\r
+ <TextView\r
+ android:id="@+id/gameNameTextView"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_alignParentTop="true"\r
+ android:textColor="#ffffff"\r
+ android:background="#00000000"\r
+ android:text="@string/no_game_data"\r
+ android:textAppearance="?android:attr/textAppearanceLarge" />\r
+\r
+ <Button\r
+ android:id="@+id/doSelectButton"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content"\r
+ android:layout_alignParentBottom="true"\r
+ android:layout_alignParentLeft="true"\r
+ android:layout_alignParentRight="true"\r
+ android:layout_margin="8dp"\r
+ android:onClick="onDoSelectButtonClick"\r
+ android:state_enabled="false"\r
+ android:textColor="#000000"\r
+ android:background="@drawable/shape_select_button"\r
+ android:text="@string/do_select" />\r
+\r
+ <TextView\r
+ android:id="@+id/totalCardsSizeTextView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_above="@+id/doSelectButton"\r
+ android:layout_centerHorizontal="true"\r
+ android:paddingRight="20sp"\r
+ android:gravity="right|center_vertical"\r
+ android:textColor="#ffffff"\r
+ android:background="#00000000"\r
+ android:textAppearance="?android:attr/textAppearanceSmall" />\r
+\r
+ <ListView\r
+ android:id="@+id/expansionListView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:layout_above="@+id/totalCardsSizeTextView"\r
+ android:layout_below="@+id/gameNameTextView"\r
+ android:layout_centerHorizontal="true"\r
+ android:divider="#555555"\r
+ android:dividerHeight="1dp"\r
+ android:background="#000000"\r
+ android:choiceMode="multipleChoice"\r
+ android:focusable="true" >\r
+\r
+ </ListView>\r
+</RelativeLayout>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="match_parent"\r
+ android:orientation="vertical" >\r
+\r
+ <LinearLayout\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:orientation="horizontal" >\r
+\r
+ <CheckBox\r
+ android:id="@+id/cardCheckBox"\r
+ android:layout_width="0dip"\r
+ android:layout_height="match_parent"\r
+ android:layout_weight="1"\r
+ android:paddingLeft="40sp"\r
+ android:gravity="left|center_vertical"\r
+ android:textColor="#ffffff"\r
+ android:background="#000000"\r
+ android:textSize="18sp"\r
+ android:clickable="false"\r
+ android:focusable="false" />\r
+\r
+ <TextView\r
+ android:id="@+id/cardPriceTextView"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="match_parent"\r
+ android:width="60sp"\r
+ android:paddingRight="20sp"\r
+ android:gravity="center"\r
+ android:textColor="#ffffff"\r
+ android:background="#000000"\r
+ android:textSize="18sp" \r
+ android:clickable="false"\r
+ android:focusable="false" />\r
+\r
+ </LinearLayout>\r
+\r
+ <LinearLayout\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:orientation="horizontal" >\r
+\r
+ <TextView\r
+ android:id="@+id/cardIndexTextView"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="match_parent"\r
+ android:width="80sp"\r
+ android:gravity="left|center_vertical"\r
+ android:textColor="#c0c0ff"\r
+ android:background="#000000"\r
+ android:textAppearance="?android:attr/textAppearanceSmall"\r
+ android:clickable="false"\r
+ android:focusable="false" />\r
+\r
+ <TextView\r
+ android:id="@+id/expansionNameTextView"\r
+ android:layout_width="0dip"\r
+ android:layout_height="wrap_content"\r
+ android:layout_weight="1"\r
+ android:paddingRight="20sp"\r
+ android:gravity="right|center_vertical"\r
+ android:textColor="#c0c0ff"\r
+ android:background="#000000"\r
+ android:textAppearance="?android:attr/textAppearanceSmall"\r
+ android:clickable="false"\r
+ android:focusable="false" />\r
+\r
+ </LinearLayout>\r
+ \r
+</LinearLayout>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="match_parent"\r
+ android:orientation="vertical" >\r
+\r
+ <CheckBox\r
+ android:id="@+id/expansionCheckBox"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:gravity="left|center_vertical"\r
+ android:paddingLeft="40sp"\r
+ android:textColor="#ffffff"\r
+ android:textSize="18sp"\r
+ android:clickable="false"\r
+ android:focusable="false" />\r
+\r
+ <TextView\r
+ android:id="@+id/nCardsTextView"\r
+ android:layout_width="match_parent"\r
+ android:layout_height="wrap_content"\r
+ android:gravity="right|center_vertical"\r
+ android:paddingRight="20sp"\r
+ android:textColor="#c0c0ff"\r
+ android:textAppearance="?android:attr/textAppearanceSmall"\r
+ android:focusable="false"\r
+ android:clickable="false" />\r
+\r
+</LinearLayout>\r
--- /dev/null
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+\r
+ <item\r
+ android:id="@+id/action_settings"\r
+ android:orderInCategory="100"\r
+ android:showAsAction="never"\r
+ android:title="@string/action_settings"/>\r
+\r
+</menu>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+ <item android:id="@+id/menuCheckAll2"\r
+ android:title="@string/check_all"\r
+ android:orderInCategory="0">\r
+ </item>\r
+\r
+ <item android:id="@+id/menuUncheckAll2"\r
+ android:title="@string/uncheck_all"\r
+ android:orderInCategory="1">\r
+ </item>\r
+\r
+</menu>\r
--- /dev/null
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+\r
+ <item\r
+ android:id="@+id/action_settings"\r
+ android:orderInCategory="100"\r
+ android:showAsAction="never"\r
+ android:title="@string/action_settings"/>\r
+\r
+</menu>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >\r
+ <item android:id="@+id/menuCheckAll"\r
+ android:orderInCategory="0"\r
+ android:title="@string/check_all">\r
+ </item>\r
+\r
+ <item android:id="@+id/menuUncheckAll"\r
+ android:orderInCategory="1"\r
+ android:title="@string/uncheck_all">\r
+ </item>\r
+\r
+ <item android:id="@+id/menuSwitchGame"\r
+ android:orderInCategory="2"\r
+ android:title="@string/switch_a_game">\r
+ </item>\r
+\r
+ <item android:id="@+id/menuMore"\r
+ android:orderInCategory="3"\r
+ android:title="@string/more">\r
+ </item>\r
+\r
+</menu>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- ja -->\r
+<resources>\r
+\r
+ <string name="action_settings">設定</string>\r
+ <string name="app_uri">https://sourceforge.jp/projects/gamerandomizer/wiki/FrontPage</string>\r
+ <string name="failed_to_load_game_data">ゲームデータの読み込みに失敗しました</string>\r
+ <string name="failed_to_load_status_data">ステータスデータの読み込みに失敗しました</string>\r
+ <string name="no_game_data_directory">ゲームデータのフォルダがありません</string>\r
+ <string name="no_game_data">ゲームデータがありません</string>\r
+ <string name="no_card_is_checked">カードにチェックが付いていません</string>\r
+ <string name="not_enough_cards">カードが足りません</string>\r
+ <string name="do_select">選ぶ!</string>\r
+ <string name="do_reselect">選び直す!</string>\r
+ <string name="use_expansion">「%s」を使用</string>\r
+ <string name="total_n_cards">合計: %1$d/%2$d</string>\r
+ <string name="sort">整列</string>\r
+ <string name="check_all">すべてチェック</string>\r
+ <string name="uncheck_all">チェック全解除</string>\r
+ <string name="switch_a_game">ゲームの切り替え</string>\r
+ <string name="more">その他</string>\r
+ <string name="reload_data">データの再読み込み</string>\r
+ <string name="select_data_folder">データフォルダの選択</string>\r
+ <string name="locale">言語と地域</string>\r
+ <string name="about_this_application">GameRandomizerについて</string>\r
+ <string name="visit_official_web_site">公式webサイトを訪問</string>\r
+ <string name="version">バージョン %s</string>\r
+ <string name="game_data_will_be_reloaded">ゲームデータは、再読み込みされます</string>\r
+ <string name="to_the_parent_folder">(親フォルダへ)</string>\r
+ \r
+</resources>\r
--- /dev/null
+<resources>\r
+\r
+ <!--\r
+ Customize dimensions originally defined in res/values/dimens.xml (such as\r
+ screen margins) for sw600dp devices (e.g. 7" tablets) here.\r
+ -->\r
+\r
+</resources>\r
--- /dev/null
+<resources>\r
+\r
+ <!--\r
+ Customize dimensions originally defined in res/values/dimens.xml (such as\r
+ screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.\r
+ -->\r
+ <dimen name="activity_horizontal_margin">128dp</dimen>\r
+\r
+</resources>\r
--- /dev/null
+<resources>\r
+\r
+ <!--\r
+ Base application theme for API 11+. This theme completely replaces\r
+ AppBaseTheme from res/values/styles.xml on API 11+ devices.\r
+ -->\r
+ <style name="AppBaseTheme" parent="android:Theme.Holo">\r
+ <!-- API 11 theme customizations can go here. -->\r
+ </style>\r
+\r
+</resources>\r
--- /dev/null
+<resources>\r
+\r
+ <!--\r
+ Base application theme for API 14+. This theme completely replaces\r
+ AppBaseTheme from BOTH res/values/styles.xml and\r
+ res/values-v11/styles.xml on API 14+ devices.\r
+ -->\r
+ <style name="AppBaseTheme" parent="android:Theme.Holo">\r
+ <!-- API 14 theme customizations can go here. -->\r
+ </style>\r
+\r
+</resources>\r
--- /dev/null
+<resources>\r
+\r
+ <!-- Default screen margins, per the Android Design guidelines. -->\r
+ <dimen name="activity_horizontal_margin">16dp</dimen>\r
+ <dimen name="activity_vertical_margin">16dp</dimen>\r
+\r
+</resources>\r
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<resources>\r
+\r
+ <string name="app_name">GameRandomizer</string>\r
+ <string name="action_settings">Settings</string>\r
+ <string name="copyright">Copyright © 2013 Motoyuki Kasahara</string>\r
+ <string name="app_uri">https://sourceforge.jp/projects/gamerandomizer/wiki/FrontPageEn</string>\r
+ <string name="data_directory_path_file_name">data-directory-path.txt</string>\r
+ <string name="status_file_name">status.txt</string>\r
+ <string name="failed_to_load_game_data">Failed to load game data</string>\r
+ <string name="failed_to_load_status_data">Failed to load status data</string>\r
+ <string name="no_game_data_directory">No game data folder</string>\r
+ <string name="no_game_data">No game data</string>\r
+ <string name="no_card_is_checked">No card is checked</string>\r
+ <string name="not_enough_cards">Not enough cards</string>\r
+ <string name="do_select">Select!</string>\r
+ <string name="do_reselect">Select again!</string>\r
+ <string name="use_expansion">Use “%s”</string>\r
+ <string name="total_n_cards">total: %1$d/%2$d</string>\r
+ <string name="sort">Sort</string>\r
+ <string name="check_all">Check all</string>\r
+ <string name="uncheck_all">Uncheck All</string>\r
+ <string name="switch_a_game">Switch a game</string>\r
+ <string name="more">More</string>\r
+ <string name="reload_data">Reload data</string>\r
+ <string name="select_data_folder">Select a data folder</string>\r
+ <string name="locale">Language & Country</string>\r
+ <string name="about_this_application">About GameRandomizer</string>\r
+ <string name="visit_official_web_site">Visit the official web site</string>\r
+ <string name="version">Version %s</string>\r
+ <string name="game_data_will_be_reloaded">Game data will be reloaded</string>\r
+ <string name="to_the_parent_folder">(to the parent folder)</string>\r
+ \r
+</resources>\r
--- /dev/null
+<resources>\r
+\r
+ <!--\r
+ Base application theme, dependent on API level. This theme is replaced\r
+ by AppBaseTheme from res/values-vXX/styles.xml on newer devices.\r
+ -->\r
+ <style name="AppBaseTheme" parent="android:Theme.Black">\r
+ <!--\r
+ Theme customizations available in newer API levels can go in\r
+ res/values-vXX/styles.xml, while customizations related to\r
+ backward-compatibility can go here.\r
+ -->\r
+ </style>\r
+\r
+ <!-- Application theme. -->\r
+ <style name="AppTheme" parent="AppBaseTheme">\r
+ <!-- All customizations that are NOT specific to a particular API-level can go here. -->\r
+ </style>\r
+\r
+</resources>\r
--- /dev/null
+//\r
+// Copyright (c) 2013 Motoyuki Kasahara\r
+//\r
+// This program is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 2 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program. If not, see <http://www.gnu.org/licenses/>.\r
+//\r
+package jp.sourceforge.gamerandomizer;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Hashtable;\r
+import java.util.List;\r
+import java.util.Map;\r
+\r
+import jp.sourceforge.gamerandomizerlib.Card;\r
+import jp.sourceforge.gamerandomizerlib.CardComparator;\r
+import jp.sourceforge.gamerandomizerlib.Expansion;\r
+import jp.sourceforge.gamerandomizerlib.Game;\r
+import jp.sourceforge.gamerandomizerlib.GameBuilder;\r
+import jp.sourceforge.gamerandomizerlib.GameBuilderXMLFile;\r
+import jp.sourceforge.gamerandomizerlib.Randomizer;\r
+import android.app.Activity;\r
+import android.app.AlertDialog;\r
+import android.content.DialogInterface;\r
+import android.os.Bundle;\r
+import android.util.Log;\r
+import android.view.Menu;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.Button;\r
+import android.widget.ListView;\r
+import android.widget.TextView;\r
+import android.widget.Toast;\r
+\r
+import jp.sourceforge.gamerandomizer.R;\r
+\r
+public class CardListActivity extends Activity {\r
+ // Path to a data directory.\r
+ private String dataDirectoryPath;\r
+\r
+ // Game.\r
+ private Game game;\r
+\r
+ // Randomizer.\r
+ private Randomizer randomizer;\r
+\r
+ // Cards selected by the randomizer.\r
+ private List<Card> cards;\r
+\r
+ // Check status of expansions of the current game.\r
+ private List<Boolean> cardChecks;\r
+\r
+ // An adapter for expansion listview.\r
+ private CardListAdapter cardListAdapter;\r
+\r
+ // Focus state (for save and restore state).\r
+ enum FocusState {\r
+ THIS_ACTIVITY, GAME_LIST_DIALOG, ERROR_DIALOG,\r
+ };\r
+ private FocusState focusState;\r
+\r
+ // Saved title and message of error dialog\r
+ // (for save and restore state).\r
+ String lastErrorDialogTitle;\r
+ String lastErrorDialogMessage;\r
+\r
+ @Override\r
+ protected void onCreate(Bundle savedInstanceState) {\r
+ super.onCreate(savedInstanceState);\r
+ setContentView(R.layout.activity_card_list);\r
+\r
+ //\r
+ // Initialize members.\r
+ //\r
+ dataDirectoryPath = null;\r
+ game = null;\r
+ randomizer = null;\r
+ cards = new ArrayList<Card>();\r
+ cardChecks = new ArrayList<Boolean>();\r
+ cardListAdapter = new CardListAdapter(this);\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ lastErrorDialogTitle = "";\r
+ lastErrorDialogMessage = "";\r
+\r
+ //\r
+ // Set a listener for the expansion list.\r
+ //\r
+ ListView cardListView = (ListView)\r
+ findViewById(R.id.cardListView);\r
+\r
+ cardListView.setOnItemClickListener(\r
+ new OnItemClickListener() {\r
+ @Override\r
+ public void onItemClick(AdapterView<?> parent, \r
+ View view, int position, long ld) {\r
+ ListView lv = (ListView)parent;\r
+ cardChecks.set(position, lv.isItemChecked(position));\r
+ cardListAdapter.notifyDataSetChanged();\r
+ view.invalidate();\r
+ }\r
+ });\r
+\r
+ //\r
+ // Set initial data.\r
+ //\r
+ loadDataDirectoryPath();\r
+ loadGame();\r
+ drawActivity();\r
+ }\r
+\r
+ //\r
+ // Handles an on-save-instance-state event of the activity.\r
+ //\r
+ @Override\r
+ protected void onSaveInstanceState(Bundle state) {\r
+ super.onSaveInstanceState(state);\r
+\r
+ state.putString("focus-state", focusState.toString());\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++)\r
+ state.putString("card" + i, cards.get(i).getId());\r
+ for (int i = 0; i < cardsSize; i++)\r
+ state.putBoolean("card-check" + i, cardChecks.get(i));\r
+\r
+ state.putString("error-dialog-title", lastErrorDialogTitle);\r
+ state.putString("error-dialog-message", lastErrorDialogMessage);\r
+\r
+ ListView lv = (ListView)findViewById(R.id.cardListView);\r
+ state.putInt("listview-position", lv.getFirstVisiblePosition());\r
+ }\r
+\r
+ //\r
+ // Handles an on-save-instance-state event of the activity.\r
+ //\r
+ @Override\r
+ protected void onRestoreInstanceState(Bundle state) {\r
+ super.onRestoreInstanceState(state);\r
+\r
+ //\r
+ // Read values saved in the state.\r
+ //\r
+ try {\r
+ focusState = Enum.valueOf(FocusState.class,\r
+ state.getString("focus-state"));\r
+ } catch (Exception e) {\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++) {\r
+ String id = state.getString("card" + i);\r
+ if (id != null)\r
+ cards.set(i, game.getCard(id));\r
+ }\r
+ for (int i = 0; i < cardsSize; i++)\r
+ cardChecks.set(i, state.getBoolean("card-check" + i));\r
+ drawActivity();\r
+\r
+ lastErrorDialogTitle = state.getString("error-dialog-title");\r
+ if (lastErrorDialogTitle == null)\r
+ lastErrorDialogTitle = "";\r
+ lastErrorDialogMessage = state.getString("error-dialog-message");\r
+ if (lastErrorDialogMessage == null)\r
+ lastErrorDialogMessage = "";\r
+\r
+ ListView lv = (ListView)findViewById(R.id.cardListView);\r
+ lv.setSelection(state.getInt("listview-position"));\r
+\r
+ //\r
+ // Show a dialog if needed.\r
+ //\r
+ switch (focusState) {\r
+ case ERROR_DIALOG:\r
+ showErrorDialog(lastErrorDialogTitle,\r
+ lastErrorDialogMessage);\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ }\r
+\r
+ //\r
+ // Handle an on-create event of the option menu.\r
+ //\r
+ @Override\r
+ public boolean onCreateOptionsMenu(Menu menu) {\r
+ // Inflate the menu; this adds items to the action bar if it is present.\r
+ getMenuInflater().inflate(R.menu.card_list_option_menu, menu);\r
+ return true;\r
+ }\r
+\r
+ //\r
+ // Handle an on-options-item-selected event of the option menu.\r
+ //\r
+ @Override\r
+ public boolean onOptionsItemSelected(MenuItem item) {\r
+ switch (item.getItemId()) {\r
+ case R.id.menuCheckAll2:\r
+ checkAllCards();\r
+ break;\r
+ case R.id.menuUncheckAll2:\r
+ uncheckAllCards();\r
+ }\r
+ return true;\r
+ }\r
+\r
+ //\r
+ // Handle an on-click event of the "Select again!" button.\r
+ //\r
+ public void onDoReselectButtonClick(View view) {\r
+ int reselectedCardsSize = reselectCards();\r
+ if (reselectedCardsSize == 0) {\r
+ Toast.makeText(this, R.string.no_card_is_checked,\r
+ Toast.LENGTH_SHORT).show();\r
+ return;\r
+ }\r
+\r
+ Map<String, Boolean> map = new Hashtable<String, Boolean>();\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++)\r
+ map.put(cards.get(i).getId(), cardChecks.get(i));\r
+ Collections.sort(cards, new CardComparator());\r
+ for (int i = 0; i < cardsSize; i++)\r
+ cardChecks.set(i, map.get(cards.get(i).getId()));\r
+\r
+ drawActivity();\r
+ }\r
+\r
+ //\r
+ // Determine path to a data directory.\r
+ //\r
+ private void loadDataDirectoryPath() {\r
+ File statusFileDir = getFilesDir();\r
+ if (statusFileDir == null) {\r
+ Log.e("setDataDirectoryPath", "getFilesDir() returns null");\r
+ return;\r
+ }\r
+ DataDirectoryPathFileReader reader = null;\r
+ try {\r
+ File pathFile = new File(statusFileDir,\r
+ getString(R.string.data_directory_path_file_name));\r
+ reader = new DataDirectoryPathFileReader(pathFile);\r
+ dataDirectoryPath = reader.getDataDirectoryPath();\r
+ } catch (Exception e) {\r
+ Log.e("setDataDirectoryPath", e.getMessage());\r
+ }\r
+\r
+ if (dataDirectoryPath == null) {\r
+ showErrorDialog(getString(R.string.failed_to_load_game_data),\r
+ getString(R.string.no_game_data_directory)); \r
+ }\r
+ }\r
+\r
+ //\r
+ // Load game data from XML files.\r
+ //\r
+ // The game data and status currently used are cleared. If an error\r
+ // occurs at loading data or no game is found, it shows an alert dialog.\r
+ //\r
+ private void loadGame() {\r
+ //\r
+ // Clear the current data.\r
+ //\r
+ game = null;\r
+ randomizer = null;\r
+ cards.clear();\r
+ cardChecks.clear();\r
+ if (dataDirectoryPath == null)\r
+ return;\r
+\r
+ //\r
+ // Read a status file.\r
+ //\r
+ File statusFileDir = getFilesDir();\r
+ if (statusFileDir == null)\r
+ return;\r
+ StatusFileReader reader = null;\r
+ try {\r
+ File statusFile = new File(statusFileDir,\r
+ getString(R.string.status_file_name));\r
+ reader = new StatusFileReader(statusFile);\r
+ } catch (Exception e) {\r
+ showErrorDialog(getString(R.string.failed_to_load_status_data),\r
+ e.getMessage());\r
+ return;\r
+ }\r
+\r
+ String lastGameId = reader.getLastGame();\r
+ if (lastGameId == null) {\r
+ return;\r
+ }\r
+\r
+ //\r
+ // Load game data.\r
+ //\r
+ try {\r
+ GameBuilder builder =\r
+ new GameBuilderXMLFile(dataDirectoryPath);\r
+ game = builder.build(lastGameId);\r
+ } catch (Exception e) {\r
+ Log.e("loadGame", e.getMessage(), e);\r
+ showErrorDialog(getString(R.string.failed_to_load_game_data),\r
+ e.getMessage());\r
+ game = null;\r
+ return;\r
+ }\r
+ if (game == null) {\r
+ showErrorDialog(getString(R.string.failed_to_load_game_data),\r
+ getString(R.string.no_game_data));\r
+ return;\r
+ }\r
+\r
+ //\r
+ // Set 'randomizer'.\r
+ //\r
+ int expansionsSize = game.getExpansionsSize();\r
+ List<Expansion> xl = new ArrayList<Expansion>(expansionsSize);\r
+ for (Expansion x : game.getExpansions()) {\r
+ if (reader.getExpansionCheck(game.getId(), x.getId()))\r
+ xl.add(x);\r
+ }\r
+ game.removeOtherExpansions(xl);\r
+\r
+ //\r
+ // Set 'cards' and 'cardChecks'.\r
+ //\r
+ randomizer = new Randomizer(game);\r
+ randomizer.select(cards);\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++)\r
+ cardChecks.add(false);\r
+ Collections.sort(cards, new CardComparator());\r
+ }\r
+\r
+ //\r
+ // Draw a screen.\r
+ // If the current game is selected, show its information.\r
+ // Otherwise, show a screen which tells 'no game data'.\r
+ //\r
+ private void drawActivity() {\r
+ if (randomizer == null)\r
+ drawActivityNoGame();\r
+ else\r
+ drawActivityCurrentGame();\r
+ }\r
+\r
+ //\r
+ // Draw a screen which tells 'no game data'.\r
+ //\r
+ private void drawActivityNoGame() {\r
+ //\r
+ // Hide the game title. Show the "no game data" message instead.\r
+ //\r
+ TextView gameNameView = (TextView)findViewById(R.id.gameNameTextView2);\r
+ gameNameView.setText(R.string.no_game_data);\r
+\r
+ //\r
+ // Clear the adapter.\r
+ //\r
+ cardListAdapter.clear();\r
+ ListView cardListView = (ListView)findViewById(R.id.cardListView);\r
+ cardListView.setAdapter(cardListAdapter);\r
+\r
+ //\r
+ // Disable the 'Select again!' button.\r
+ //\r
+ Button doReselectButton = (Button)findViewById(R.id.doReselectButton);\r
+ doReselectButton.setEnabled(false);\r
+ }\r
+\r
+ //\r
+ // Draw a screen which shows information about the current game.\r
+ //\r
+ private void drawActivityCurrentGame() {\r
+ //\r
+ // Show a title of the game.\r
+ //\r
+ TextView gameNameView = (TextView)findViewById(R.id.gameNameTextView2);\r
+ gameNameView.setText(randomizer.getGame().getTitle());\r
+\r
+ //\r
+ // Show cards.\r
+ //\r
+ cardListAdapter.clear();\r
+ for (Card c : cards) {\r
+ CardListItem item = new CardListItem(\r
+ c.getTitle(), c.getPrice(), c.getExpansion().getTitle());\r
+ cardListAdapter.add(item);\r
+ }\r
+ ListView cardListView = (ListView)\r
+ findViewById(R.id.cardListView);\r
+ cardListView.setAdapter(cardListAdapter);\r
+\r
+ //\r
+ // Set checkboxes of the cards.\r
+ //\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++)\r
+ cardListView.setItemChecked(i, cardChecks.get(i));\r
+\r
+ //\r
+ // Enable the 'Select again!' button.\r
+ //\r
+ Button doReselectButton = (Button)findViewById(R.id.doReselectButton);\r
+ doReselectButton.setEnabled(true);\r
+\r
+ if (cardsSize < game.getSelectionSize()) {\r
+ Toast.makeText(this, R.string.not_enough_cards,\r
+ Toast.LENGTH_LONG).show();\r
+ }
+ }\r
+\r
+ //\r
+ // Check all expansions of the current game.\r
+ //\r
+ private void checkAllCards() {\r
+ ListView lv = (ListView)findViewById(R.id.cardListView);\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++) {\r
+ lv.setItemChecked(i, true);\r
+ cardChecks.set(i, true);\r
+ }\r
+ lv.invalidate();\r
+ }\r
+\r
+ //\r
+ // Uncheck all expansions of the current game.\r
+ //\r
+ private void uncheckAllCards() {\r
+ ListView lv = (ListView)findViewById(R.id.cardListView);\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++) {\r
+ lv.setItemChecked(i, false);\r
+ cardChecks.set(i, false);\r
+ }\r
+ lv.invalidate();\r
+ }\r
+\r
+ //\r
+ // Select cards again.\r
+ //\r
+ private int reselectCards() {\r
+ List<Card> cl = new ArrayList<Card>();\r
+ List<Boolean> bl = new ArrayList<Boolean>();\r
+\r
+ int cardsSize = cards.size();\r
+ for (int i = 0; i < cardsSize; i++) {\r
+ if (!cardChecks.get(i)) {\r
+ cl.add(cards.get(i));\r
+ bl.add(false);\r
+ }\r
+ }\r
+\r
+ int reselectedCardsSize = cardsSize - cl.size();\r
+ cards = cl;\r
+ cardChecks = bl;\r
+ randomizer.select(cards);\r
+ for (int i = cardChecks.size(); i < cardsSize; i++)\r
+ cardChecks.add(true);\r
+\r
+ return reselectedCardsSize;\r
+ }\r
+\r
+ //\r
+ // Show an error dialog.\r
+ //\r
+ private void showErrorDialog(String title, String message) {\r
+ lastErrorDialogTitle = title;\r
+ lastErrorDialogMessage = message;\r
+\r
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);\r
+ dialog.setTitle(title);\r
+ dialog.setMessage(message);\r
+ dialog.setPositiveButton(android.R.string.ok,\r
+ new DialogInterface.OnClickListener() {\r
+ @Override\r
+ public void onClick(DialogInterface iface, int which) {\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+ });\r
+ dialog.setOnCancelListener(\r
+ new DialogInterface.OnCancelListener() {\r
+ @Override\r
+ public void onCancel(DialogInterface dialog) {\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+ });\r
+ focusState = FocusState.ERROR_DIALOG;\r
+ dialog.show();\r
+ }\r
+}\r
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class CardListAdapter extends ArrayAdapter<CardListItem> {
+ public CardListAdapter(Context context) {
+ super(context, 0);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ view = inflater.inflate(R.layout.card_list, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ CheckBox cardCheckBox =
+ (CheckBox)view.findViewById(R.id.cardCheckBox);
+ CardListItem item = getItem(position);
+ cardCheckBox.setText(item.getTitle());
+
+ ListView listView = (ListView)parent;
+ cardCheckBox.setChecked(listView.isItemChecked(position));
+ cardCheckBox.setVisibility(View.VISIBLE);
+
+ TextView cardPriceTextView =
+ (TextView)view.findViewById(R.id.cardPriceTextView);
+ cardPriceTextView.setText(item.getPrice());
+
+ TextView cardIndexTextView =
+ (TextView)view.findViewById(R.id.cardIndexTextView);
+ cardIndexTextView.setText(String.format("#%d", position + 1));
+
+ TextView expansionNameTextView =
+ (TextView)view.findViewById(R.id.expansionNameTextView);
+ expansionNameTextView.setText(item.getExpansion());
+
+ return view;
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class CardListItem implements Parcelable {
+ private String title;
+ private String price;
+ private String expansion;
+
+ public CardListItem(String titleArg, String priceArg,
+ String expansionArg) {
+ title = titleArg;
+ price = priceArg;
+ expansion = expansionArg;
+ }
+
+ public CardListItem(Parcel p) {
+ title = p.readString();
+ price = p.readString();
+ expansion = p.readString();
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String titleArg) {
+ title = titleArg;
+ }
+
+ public String getPrice() {
+ return price;
+ }
+
+ public void setPrice(String priceArg) {
+ price = priceArg;
+ }
+
+ public String getExpansion() {
+ return expansion;
+ }
+
+ public void setExpansion(String expansionArg) {
+ expansion = expansionArg;
+ }
+
+ public void writeToParcel(Parcel p, int flags) {
+ p.writeString(title);
+ p.writeString(price);
+ p.writeString(expansion);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<CardListItem> CREATOR = new Creator<CardListItem>() {
+ public CardListItem createFromParcel(Parcel p) {
+ return new CardListItem(p);
+ }
+
+ public CardListItem[] newArray(int size) {
+ return new CardListItem[size];
+ }
+ };
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class DataDirectoryPathFileReader {
+ File dataDirectory;
+
+ public DataDirectoryPathFileReader(File file)
+ throws IOException, FileNotFoundException {
+ dataDirectory = null;
+ readFile(file);
+ }
+
+ public DataDirectoryPathFileReader(String filePath)
+ throws IOException, FileNotFoundException {
+ this(new File(filePath));
+ }
+
+ public File getDataDirectory() {
+ return dataDirectory;
+ }
+
+ public String getDataDirectoryPath() {
+ return dataDirectory.getAbsolutePath();
+ }
+
+ private void readFile(File file)
+ throws IOException, FileNotFoundException {
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ String path = reader.readLine();
+ if (path != null && !path.isEmpty())
+ dataDirectory = new File(path);
+ reader.close();
+ } finally {
+ if (reader != null)
+ reader.close();
+ }
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+
+public class DataDirectoryPathFileWriter {
+ private File statusFile;
+
+ private static final String TEMPORARY_FILE_SUFFIX = ".tmp";
+
+ public DataDirectoryPathFileWriter(File file) {
+ statusFile = file;
+ }
+
+ public DataDirectoryPathFileWriter(String filePath) {
+ this(new File(filePath));
+ }
+
+ public void writeFile(File dataDirectory)
+ throws IOException {
+ writeFile(dataDirectory.getAbsolutePath());
+ }
+
+ public void writeFile(String dataDirectoryPath)
+ throws IOException {
+ File temporaryFile = new File(statusFile.getAbsolutePath() +
+ TEMPORARY_FILE_SUFFIX);
+ temporaryFile.delete();
+
+ BufferedWriter writer = null;
+
+ try {
+ writer = new BufferedWriter(new FileWriter(temporaryFile));
+ if (dataDirectoryPath != null)
+ writer.write(dataDirectoryPath);
+ writer.close();
+ temporaryFile.renameTo(statusFile);
+ } finally {
+ if (writer != null)
+ writer.close();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+//\r
+// Copyright (c) 2013 Motoyuki Kasahara\r
+//\r
+// This program is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 2 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program. If not, see <http://www.gnu.org/licenses/>.\r
+//\r
+package jp.sourceforge.gamerandomizer;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.List;\r
+\r
+import android.app.Activity;\r
+import android.content.Intent;\r
+import android.os.Bundle;\r
+import android.os.Environment;\r
+import android.util.Log;\r
+import android.view.Menu;\r
+import android.view.MenuItem;\r
+import android.view.View;\r
+import android.widget.AdapterView;\r
+import android.widget.AdapterView.OnItemClickListener;\r
+import android.widget.ArrayAdapter;\r
+import android.widget.ListView;\r
+import android.widget.TextView;\r
+\r
+public class DirectorySelectionActivity extends Activity {\r
+ // Directory which this activity currently displays.\r
+ private File currentDirectory;\r
+\r
+ // An adapter for expansion listview.\r
+ private ArrayAdapter<String> directoryListAdapter;\r
+\r
+ @Override\r
+ protected void onCreate(Bundle savedInstanceState) {\r
+ super.onCreate(savedInstanceState);\r
+ setContentView(R.layout.activity_directory_selection);\r
+\r
+ //\r
+ // Initialize members.\r
+ //\r
+ currentDirectory = null;\r
+ directoryListAdapter = new ArrayAdapter<String>(this, \r
+ android.R.layout.simple_list_item_1);\r
+\r
+ //\r
+ // Set a listener for the directory list.\r
+ //\r
+ ListView directoryListView = (ListView)\r
+ findViewById(R.id.directoryListView);\r
+ directoryListView.setOnItemClickListener(\r
+ new OnItemClickListener() {\r
+ @Override\r
+ public void onItemClick(AdapterView<?> parent,\r
+ View view,int position, long ld) {\r
+ if (position == 0)\r
+ changeDirectoryToParent();\r
+ else {\r
+ changeDirectory(\r
+ directoryListAdapter.getItem(position));\r
+ }\r
+ }\r
+ });\r
+\r
+ //\r
+ // Set initial data.\r
+ //\r
+ loadDataDirectoryPath();\r
+\r
+ //\r
+ // Draw the activity.\r
+ //\r
+ drawActivity();\r
+ }\r
+\r
+ //\r
+ // Handles an on-save-instance-state event of the activity.\r
+ //\r
+ @Override\r
+ protected void onSaveInstanceState(Bundle state) {\r
+ super.onSaveInstanceState(state);\r
+\r
+ state.putString("current-directory",\r
+ currentDirectory.getAbsolutePath());\r
+\r
+ ListView lv = (ListView)findViewById(R.id.directoryListView);\r
+ state.putInt("listview-position", lv.getFirstVisiblePosition());\r
+ }\r
+\r
+ //\r
+ // Handles an on-save-instance-state event of the activity.\r
+ //\r
+ @Override\r
+ protected void onRestoreInstanceState(Bundle state) {\r
+ super.onRestoreInstanceState(state);\r
+\r
+ String currentDirectoryPath = state.getString("current-directory");\r
+ if (currentDirectoryPath != null)\r
+ currentDirectory = new File(currentDirectoryPath);\r
+ drawActivity();\r
+\r
+ ListView lv = (ListView)findViewById(R.id.directoryListView);\r
+ lv.setSelection(state.getInt("listview-position"));\r
+ }\r
+\r
+ //\r
+ // Handle an on-create event of the option menu.\r
+ //\r
+ @Override\r
+ public boolean onCreateOptionsMenu(Menu menu) {\r
+ return true;\r
+ }\r
+\r
+ //\r
+ // Handle an on-options-item-selected event of the option menu.\r
+ //\r
+ @Override\r
+ public boolean onOptionsItemSelected(MenuItem item) {\r
+ return true;\r
+ }\r
+\r
+ //\r
+ // Determine path to a data directory.\r
+ //\r
+ private void loadDataDirectoryPath() {\r
+ String dataDirectoryPath = null;\r
+ File statusFileDir = getFilesDir();\r
+ if (statusFileDir == null)\r
+ Log.e("setDataDirectoryPath", "getFilesDir() returns null");\r
+ else {\r
+ DataDirectoryPathFileReader reader = null;\r
+ try {\r
+ File pathFile = new File(statusFileDir,\r
+ getString(R.string.data_directory_path_file_name));\r
+ reader = new DataDirectoryPathFileReader(pathFile);\r
+ dataDirectoryPath = reader.getDataDirectoryPath();\r
+ } catch (Exception e) {\r
+ Log.e("setDataDirectoryPath", e.getMessage());\r
+ }\r
+ }\r
+\r
+ if (dataDirectoryPath == null) {\r
+ File externalStorageDirectory =\r
+ Environment.getExternalStorageDirectory();\r
+ if (externalStorageDirectory != null)\r
+ dataDirectoryPath = externalStorageDirectory.getAbsolutePath();\r
+ }\r
+\r
+ if (dataDirectoryPath == null)\r
+ dataDirectoryPath = File.pathSeparator;\r
+\r
+ currentDirectory = new File(dataDirectoryPath);\r
+ }\r
+\r
+ //\r
+ // Draw a screen.\r
+ //\r
+ private void drawActivity() {\r
+ if (currentDirectory == null)\r
+ return;\r
+\r
+ //\r
+ // Show the current directory path.\r
+ //\r
+ TextView currentDirectoryPathTextView = (TextView)\r
+ findViewById(R.id.currentDirectoryPathTextView);\r
+ currentDirectoryPathTextView.setText(\r
+ currentDirectory.getAbsolutePath());\r
+\r
+ //\r
+ // Show sub-directories.\r
+ //\r
+ directoryListAdapter.clear();\r
+ directoryListAdapter.add(getString(R.string.to_the_parent_folder));\r
+\r
+ File[] files = null;\r
+ try {\r
+ files = currentDirectory.listFiles();\r
+ } catch (Exception e) {\r
+ ; // Nothing to do.\r
+ }\r
+\r
+ if (files != null) {\r
+ List<String> names = new ArrayList<String>();\r
+ for (File file : files) {\r
+ if (file.isDirectory() && !file.isHidden())\r
+ names.add(file.getName());\r
+ }\r
+ Collections.sort(names, new Comparator<String>() {\r
+ @Override\r
+ public int compare(String s1, String s2) {\r
+ return s1.compareToIgnoreCase(s2);\r
+ }\r
+ });\r
+ for (String name : names)\r
+ directoryListAdapter.add(name);\r
+ }\r
+\r
+ ListView directoryListView = (ListView)\r
+ findViewById(R.id.directoryListView);\r
+ directoryListView.setAdapter(directoryListAdapter);\r
+ }\r
+\r
+ private void changeDirectory(String directoryName) {\r
+ currentDirectory = new File(currentDirectory, directoryName);\r
+ drawActivity();\r
+ }\r
+\r
+ private void changeDirectoryToParent() {\r
+ File parentDirectory = currentDirectory.getParentFile();\r
+ if (parentDirectory != null && parentDirectory.canRead()) {\r
+ currentDirectory = parentDirectory;\r
+ drawActivity();\r
+ }\r
+ }\r
+\r
+ //\r
+ // Handle an on-click event of the "OK" button.\r
+ //\r
+ public void onOkButtonClick(View view) {\r
+ Intent in = new Intent();\r
+ if (currentDirectory != null)\r
+ in.putExtra("path", currentDirectory.getAbsolutePath());\r
+ setResult(RESULT_OK, in);\r
+ finish();\r
+ }\r
+}\r
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class ExpansionListAdapter extends ArrayAdapter<ExpansionListItem> {
+ public ExpansionListAdapter(Context context) {
+ super(context, 0);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ view = inflater.inflate(R.layout.expansion_list, parent, false);
+ } else {
+ view = convertView;
+ }
+
+ CheckBox expansionCheckBox =
+ (CheckBox)view.findViewById(R.id.expansionCheckBox);
+ ExpansionListItem item = getItem(position);
+ expansionCheckBox.setText(item.getTitle());
+
+ ListView listView = (ListView)parent;
+ expansionCheckBox.setChecked(listView.isItemChecked(position));
+ expansionCheckBox.setVisibility(View.VISIBLE);
+
+ TextView nCardsTextView =
+ (TextView)view.findViewById(R.id.nCardsTextView);
+ nCardsTextView.setText("" + item.getNCards());
+
+ return view;
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ExpansionListItem implements Parcelable {
+ private String title;
+ private int nCards;
+
+ public ExpansionListItem(String titleArg, int nCardsArg) {
+ title = titleArg;
+ nCards = nCardsArg;
+ }
+
+ public ExpansionListItem(Parcel p) {
+ title = p.readString();
+ nCards = p.readInt();
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String titleArg) {
+ title = titleArg;
+ }
+
+ public int getNCards() {
+ return nCards;
+ }
+
+ public void setNCards(int nCardsArg) {
+ nCards = nCardsArg;
+ }
+
+ public void writeToParcel(Parcel p, int flags) {
+ p.writeString(title);
+ p.writeInt(nCards);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<ExpansionListItem> CREATOR = new Creator<ExpansionListItem>() {
+ public ExpansionListItem createFromParcel(Parcel p) {
+ return new ExpansionListItem(p);
+ }
+
+ public ExpansionListItem[] newArray(int size) {
+ return new ExpansionListItem[size];
+ }
+ };
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Hashtable;
+import java.util.List;
+
+import jp.sourceforge.gamerandomizerlib.Expansion;
+import jp.sourceforge.gamerandomizerlib.Game;
+import jp.sourceforge.gamerandomizerlib.GameBuilder;
+import jp.sourceforge.gamerandomizerlib.GameBuilderXMLFile;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class MainActivity extends Activity {
+ // Path to a data directory.
+ private String dataDirectoryPath;
+
+ // Loaded games.
+ private List<Game> games;
+
+ // Check status of expansions of the current game.
+ private Hashtable<Game, List<Boolean>> expansionChecks;
+
+ // A game currently shown on the screen.
+ private Game currentGame;
+
+ // A game the user selects. (used in the game-selection dialog)
+ private Game nextGame;
+
+ // The number of candidate cards.
+ private int cardsSize;
+
+ // An adapter for expansion listview.
+ private ExpansionListAdapter expansionListAdapter;
+
+ // Focus state (for save and restore state).
+ enum FocusState {
+ THIS_ACTIVITY, GAME_LIST_DIALOG, ERROR_DIALOG,
+ };
+ private FocusState focusState;
+
+ // Saved title and message of error dialog
+ // (for save and restore state).
+ String lastErrorDialogTitle;
+ String lastErrorDialogMessage;
+
+ // Request code for "MoreActivity".
+ private static final int REQUEST_CODE_MORE = 1;
+
+ //
+ // Handle an on-create event of the activity.
+ //
+ @Override
+ protected void onCreate(Bundle state) {
+ super.onCreate(state);
+ setContentView(R.layout.activity_main);
+
+ //
+ // Initialize members.
+ //
+ dataDirectoryPath = null;
+ games = new ArrayList<Game>();
+ expansionChecks = new Hashtable<Game, List<Boolean>>();
+ currentGame = null;
+ nextGame = null;
+ cardsSize = 0;
+ expansionListAdapter = new ExpansionListAdapter(this);
+ focusState = FocusState.THIS_ACTIVITY;
+ lastErrorDialogTitle = "";
+ lastErrorDialogMessage = "";
+
+ //
+ // Set initial data.
+ //
+ loadDataDirectoryPath();
+ loadGames();
+ loadStatus();
+
+ //
+ // Set a listener for the expansion list.
+ //
+ ListView expansionListView = (ListView)
+ findViewById(R.id.expansionListView);
+ expansionListView.setOnItemClickListener(
+ new OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent,
+ View view, int position, long ld) {
+ ListView lv = (ListView)parent;
+ expansionChecks.get(currentGame).set(position,
+ lv.isItemChecked(position));
+ expansionListAdapter.notifyDataSetChanged();
+ view.invalidate();
+ postProcExpansionCheckUpdated();
+ }
+ });
+
+ //
+ // Draw the activity.
+ //
+ drawActivity();
+ }
+
+ //
+ // Handle an on-stop event of the activity.
+ //
+ @Override
+ protected void onStop() {
+ super.onStop();
+ saveStatus();
+ }
+
+ //
+ // Handles an on-save-instance-state event of the activity.
+ //
+ @Override
+ protected void onSaveInstanceState(Bundle state) {
+ super.onSaveInstanceState(state);
+
+ saveStatus();
+ state.putString("focus-state", focusState.toString());
+ if (nextGame != null)
+ state.putString("next-game", nextGame.getId());
+
+ state.putString("error-dialog-title", lastErrorDialogTitle);
+ state.putString("error-dialog-message", lastErrorDialogMessage);
+
+ if (currentGame != null) {
+ ListView lv = (ListView)findViewById(R.id.expansionListView);
+ Expansion x = currentGame.getExpansion(
+ lv.getFirstVisiblePosition());
+ state.putString("listview-position", x.getId());
+ }
+ }
+
+ //
+ // Handles an on-restore-instance-state event of the activity.
+ //
+ @Override
+ protected void onRestoreInstanceState(Bundle state) {
+ super.onRestoreInstanceState(state);
+
+ //
+ // Read values saved in the state.
+ //
+ try {
+ focusState = Enum.valueOf(FocusState.class,
+ state.getString("focus-state"));
+ } catch (Exception e) {
+ focusState = FocusState.THIS_ACTIVITY;
+ }
+
+ String id = state.getString("next-game");
+ int index = -1;
+ if (id != null)
+ index = games.indexOf(new Game(id, "", 0));
+ if (index >= 0)
+ nextGame = games.get(index);
+
+ lastErrorDialogTitle = state.getString("error-dialog-title");
+ if (lastErrorDialogTitle == null)
+ lastErrorDialogTitle = "";
+ lastErrorDialogMessage = state.getString("error-dialog-message");
+ if (lastErrorDialogMessage == null)
+ lastErrorDialogMessage = "";
+
+ ListView lv = (ListView)findViewById(R.id.expansionListView);
+ String lvPosition = state.getString("listview-position");
+ if (lvPosition != null) {
+ int gamesSize = games.size();
+ for (int i = 0; i < gamesSize; i++) {
+ if (games.get(i).getId().equals(lvPosition)) {
+ lv.setSelection(i);
+ break;
+ }
+ }
+ }
+
+ //
+ // Show a dialog if needed.
+ //
+ switch (focusState) {
+ case GAME_LIST_DIALOG:
+ switchGame(nextGame);
+ break;
+ case ERROR_DIALOG:
+ showErrorDialog(lastErrorDialogTitle,
+ lastErrorDialogMessage);
+ break;
+ default:
+ break;
+ }
+ }
+
+ //
+ // Handle an on-create event of the option menu.
+ //
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main_option_menu, menu);
+ return true;
+ }
+
+ //
+ // Handle an on-options-item-selected event of the option menu.
+ //
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menuSwitchGame:
+ switchGame();
+ break;
+ case R.id.menuCheckAll:
+ checkAllExpansions();
+ break;
+ case R.id.menuUncheckAll:
+ uncheckAllExpansions();
+ break;
+ case R.id.menuMore:
+ saveStatus();
+ Intent in = new Intent(this, MoreActivity.class);
+ startActivityForResult(in, REQUEST_CODE_MORE);
+ break;
+ }
+ return true;
+ }
+
+ //
+ // Get result of an activity.
+ //
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode,
+ Intent data) {
+ if (resultCode != RESULT_OK)
+ return;
+ if (requestCode == REQUEST_CODE_MORE) {
+ loadDataDirectoryPath();
+ loadGames();
+ loadStatus();
+ drawActivity();
+ }
+ }
+
+ //
+ // Handle an on-click event of the "Select!" button.
+ //
+ public void onDoSelectButtonClick(View view) {
+ if (cardsSize < currentGame.getSelectionSize()) {
+ Toast.makeText(this, R.string.not_enough_cards,
+ Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ saveStatus();
+ Intent in = new Intent(this, CardListActivity.class);
+ startActivity(in);
+ }
+
+ //
+ // Determine path to a data directory.
+ //
+ private void loadDataDirectoryPath() {
+ File statusFileDir = getFilesDir();
+ if (statusFileDir == null) {
+ Log.e("setDataDirectoryPath", "getFilesDir() returns null");
+ return;
+ }
+ DataDirectoryPathFileReader reader = null;
+ try {
+ File pathFile = new File(statusFileDir,
+ getString(R.string.data_directory_path_file_name));
+ reader = new DataDirectoryPathFileReader(pathFile);
+ dataDirectoryPath = reader.getDataDirectoryPath();
+ } catch (Exception e) {
+ Log.e("setDataDirectoryPath", e.getMessage());
+ }
+
+ if (dataDirectoryPath == null) {
+ showErrorDialog(getString(R.string.failed_to_load_game_data),
+ getString(R.string.no_game_data_directory));
+ }
+ }
+
+ //
+ // Load game data from XML files.
+ //
+ // The game data and status currently used are cleared. If an error
+ // occurs at loading data or no game is found, it shows an alert dialog.
+ //
+ private void loadGames() {
+ //
+ // Clear the current data.
+ //
+ games.clear();
+ expansionChecks.clear();
+ currentGame = null;
+
+ if (dataDirectoryPath == null)
+ return;
+
+ //
+ // Load game data.
+ //
+ try {
+ GameBuilder builder =
+ new GameBuilderXMLFile(dataDirectoryPath);
+ builder.buildAll(games);
+ } catch (Exception e) {
+ Log.e("loadGames", e.getMessage(), e);
+ showErrorDialog(getString(R.string.failed_to_load_game_data),
+ e.getMessage());
+ games.clear();
+ return;
+ }
+
+ int gamesSize = games.size();
+ if (gamesSize == 0) {
+ showErrorDialog(getString(R.string.no_game_data), dataDirectoryPath);
+ return;
+ }
+ Collections.sort(games, new Comparator<Game>() {
+ @Override
+ public int compare(Game g1, Game g2) {
+ return g1.getTitle().compareToIgnoreCase(g2.getTitle());
+ }
+ });
+
+ //
+ // Set 'expansionChecks'.
+ //
+ for (Game g : games) {
+ int expansionsSize = g.getExpansionsSize();
+ List<Boolean> bl = new ArrayList<Boolean>(expansionsSize);
+ for (int j = 0; j < expansionsSize; j++)
+ bl.add(false);
+ expansionChecks.put(g, bl);
+ }
+ }
+
+ //
+ // Load a status file.
+ //
+ private void loadStatus() {
+ if (games.size() == 0)
+ return;
+
+ //
+ // Open the status file.
+ //
+ File statusFileDir = getFilesDir();
+ if (statusFileDir == null) {
+ Log.e("saveStatus", "getFilesDir() returns null");
+ return;
+ }
+ StatusFileReader reader = null;
+ try {
+ File statusFile = new File(statusFileDir,
+ getString(R.string.status_file_name));
+ reader = new StatusFileReader(statusFile);
+ } catch (Exception e) {
+ Log.e("loadStatus", e.getMessage());
+ return;
+ }
+
+ //
+ // Set 'currentGame'.
+ //
+ currentGame = games.get(0);
+ String lastGameName = reader.getLastGame();
+ if (lastGameName != null) {
+ int gameIndex = games.indexOf(new Game(lastGameName, "", 0));
+ if (gameIndex >= 0)
+ currentGame = games.get(gameIndex);
+ }
+
+ //
+ // Set 'expansionChecks'.
+ //
+ for (Game g : games) {
+ List<Boolean> bl = expansionChecks.get(g);
+ int blSize = bl.size();
+ for (int i = 0; i < blSize; i++) {
+ bl.set(i, reader.getExpansionCheck(g.getId(),
+ g.getExpansion(i).getId()));
+ }
+ if (bl.indexOf(true) < 0)
+ bl.set(0, true);
+ }
+ }
+
+ //
+ // Save a status file.
+ //
+ private void saveStatus() {
+ //
+ // Open the status file.
+ //
+ File statusFileDir = getFilesDir();
+ if (statusFileDir == null) {
+ Log.e("saveStatus", "getFilesDir() returns null");
+ return;
+ }
+ File statusFile = new File(statusFileDir,
+ getString(R.string.status_file_name));
+ StatusFileWriter writer = null;
+ try {
+ writer = new StatusFileWriter(statusFile);
+ } catch (Exception e) {
+ Log.e("saveStatus", e.getMessage());
+ return;
+ }
+
+ //
+ // Put 'currentGame'.
+ //
+ if (currentGame != null)
+ writer.setLastGame(currentGame.getId());
+
+ //
+ // Put 'expansionChecks'.
+ //
+ for (Game g : games) {
+ List<Boolean> bl = expansionChecks.get(g);
+ int blSize = bl.size();
+ for (int i = 0; i < blSize; i++) {
+ writer.setExpansionCheck(g.getId(),
+ g.getExpansion(i).getId(), bl.get(i));
+ }
+ }
+
+ try {
+ writer.writeFile();
+ } catch (Exception e) {
+ Log.e("saveStatus", e.getMessage());
+ }
+ }
+
+ //
+ // Draw a screen.
+ // If the current game is selected, show its information.
+ // Otherwise, show a screen which tells 'no game data'.
+ //
+ private void drawActivity() {
+ if (currentGame == null)
+ drawActivityNoGame();
+ else
+ drawActivityCurrentGame();
+ }
+
+ //
+ // Draw a screen which tells 'no game data'.
+ //
+ private void drawActivityNoGame() {
+ //
+ // Hide the game title. Show the "no game data" message instead.
+ //
+ TextView gameNameView = (TextView)findViewById(R.id.gameNameTextView);
+ gameNameView.setText(R.string.no_game_data);
+
+ //
+ // Clear the adapter.
+ //
+ expansionListAdapter.clear();
+ ListView expansionView = (ListView)
+ findViewById(R.id.expansionListView);
+ expansionView.setAdapter(expansionListAdapter);
+
+ //
+ // Hide the total number of cards used for random selection.
+ //
+ TextView cardsSizeView = (TextView)findViewById(R.id.totalCardsSizeTextView);
+ cardsSizeView.setText("");
+
+ //
+ // Disable the 'Select!' button.
+ //
+ Button doSelectButton = (Button)findViewById(R.id.doSelectButton);
+ doSelectButton.setEnabled(false);
+ }
+
+ //
+ // Draw a screen which shows information about the current game.
+ //
+ private void drawActivityCurrentGame() {
+ //
+ // Show a title of the game.
+ //
+ TextView gameNameView = (TextView)findViewById(R.id.gameNameTextView);
+ gameNameView.setText(currentGame.getTitle());
+
+ //
+ // Show expansions.
+ //
+ expansionListAdapter.clear();
+ List<Expansion> expansions = currentGame.getExpansions();
+ for (Expansion x : expansions) {
+ ExpansionListItem item = new ExpansionListItem(
+ String.format(getString(R.string.use_expansion),
+ x.getTitle()), currentGame.getCardsSize(x));
+ expansionListAdapter.add(item);
+ }
+ ListView expansionListView = (ListView)
+ findViewById(R.id.expansionListView);
+ expansionListView.setAdapter(expansionListAdapter);
+
+ //
+ // Set checkboxes of the expansions.
+ //
+ int expansionsSize = expansions.size();
+ for (int i = 0; i < expansionsSize; i++) {
+ expansionListView.setItemChecked(i,
+ expansionChecks.get(currentGame).get(i));
+ }
+
+ //
+ // Enable the 'Select!' button.
+ //
+ Button doSelectButton = (Button)findViewById(R.id.doSelectButton);
+ doSelectButton.setEnabled(true);
+
+ //
+ // Show the total number of cards.
+ //
+ postProcExpansionCheckUpdated();
+ }
+
+ //
+ // Show the total number of cards.
+ // Also enable/disable the "Select!" button.
+ //
+ private void postProcExpansionCheckUpdated() {
+ if (currentGame == null)
+ return;
+
+ Game g = new Game(currentGame);
+ List<Boolean> bl = expansionChecks.get(currentGame);
+ for (int i = 0; i < bl.size(); i++) {
+ if (!bl.get(i)) {
+ Expansion x = currentGame.getExpansion(i);
+ g.removeExpansion(x);
+ }
+ }
+ cardsSize = g.getCardsSize();
+
+ TextView tv = (TextView)findViewById(R.id.totalCardsSizeTextView);
+ tv.setText(String.format(getString(R.string.total_n_cards),
+ cardsSize, currentGame.getCardsSize()));
+ }
+
+ //
+ // Show a game selection dialog and switch the current game.
+ //
+ public void switchGame() {
+ switchGame(currentGame);
+ }
+
+ public void switchGame(Game checkedGame) {
+ if (games.size() == 0)
+ return;
+
+ String[] items = new String[games.size()];
+ int i = 0;
+ for (Game g : games)
+ items[i++] = g.getTitle();
+
+ int checkedIndex;
+ if (checkedGame != null)
+ nextGame = checkedGame;
+ else if (currentGame != null)
+ nextGame = currentGame;
+ else
+ nextGame = games.get(0);
+ checkedIndex = games.indexOf(nextGame);
+
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);
+ dialog.setTitle(R.string.switch_a_game);
+ dialog.setSingleChoiceItems(items, checkedIndex,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface iface, int which) {
+ nextGame = games.get(which);
+ }
+ });
+ dialog.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface ifece, int which) {
+ if (currentGame != nextGame) {
+ currentGame = nextGame;
+ drawActivity();
+ }
+ nextGame = null;
+ focusState = FocusState.THIS_ACTIVITY;
+ }
+ });
+ dialog.setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ nextGame = null;
+ focusState = FocusState.THIS_ACTIVITY;
+ }
+ });
+ focusState = FocusState.GAME_LIST_DIALOG;
+ dialog.show();
+ }
+
+ //
+ // Check all expansions of the current game.
+ //
+ private void checkAllExpansions() {
+ ListView lv = (ListView)findViewById(R.id.expansionListView);
+ int expansionsSize = currentGame.getExpansionsSize();
+ for (int i = 0; i < expansionsSize; i++) {
+ lv.setItemChecked(i, true);
+ expansionChecks.get(currentGame).set(i, true);
+ }
+ lv.invalidate();
+ postProcExpansionCheckUpdated();
+ }
+
+ //
+ // Uncheck all expansions of the current game.
+ //
+ private void uncheckAllExpansions() {
+ ListView lv = (ListView)findViewById(R.id.expansionListView);
+ int expansionsSize = currentGame.getExpansionsSize();
+ for (int i = 0; i < expansionsSize; i++) {
+ lv.setItemChecked(i, false);
+ expansionChecks.get(currentGame).set(i, false);
+ }
+ lv.invalidate();
+ postProcExpansionCheckUpdated();
+ }
+
+ //
+ // Show an error dialog.
+ //
+ private void showErrorDialog(String title, String message) {
+ lastErrorDialogTitle = title;
+ lastErrorDialogMessage = message;
+
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);
+ dialog.setTitle(title);
+ dialog.setMessage(message);
+ dialog.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface iface, int which) {
+ focusState = FocusState.THIS_ACTIVITY;
+ }
+ });
+ dialog.setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ focusState = FocusState.THIS_ACTIVITY;
+ }
+ });
+ focusState = FocusState.ERROR_DIALOG;
+ dialog.show();
+ }
+}
--- /dev/null
+//\r
+// Copyright (c) 2013 Motoyuki Kasahara\r
+//\r
+// This program is free software: you can redistribute it and/or modify\r
+// it under the terms of the GNU General Public License as published by\r
+// the Free Software Foundation, either version 2 of the License, or\r
+// (at your option) any later version.\r
+//\r
+// This program is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+// GNU General Public License for more details.\r
+//\r
+// You should have received a copy of the GNU General Public License\r
+// along with this program. If not, see <http://www.gnu.org/licenses/>.\r
+//\r
+package jp.sourceforge.gamerandomizer;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import android.app.AlertDialog;\r
+import android.app.ListActivity;\r
+import android.content.DialogInterface;\r
+import android.content.Intent;\r
+import android.content.pm.PackageInfo;\r
+import android.content.pm.PackageManager;\r
+import android.content.pm.PackageManager.NameNotFoundException;\r
+import android.net.Uri;\r
+import android.os.Bundle;\r
+import android.util.Log;\r
+import android.view.View;\r
+import android.widget.ArrayAdapter;\r
+import android.widget.ListView;\r
+import android.widget.Toast;\r
+\r
+public class MoreActivity extends ListActivity {\r
+ // Adapter of the list.\r
+ private ArrayAdapter<String> adapter;\r
+\r
+ // List items.\r
+ private static final int[] listItems = {\r
+ R.string.reload_data,\r
+ R.string.select_data_folder,\r
+ R.string.about_this_application,\r
+ R.string.visit_official_web_site,\r
+ };\r
+\r
+ // Reloading data files is requested or not.\r
+ boolean reloadDataFlag;\r
+\r
+ // Focus place.\r
+ enum FocusState {\r
+ THIS_ACTIVITY, ABOUT_THIS_APPLICATION_DIALOG,\r
+ };\r
+ private FocusState focusState;\r
+\r
+ // Request code for "DirectorySelectionActivity".\r
+ private static final int REQUEST_CODE_DIRECTORY_SELECTION = 1;\r
+\r
+ //\r
+ // Handle an on-create event of the activity.\r
+ //\r
+ @Override\r
+ protected void onCreate(Bundle savedInstanceState) {\r
+ super.onCreate(savedInstanceState);\r
+\r
+ //\r
+ // Setup the listview.\r
+ //\r
+ List<String> titles = new ArrayList<String>();\r
+ for (int i = 0; i < listItems.length; i++)\r
+ titles.add(getString(listItems[i]));\r
+\r
+ adapter = new ArrayAdapter<String>(this,\r
+ android.R.layout.simple_list_item_1, titles);\r
+ this.setListAdapter(adapter);\r
+\r
+ //\r
+ // Initialize members.\r
+ //\r
+ reloadDataFlag = false;\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+\r
+ //\r
+ // Handles an on-save-instance-state event of the activity.\r
+ //\r
+ @Override\r
+ protected void onSaveInstanceState(Bundle state) {\r
+ super.onSaveInstanceState(state);\r
+ state.putString("focus-state", focusState.toString());\r
+\r
+ state.putInt("listview-position",\r
+ getListView().getFirstVisiblePosition());\r
+ }\r
+\r
+ //\r
+ // Handles an on-restore-instance-state event of the activity.\r
+ //\r
+ @Override\r
+ protected void onRestoreInstanceState(Bundle state) {\r
+ super.onRestoreInstanceState(state);\r
+\r
+ //\r
+ // Read values saved in the state.\r
+ //\r
+ try {\r
+ focusState = Enum.valueOf(FocusState.class,\r
+ state.getString("focus-state"));\r
+ } catch (Exception e) {\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+\r
+ getListView().setSelection(state.getInt("listview-position"));\r
+\r
+ //\r
+ // Show a dialog if needed.\r
+ //\r
+ switch (focusState) {\r
+ case ABOUT_THIS_APPLICATION_DIALOG:\r
+ showAboutApplicationDialog();\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ }\r
+\r
+ //\r
+ // Handle an on-click event of the list.\r
+ //\r
+ @Override\r
+ protected void onListItemClick(ListView listView, View view,\r
+ int position, long id) {\r
+ super.onListItemClick(listView, view, position, id);\r
+\r
+ if (position < 0 || position >= listItems.length)\r
+ return;\r
+\r
+ switch (listItems[position]) {\r
+ case R.string.reload_data:\r
+ reloadDataFlag = true;\r
+ setResult();\r
+ Toast.makeText(this, R.string.game_data_will_be_reloaded,\r
+ Toast.LENGTH_LONG).show();\r
+ break;\r
+ case R.string.select_data_folder:\r
+ Intent in = new Intent(this,\r
+ DirectorySelectionActivity.class);\r
+ startActivityForResult(in, REQUEST_CODE_DIRECTORY_SELECTION);\r
+ break;\r
+ case R.string.about_this_application:\r
+ showAboutApplicationDialog();\r
+ break;\r
+ case R.string.visit_official_web_site:\r
+ Intent in2 = new Intent(Intent.ACTION_VIEW,\r
+ Uri.parse(getString(R.string.app_uri)));\r
+ startActivity(in2);\r
+ break;\r
+ }\r
+ }\r
+\r
+ //\r
+ // Get result of an activity.\r
+ //\r
+ @Override\r
+ protected void onActivityResult(int requestCode, int resultCode,\r
+ Intent data) {\r
+ if (resultCode != RESULT_OK)\r
+ return;\r
+ if (requestCode == REQUEST_CODE_DIRECTORY_SELECTION) {\r
+ String path = data.getStringExtra("path");\r
+ if (path != null) {\r
+ saveDataDirectoryPath(path);\r
+ reloadDataFlag = true;\r
+ setResult();\r
+ }\r
+ }\r
+ }\r
+\r
+ //\r
+ // Set result of this activity.\r
+ //\r
+ protected void setResult() {\r
+ Intent in = new Intent();\r
+ in.putExtra("reload", reloadDataFlag);\r
+ setResult(RESULT_OK, in);\r
+ }\r
+\r
+ //\r
+ // Save a data directory path file.\r
+ //\r
+ private void saveDataDirectoryPath(String path) {\r
+ //\r
+ // Open the status file.\r
+ //\r
+ File statusFileDir = getFilesDir();\r
+ if (statusFileDir == null) {\r
+ Log.e("saveDataDirectoryPath", "getFilesDir() returns null");\r
+ return;\r
+ }\r
+ File pathFile = new File(statusFileDir,\r
+ getString(R.string.data_directory_path_file_name));\r
+ DataDirectoryPathFileWriter writer = null;\r
+ try {\r
+ writer = new DataDirectoryPathFileWriter(pathFile);\r
+ writer.writeFile(path);\r
+ } catch (Exception e) {\r
+ Log.e("saveDataDirectoryPath", e.getMessage());\r
+ return; \r
+ }\r
+ }\r
+\r
+ //\r
+ // Show the "About this application" dialog.\r
+ //\r
+ private void showAboutApplicationDialog() {\r
+ PackageManager manager = this.getPackageManager();\r
+ String version = "";\r
+ String copyright = getString(R.string.copyright);\r
+\r
+ try {\r
+ PackageInfo info = manager.getPackageInfo(\r
+ this.getPackageName(), PackageManager.GET_ACTIVITIES);\r
+ version = String.format(getString(R.string.version),\r
+ info.versionName);\r
+ } catch (NameNotFoundException e) {\r
+ ; // Nothing to do.\r
+ }\r
+\r
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);\r
+ dialog.setTitle(R.string.about_this_application);\r
+ dialog.setMessage(version + "\n" + copyright);\r
+ dialog.setIcon(R.drawable.ic_launcher);\r
+ dialog.setPositiveButton(android.R.string.ok,\r
+ new DialogInterface.OnClickListener() {\r
+ @Override\r
+ public void onClick(DialogInterface iface, int which) {\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+ });\r
+ dialog.setOnCancelListener(\r
+ new DialogInterface.OnCancelListener() {\r
+ @Override\r
+ public void onCancel(DialogInterface dialog) {\r
+ focusState = FocusState.THIS_ACTIVITY;\r
+ }\r
+ });\r
+ focusState = FocusState.ABOUT_THIS_APPLICATION_DIALOG;\r
+ dialog.show();\r
+ }\r
+}\r
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+public class StatusFileReader {
+ private String lastGame;
+ private Hashtable<String, Hashtable<String, Boolean>> expansionChecks;
+
+ public StatusFileReader(File file)
+ throws IOException, FileNotFoundException {
+ lastGame = null;
+ expansionChecks = new Hashtable<String, Hashtable<String, Boolean>>();
+ readFile(file);
+ }
+
+ public StatusFileReader(String filePath)
+ throws IOException, FileNotFoundException {
+ this(new File(filePath));
+ }
+
+ public String getLastGame() {
+ return lastGame;
+ }
+
+ public boolean getExpansionCheck(String gameId, String expansionId) {
+ Hashtable<String, Boolean> expansions = expansionChecks.get(gameId);
+ if (expansions == null)
+ return false;
+ Boolean used = expansions.get(expansionId);
+ if (used == null)
+ return false;
+ return used;
+ }
+
+ private void readFile(File file)
+ throws IOException, FileNotFoundException {
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ String line;
+ while ((line = reader.readLine()) != null)
+ parseLine(line);
+ } finally {
+ reader.close();
+ }
+ }
+
+ private void parseLine(String line) {
+ List<String> fields = splitLineIntoFields(line);
+ int size = fields.size();
+ if (size == 0)
+ return;
+
+ String command = fields.get(0);
+ if (command.equals("last-game") && size == 2)
+ lastGame = fields.get(1);
+ else if (command.equals("expansion-check") && size == 4) {
+ Hashtable<String, Boolean> dic =
+ expansionChecks.get(fields.get(1));
+ if (dic == null)
+ dic = new Hashtable<String, Boolean>();
+ dic.put(fields.get(2), Boolean.parseBoolean(fields.get(3)));
+ expansionChecks.put(fields.get(1), dic);
+ }
+ }
+
+ private List<String> splitLineIntoFields(String line) {
+ List<String> list = new ArrayList<String>();
+ String ln = line.trim();
+ for (;;) {
+ int length = ln.length();
+ int index = ln.indexOf('\t');
+ if (index < 0) {
+ list.add(unescapeString(ln));
+ return list;
+ }
+ list.add(unescapeString(ln.substring(0, index)));
+ ln = ln.substring(index + 1, length);
+ }
+ }
+
+ private String unescapeString(String string) {
+ StringBuffer strIn = new StringBuffer(string);
+ StringBuffer strOut = new StringBuffer();
+ int lengthIn = strIn.length();
+
+ int i = 0;
+ while (i < lengthIn) {
+ int c = strIn.codePointAt(i);
+ if (c == '%' && i + 2 < lengthIn) {
+ int hex1 = strIn.codePointAt(i + 1);
+ int hex2 = strIn.codePointAt(i + 2);
+ int c2 = 0;
+ if ('0' <= hex1 && hex1 <= '9')
+ c2 = (hex1 - '0') << 4;
+ else if ('A' <= hex1 && hex1 <= 'F')
+ c2 = (hex1 - 'A' + 0x0a) << 4;
+ else if ('a' <= hex1 && hex1 <= 'f')
+ c2 = (hex1 - 'a' + 0x0a) << 4;
+ else {
+ strOut.appendCodePoint(c);
+ i++;
+ continue;
+ }
+ if ('0' <= hex2 && hex2 <= '9')
+ c2 |= hex2 - '0';
+ else if ('A' <= hex2 && hex2 <= 'F')
+ c2 |= hex2 - 'A' + 0x0a;
+ else if ('a' <= hex2 && hex2 <= 'f')
+ c2 |= hex2 - 'a' + 0x0a;
+ else {
+ strOut.appendCodePoint(c);
+ i++;
+ continue;
+ }
+ strOut.appendCodePoint(c2);
+ i += 3;
+ } else {
+ strOut.appendCodePoint(c);
+ i++;
+ }
+ }
+
+ return strOut.toString();
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizer;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+public class StatusFileWriter {
+ private File statusFile;
+ private String lastGame;
+ private Hashtable<String, Hashtable<String, Boolean>> expansionChecks;
+
+ private static final String TEMPORARY_FILE_SUFFIX = ".tmp";
+
+ public StatusFileWriter(File file) {
+ statusFile = file;
+ lastGame = null;
+ expansionChecks = new Hashtable<String, Hashtable<String, Boolean>>();
+ }
+
+ public StatusFileWriter(String filePath) {
+ this(new File(filePath));
+ }
+
+ public void setLastGame(String gameId) {
+ lastGame = gameId;
+ }
+
+ public void setExpansionCheck(String gameId, String expansionId,
+ boolean used) {
+ Hashtable<String, Boolean> dic = expansionChecks.get(gameId);
+ if (dic == null) {
+ dic = new Hashtable<String, Boolean>();
+ expansionChecks.put(gameId, dic);
+ }
+ dic.put(expansionId, (Boolean)used);
+ }
+
+ public void writeFile()
+ throws IOException {
+ File temporaryFile = new File(statusFile.getAbsolutePath() +
+ TEMPORARY_FILE_SUFFIX);
+ temporaryFile.delete();
+
+ BufferedWriter writer = null;
+
+ try {
+ writer = new BufferedWriter(new FileWriter(temporaryFile));
+ if (lastGame != null) {
+ writer.write("last-game\t");
+ writer.write(escapeString(lastGame));
+ writer.newLine();
+ }
+ Enumeration<String> en = expansionChecks.keys();
+ while (en.hasMoreElements()) {
+ String game = en.nextElement();
+ Hashtable<String, Boolean> dic = expansionChecks.get(game);
+ Enumeration<String> en2 = dic.keys();
+ while (en2.hasMoreElements()) {
+ String expansion = en2.nextElement();
+ Boolean value = dic.get(expansion);
+ writer.write("expansion-check\t");
+ writer.write(escapeString(game));
+ writer.write('\t');
+ writer.write(escapeString(expansion));
+ writer.write('\t');
+ writer.write(value.toString());
+ writer.newLine();
+ }
+ }
+ writer.close();
+ } finally {
+ if (writer != null)
+ writer.close();
+ }
+
+ temporaryFile.renameTo(statusFile);
+ }
+
+ private String escapeString(String string) {
+ StringBuffer strIn = new StringBuffer(string);
+ StringBuffer strOut = new StringBuffer();
+ int lengthIn = strIn.length();
+
+ for (int i = 0; i < lengthIn; i++) {
+ int c = strIn.codePointAt(i);
+ if (c < ' ' || c == '%') {
+ strOut.append('%');
+ int hex1 = (c & 0xf0) >> 4;
+ int hex2 = (c & 0x0f);
+ if (hex1 <= 9)
+ strOut.appendCodePoint('0' + hex1);
+ else
+ strOut.appendCodePoint('a' + hex1 - 10);
+ if (hex2 <= 9)
+ strOut.appendCodePoint('0' + hex2);
+ else
+ strOut.appendCodePoint('a' + hex2 - 10);
+ } else {
+ strOut.appendCodePoint(c);
+ }
+ }
+
+ return strOut.toString();
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+/**
+ * <p>
+ * Represents a card of a deck building game.
+ * </p>
+ *
+ * <p>
+ * Its object memories ID, a title and a price of a card. Note that type of
+ * price is 'String' not 'int', since some games use '5+', '?' and so on.
+ * It doesn't handles detailed explanation of a card (e.g. "+2 Actions").
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public class Card implements Comparable<Card> {
+ private String id;
+ private String title;
+ private String price;
+ private Expansion expansion;
+
+ /**
+ * Constructs an object.
+ *
+ * The created object has the specified ID, title, price and expansion
+ * which provides this card.
+ *
+ * @param idArg ID
+ * @param titleArg card title
+ * @param priceArg price
+ * @param expansionArg expansion
+ */
+ public Card(String idArg, String titleArg, String priceArg,
+ Expansion expansionArg) {
+ assert idArg != null;
+ assert titleArg != null;
+ assert priceArg != null;
+ assert expansionArg != null;
+
+ id = idArg;
+ title = titleArg;
+ price = priceArg;
+ expansion = expansionArg;
+ }
+
+ /**
+ * Constructs a copy of the specified object.
+ *
+ * @param card the source object
+ */
+ public Card(Card card) {
+ assert card != null;
+
+ id = card.id;
+ title = card.title;
+ price = card.price;
+ expansion = card.expansion;
+ }
+
+ /**
+ * Returns ID of this card.
+ *
+ * @return ID
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns the title of this card.
+ *
+ * @return a title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Returns price of this card.
+ *
+ * @return price
+ */
+ public String getPrice() {
+ return price;
+ }
+
+ /**
+ * Returns an expansion which provides this card.
+ *
+ * @return an expansion
+ */
+ public Expansion getExpansion() {
+ return expansion;
+ }
+
+ /**
+ * Returns true if this object is equal to the specified one.
+ *
+ * It returns true, if both objects have the same ID,
+ *
+ * @param o an object to be compared
+ * @return true if both objects have the same ID
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null || !(o instanceof Card))
+ return false;
+ Card card = (Card)o;
+ return id.equals(card.id);
+ }
+
+ /**
+ * Returns a hash value of this object.
+ *
+ * @return a hash value
+ */
+ @Override
+ public int hashCode() {
+ if (id == null)
+ return 0;
+ else
+ return id.hashCode();
+ }
+
+ /**
+ * Compares this object and the specified one.
+ *
+ * It compares ID of both objects. It returns an integer less than,
+ * equal to, or greater than zero if ID of this object is respectively
+ * less than, equal to, or greater than that of the specified object.
+ *
+ * @param card a card object to be compared
+ * @return a negative integer, zero, or a positive integer according
+ * with result of the comparison.
+ */
+ public int compareTo(Card card) {
+ return id.compareTo(card.id);
+ }
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a title of this object.
+ */
+ @Override
+ public String toString() {
+ return title;
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+public class CardComparator implements Comparator<Card>,
+ Serializable {
+ private CardComparisonMode mode;
+
+ /**
+ * Constructs an object.
+ *
+ * Comparison mode of the constructed object is set to
+ * 'PRICE_THEN_EXPANSION'.
+ */
+ public CardComparator() {
+ mode = CardComparisonMode.PRICE_THEN_EXPANSION;
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * Comparison mode of the constructed object is set to the specified
+ * value.
+ *
+ * @param modeArg Comparison mode
+ */
+ public CardComparator(CardComparisonMode modeArg) {
+ assert (mode == CardComparisonMode.PRICE_THEN_EXPANSION
+ || mode == CardComparisonMode.EXPANSION_THEN_PRICE);
+ mode = modeArg;
+ }
+
+ /**
+ * Returns comparision mode.
+ *
+ * @return comparision mode
+ */
+ public CardComparisonMode getMode() {
+ return mode;
+ }
+
+ /**
+ * Compares the specified two cards.
+ *
+ * It compares two card objects 'c1' and 'c2'. It returns an integer
+ * less than, equal to, or greater than zero if 'c1' is respectively
+ * less than, equal to, or greater than 'c2'.
+ *
+ * @param c1 a card
+ * @param c2 a card
+ * @return a negative integer, zero, or a positive integer according
+ * with result of the comparison.
+ */
+ @Override
+ public int compare(Card c1, Card c2) {
+ int result = 0;
+
+ switch (mode) {
+ case PRICE_THEN_EXPANSION:
+ result = comparePrice(c1, c2);
+ if (result == 0)
+ result = compareExpansion(c1, c2);
+ break;
+ case EXPANSION_THEN_PRICE:
+ result = compareExpansion(c1, c2);
+ if (result == 0)
+ result = comparePrice(c1, c2);
+ break;
+ }
+
+ return result;
+ }
+
+ //
+ // Internal method of compare().
+ //
+ // It compares two cards with their price values.
+ //
+ private int comparePrice(Card c1, Card c2) {
+ String price1 = c1.getPrice();
+ int length1 = price1.length();
+
+ String price2 = c2.getPrice();
+ int length2 = price2.length();
+
+ int idx1 = 0;
+ int idx2 = 0;
+
+ for (;;) {
+ if (idx1 >= length1 || idx2 >= length2)
+ return length1 - length2;
+ int p1 = price1.codePointAt(idx1);
+ int p2 = price2.codePointAt(idx2);
+ if ('0' <= p1 && p1 <= '9' && '0' <= p2 && p2 <= '9') {
+ int num1 = p1 - '0';
+ int num2 = p2 - '0';
+ idx1++;
+ idx2++;
+ for (;;) {
+ boolean updated = false;
+ if (idx1 < length1) {
+ p1 = price1.codePointAt(idx1);
+ if ('0' <= p1 && p1 <= '9') {
+ num1 = num1 * 10 + (p1 - '0');
+ updated = true;
+ idx1++;
+ }
+ }
+ if (idx2 < length2) {
+ p2 = price2.codePointAt(idx2);
+ if ('0' <= p2 && p2 <= '9') {
+ num2 = num2 * 10 + (p2 - '0');
+ updated = true;
+ idx2++;
+ }
+ }
+ if (!updated)
+ break;
+ }
+ if (num1 != num2)
+ return num1 - num2;
+ } else {
+ if (p1 != p2)
+ return p1 - p2;
+ idx1++;
+ idx2++;
+ }
+ }
+ }
+
+ //
+ // Internal method of compare().
+ //
+ // It compares two cards with their extensions.
+ //
+ private int compareExpansion(Card c1, Card c2) {
+ return c1.getExpansion().compareTo(c2.getExpansion());
+ }
+
+ /**
+ * Returns true if this object is equal to the specified one.
+ *
+ * It returns true, if both objects are equivalent.
+ *
+ * @param o an object to be compared
+ * @return true if both objects are equivalent
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null || !(o instanceof CardComparator))
+ return false;
+ CardComparator comparator = (CardComparator)o;
+ return (mode == comparator.mode);
+ }
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a string "CLASS-NAME (MODE)"
+ */
+ @Override
+ public String toString() {
+ return String.format("%s (%s)",
+ getClass().getSimpleName(), mode.toString());
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+public enum CardComparisonMode {
+ PRICE_THEN_EXPANSION, EXPANSION_THEN_PRICE
+};
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+
+/**
+ * <p>
+ * Represents an expansion of a deck building game (e.g. "Intrigue",
+ * "Sea Side" etc. of "Dominion"). It also handles the base game as an
+ * expansion.
+ * </p>
+ *
+ * <p>
+ * Its object has ID, a title and a preference of an expansion. The
+ * preference parameter is an integer value, used for sorting expansions by
+ * publication date. Its object doesn't manage what cards are provided by
+ * the expansion. For that purpose, use 'Game' class, instead.
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public class Expansion implements Comparable<Expansion> {
+ private String id;
+ private String title;
+ int preference;
+
+ /**
+ * Constructs an object.
+ *
+ * The created object has the specified ID and title. Its preference
+ * is set to the lowest value.
+ *
+ * @param idArg ID
+ * @param titleArg expansion title
+ */
+ public Expansion(String idArg, String titleArg) {
+ assert idArg != null;
+ assert titleArg != null;
+
+ id = idArg;
+ title = titleArg;
+ preference = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * The created object has the specified ID, title and preference.
+ *
+ * @param idArg ID
+ * @param titleArg a title
+ * @param preferenceArg a preference value
+ */
+ public Expansion(String idArg, String titleArg, int preferenceArg) {
+ assert idArg != null;
+ assert titleArg != null;
+
+ id = idArg;
+ title = titleArg;
+ preference = preferenceArg;
+ }
+
+ /**
+ * Constructs a copy of the specified object.
+ *
+ * @param expansion the source object
+ */
+ public Expansion(Expansion expansion) {
+ assert expansion != null;
+
+ id = expansion.id;
+ title = expansion.title;
+ preference = expansion.preference;
+ }
+
+ /**
+ * Returns ID of this object.
+ *
+ * @return ID
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns a title of this expansion.
+ *
+ * @return a title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Returns a preference of this expansion.
+ *
+ * @return a preference value
+ */
+ public int getPreference() {
+ return preference;
+ }
+
+ /**
+ * Returns true if this object is equal to the specified one.
+ *
+ * It returns true, if both objects have the same ID,
+ *
+ * @param o an object to be compared
+ * @return true if both objects have the same ID
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null || !(o instanceof Expansion))
+ return false;
+ Expansion x = (Expansion)o;
+ return id.equals(x.id);
+ }
+
+ /**
+ * Returns a hash value of this object.
+ *
+ * @return a hash value
+ */
+ @Override
+ public int hashCode() {
+ if (id == null)
+ return 0;
+ else
+ return id.hashCode();
+ }
+
+ /**
+ * Compares this object and the specified one.
+ *
+ * It compares ID of both objects. It returns an integer less than,
+ * equal to, or greater than zero if ID of this object is respectively
+ * less than, equal to, or greater than that of the specified object.
+ *
+ * @param expansion an expansion object to be compared
+ * @return a negative integer, zero, or a positive integer according
+ * with result of the comparison.
+ */
+ public int compareTo(Expansion expansion) {
+ if (preference != expansion.preference)
+ return preference - expansion.preference;
+ else
+ return id.compareTo(expansion.id);
+ }
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a title of this object.
+ */
+ @Override
+ public String toString() {
+ return title;
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * <p>
+ * Represents a deck building game (e.g. "Dominion").
+ * </p>
+ *
+ * <p>
+ * Its object manages basic information about a game, expansions and cards.
+ * Basic information of a game is set at construction of an object and it
+ * cannot be changed. Currently, basic information that an object hanles is
+ * ID, a title, the number of cards to be selected at playing the game.
+ * On the contrary, expansions and cards can be added and modified
+ * successively.
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public class Game implements Comparable<Game> {
+ private String id;
+ private String title;
+ private int selectionSize;
+
+ private List<Expansion> expansions;
+ private List<Card> cards;
+
+ // cards also registered in 'cards', but its expansion is different.
+ private List<Card> duplicateCards;
+
+ /**
+ * Constructs an object.
+ *
+ * The created object has the specified ID, title and number of
+ * cards used at playing the game.
+ *
+ * @param idArg ID
+ * @param titleArg game title
+ * @param selectionSizeArg the number of selections
+ */
+ public Game(String idArg, String titleArg, int selectionSizeArg) {
+ assert idArg != null;
+ assert titleArg != null;
+ assert selectionSizeArg > 0;
+
+ id = idArg;
+ title = titleArg;
+ selectionSize = selectionSizeArg;
+
+ expansions = new LinkedList<Expansion>();
+ cards = new LinkedList<Card>();
+ duplicateCards = new LinkedList<Card>();
+ }
+
+ /**
+ * Constructs a copy of the specified object.
+ *
+ * @param game the source object
+ */
+ public Game(Game game) {
+ assert game != null;
+
+ id = game.id;
+ title = game.title;
+ selectionSize = game.selectionSize;
+
+ expansions = new LinkedList<Expansion>(game.expansions);
+ cards = new LinkedList<Card>(game.cards);
+ duplicateCards = new LinkedList<Card>(game.duplicateCards);
+ }
+
+ /**
+ * Returns ID of this game.
+ *
+ * @return ID
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns a title of this game.
+ *
+ * @return a title
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Returns the number of selections.
+ *
+ * The term "the number of selections" means that the number of
+ * kinds of cards used at playing the game.
+ *
+ * @return the number of selections
+ */
+ public int getSelectionSize() {
+ return selectionSize;
+ }
+
+ /**
+ * Removes all expansions and cards registered in this object.
+ */
+ public synchronized void clear() {
+ expansions.clear();
+ cards.clear();
+ duplicateCards.clear();
+ }
+
+ /**
+ * Returns the number of expansions registered in this object.
+ *
+ * @return the number of expansions
+ */
+ public int getExpansionsSize() {
+ return expansions.size();
+ }
+
+ /**
+ * Returns a registered expansion at the specified position.
+ *
+ * @param index position of the expansion
+ * @return an expansion
+ */
+ public Expansion getExpansion(int index) {
+ return expansions.get(index);
+ }
+
+ /**
+ * Returns a registered expansion with the specified ID.
+ *
+ * If no card matches, it returns null.
+ *
+ * @param id ID of the expansion
+ * @return the found expansion, or null
+ */
+ public Expansion getExpansion(String id) {
+ assert id != null;
+
+ ListIterator<Expansion> it = expansions.listIterator();
+ while (it.hasNext()) {
+ Expansion x = it.next();
+ if (x.getId().equals(id))
+ return x;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a registered expansion, equal to the specified one.
+ *
+ * If no expansion matches, it returns null.
+ *
+ * @param expansionArg an expansion to be searched
+ * @return the found expansion, or null
+ */
+ public Expansion getExpansion(Expansion expansionArg) {
+ assert expansionArg != null;
+
+ ListIterator<Expansion> it = expansions.listIterator();
+ while (it.hasNext()) {
+ Expansion x = it.next();
+ if (x.equals(expansionArg))
+ return x;
+ }
+ return null;
+ }
+
+ /**
+ * Returns all registered expansions as a list.
+ *
+ * The expansions are sorted by their preference values.
+ *
+ * @return a list of expansions
+ */
+ public List<Expansion> getExpansions() {
+ return new LinkedList<Expansion>(expansions);
+ }
+
+ /**
+ * Registers the specified expansion to this object.
+ *
+ * If the specified expansion has already been registered (i.e. they
+ * have the same ID), this method does nothing.
+ *
+ * @param expansionArg an expansion to be registered
+ * @return true if the method registers the specified expansion
+ */
+ public synchronized boolean addExpansion(Expansion expansionArg) {
+ assert expansionArg != null;
+
+ if (getExpansion(expansionArg) != null)
+ return false;
+ ListIterator<Expansion> it = expansions.listIterator();
+ while (it.hasNext()) {
+ Expansion x = it.next();
+ if (x.preference > expansionArg.preference) {
+ it.previous();
+ break;
+ }
+ }
+ it.add(expansionArg);
+ return true;
+ }
+
+ /**
+ * Registers all the specified expansions to this object.
+ *
+ * If the specified expansion has already been registered (i.e. they have
+ * the same ID), this method skips it.
+ *
+ * @param expansionsArg a list of expansions to be registered
+ * @return true if the method registers at least one expansion
+ */
+ public synchronized boolean
+ addExpansions(List<Expansion> expansionsArg) {
+ assert expansionsArg != null;
+
+ boolean changed = false;
+ ListIterator<Expansion> it = expansionsArg.listIterator();
+ while (it.hasNext()) {
+ if (addExpansion(it.next()))
+ changed = true;
+ }
+ return changed;
+ }
+
+ /**
+ * Removes the specified exansion from this object.
+ *
+ * If the specified expansion is registered (i.e. they have the same ID)
+ * in this object, this method removes it. Otherwise, this method does
+ * nothing.
+ *
+ * This method also removes all cards provided by the specified expansion.
+ * If a card is provided by both the specified expansion and another one,
+ * the card provided by another one is still remained in this object.
+ *
+ * @param expansionArg an expansion to be removed
+ * @return true if this method removes the expansion
+ */
+ public synchronized boolean removeExpansion(Expansion expansionArg) {
+ assert expansionArg != null;
+
+ if (!expansions.remove(expansionArg))
+ return false;
+
+ ListIterator<Card> it = duplicateCards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (!c.getExpansion().equals(expansionArg))
+ continue;
+ it.remove();
+ }
+
+ List<Card> cl = new LinkedList<Card>();
+ it = cards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (!c.getExpansion().equals(expansionArg))
+ continue;
+ it.remove();
+
+ ListIterator<Card> it2 = duplicateCards.listIterator();
+ while (it2.hasNext()) {
+ Card c2 = it2.next();
+ if (c2.equals(c)) {
+ it2.remove();
+ cl.add(c2);
+ break;
+ }
+ }
+ }
+ cards.addAll(cl);
+
+ return true;
+ }
+
+ /**
+ * Removes all the specified exansions from this object.
+ *
+ * If the specified expansion is registered (i.e. they have the same ID)
+ * in this object, this method removes it. Otherwise, this method skips
+ * it.
+ *
+ * This method also removes all cards provided by the specified
+ * expansions. If a card is provided by both the specified expansion and
+ * another one, the card provided by another one is still remained in
+ * this object.
+ *
+ * @param expansionsArg a list of expansions to be removed
+ * @return true if this method removes at least one expansion
+ */
+ public synchronized boolean
+ removeExpansions(List<Expansion> expansionsArg) {
+ assert expansionsArg != null;
+
+ boolean changed = false;
+ ListIterator<Expansion> it = expansionsArg.listIterator();
+ while (it.hasNext()) {
+ if (removeExpansion(it.next()))
+ changed = true;
+ }
+ return changed;
+ }
+
+ /**
+ * Removes all exansions not specififed by the argument.
+ *
+ * If the specified expansion is registered (i.e. they have the same ID)
+ * in this object, this method remains it. This method removes all
+ * unspecified expansions from this object.
+ *
+ * This method also removes all cards provided by unspecified expansions.
+ * If a card is provided by both unspecified and the specified expansions,
+ * the card provided by the specified one is still remained in this
+ * object.
+ *
+ * @param expansionsArg a list of expansions
+ * @return true if this method removes at least one expansion
+ */
+ public synchronized boolean
+ removeOtherExpansions(List<Expansion> expansionsArg) {
+ assert expansionsArg != null;
+
+ List<Expansion> xl = new LinkedList<Expansion>();
+ ListIterator<Expansion> it = expansions.listIterator();
+ while (it.hasNext()) {
+ Expansion x = it.next();
+ if (expansionsArg.indexOf(x) < 0)
+ xl.add(x);
+ }
+ return removeExpansions(xl);
+ }
+
+ /**
+ * Returns the number of registered cards.
+ *
+ * @return the number of cards
+ */
+ public int getCardsSize() {
+ return cards.size();
+ }
+
+ /**
+ * Returns the number of registered cards of the specified expansion.
+ *
+ * @return the number of cards
+ */
+ public int getCardsSize(Expansion expansionArg) {
+ int size = 0;
+
+ if (!expansions.contains(expansionArg))
+ return 0;
+
+ ListIterator<Card> it = cards.listIterator();
+ while (it.hasNext()) {
+ if (it.next().getExpansion().equals(expansionArg))
+ size++;
+ }
+
+ it = duplicateCards.listIterator();
+ while (it.hasNext()) {
+ if (it.next().getExpansion().equals(expansionArg))
+ size++;
+ }
+
+ return size;
+ }
+
+ /**
+ * Returns a registered card at the specified position.
+ *
+ * @param index position of the card
+ * @return a card
+ */
+ public Card getCard(int index) {
+ return cards.get(index);
+ }
+
+ /**
+ * Returns a registered card with the specified ID.
+ *
+ * If no card matches, it returns null.
+ *
+ * @param id ID of the card
+ * @return the found card, or null
+ */
+ public Card getCard(String id) {
+ assert id != null;
+
+ ListIterator<Card> it = cards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.getId().equals(id))
+ return c;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a registered card, equal to the specified one.
+ *
+ * If no card matches, it returns null.
+ *
+ * @param cardArg a card to be searched
+ * @return the found card, or null
+ */
+ public Card getCard(Card cardArg) {
+ assert cardArg != null;
+
+ ListIterator<Card> it = cards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.equals(cardArg))
+ return c;
+ }
+ return null;
+ }
+
+ /**
+ * Returns all cards registered in this object as a list.
+ *
+ * @return a list of cards
+ */
+ public List<Card> getCards() {
+ return new LinkedList<Card>(cards);
+ }
+
+ /**
+ * Returns all cards provided by the specified expansion.
+ *
+ * This method searches registered cards and returns all cards provided
+ * by the specified expansion as a list form.
+ *
+ * It returns null if the specified expansion is not registered in this
+ * object. It returns an empty list if the specified expansion is
+ * registered but no card of the expansion is registered.
+ *
+ * @param expansionArg an expansion
+ * @return a list of cards, or null
+ */
+ public List<Card> getCards(Expansion expansionArg) {
+ assert expansionArg != null;
+
+ if (!expansions.contains(expansionArg))
+ return null;
+
+ List<Card> cl = new LinkedList<Card>();
+ ListIterator<Card> it = cards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.getExpansion().equals(expansionArg))
+ cl.add(c);
+ }
+
+ it = duplicateCards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.getExpansion().equals(expansionArg))
+ cl.add(c);
+ }
+
+ return cl;
+ }
+
+ /**
+ * Registers the specified card to this object.
+ *
+ * If the expansion of the card is not registered, this method also
+ * registers the expansion.
+ *
+ * If the specified card has already been registered (i.e. they have
+ * the same ID), this method doesn't register it again. However, if it
+ * is provided by a different expansion, this method memories the
+ * specified card as "a duplicate card". When an expansion of a
+ * registered card is removed, also the card is removed but corresponding
+ * duplicate cards, if exists, are still remained and one of them is
+ * registered instead.
+ *
+ * @param cardArg a card to be registered
+ * @return true if this method registers a card or memories it as a
+ * duplicate card.
+ */
+ public synchronized boolean addCard(Card cardArg) {
+ assert cardArg != null;
+
+ Expansion x = cardArg.getExpansion();
+
+ Card c = getCard(cardArg);
+ if (c == null) {
+ addExpansion(x);
+ cards.add(cardArg);
+ return true;
+ } else if (!c.getExpansion().equals(x)) {
+ boolean found = false;
+ ListIterator<Card> it = duplicateCards.listIterator();
+ while (it.hasNext()) {
+ c = it.next();
+ if (c.equals(cardArg) && c.getExpansion().equals(x)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ duplicateCards.add(cardArg);
+ return found;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Registers all the specified cards to this object.
+ *
+ * If the expansion of the card is not registered, this method also
+ * registers the expansion.
+ *
+ * If the specified card has already been registered (i.e. they have
+ * the same ID), this method doesn't register it again. However, if it
+ * is provided by a different expansion, this method memories the
+ * specified card as "a duplicate card". When an expansion of a
+ * registered card is removed, also the card is removed but corresponding
+ * duplicate cards, if exists, are still remained and one of them is
+ * registered instead.
+ *
+ * @param cardsArg cards to be registered
+ * @return true if this method registers at least one card or
+ * memories at least one card as a duplicate card.
+ */
+ public synchronized boolean addCards(List<Card> cardsArg) {
+ assert cardsArg != null;
+
+ boolean changed = false;
+ ListIterator<Card> it = cardsArg.listIterator();
+ while (it.hasNext()) {
+ if (addCard(it.next()))
+ changed = true;
+ }
+ return changed;
+ }
+
+ /**
+ * Removes the specified card from this object.
+ *
+ * If the specified card is registered (i.e. they have the same ID) in
+ * this object, this method removes the card. Otherwise, this method
+ * does nothing.
+ *
+ * @param cardArg a card to be removed
+ * @return true if this method removes a card
+ */
+ public synchronized boolean removeCard(Card cardArg) {
+ assert cardArg != null;
+
+ boolean changed = false;
+ ListIterator<Card> it = cards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.equals(cardArg)) {
+ it.remove();
+ changed = true;
+ break;
+ }
+ }
+
+ it = duplicateCards.listIterator();
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.equals(cardArg)) {
+ it.remove();
+ changed = true;
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * Removes all the specified cards from this object.
+ *
+ * If the specified card is registered (i.e. they have the same ID) in
+ * this object, this method removes the card. Otherwise, this method
+ * skips the card.
+ *
+ * @param cardsArg a list of cards to be removed
+ * @return true if this method removes at least one card
+ */
+ public synchronized boolean removeCards(List<Card> cardsArg) {
+ assert cardsArg != null;
+
+ boolean changed = false;
+ ListIterator<Card> it = cardsArg.listIterator();
+ while (it.hasNext()) {
+ if (removeCard(it.next()))
+ changed = true;
+ }
+ return changed;
+ }
+
+ /**
+ * Returns true if this object is equal the specified one.
+ *
+ * It returns true, if both objects have the same ID,
+ *
+ * @param o an object to be compared
+ * @return true if both objects have the same ID
+ */
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+ if (o == null || !(o instanceof Game))
+ return false;
+ Game x = (Game)o;
+ return id.equals(x.id);
+ }
+
+ /**
+ * Returns a hash value of this object.
+ *
+ * @return a hash value
+ */
+ @Override
+ public int hashCode() {
+ if (id == null)
+ return 0;
+ else
+ return id.hashCode();
+ }
+
+ /**
+ * Compares this object and the specified one.
+ *
+ * It compares ID of both objects. It returns an integer less than,
+ * equal to, or greater than zero if ID of this object is respectively
+ * less than, equal to, or greater than that of the specified object.
+ *
+ * @param game a game object to be compared
+ * @return a negative integer, zero, or a positive integer according
+ * with result of the comparison.
+ */
+ public int compareTo(Game game) {
+ return id.compareTo(game.id);
+ }
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a title of this object.
+ */
+ @Override
+ public String toString() {
+ return title;
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * <p>
+ * Builds 'Game' objects.
+ * </p>
+ *
+ * <p>
+ * Since 'GameBuilder' itself is an abstract class, use a concrete sub
+ * class to build objects.
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public abstract class GameBuilder {
+ protected Locale locale;
+
+ /**
+ * Constructs an object.
+ *
+ * The current value of the default locale is used for building
+ * a list of games.
+ */
+ public GameBuilder() {
+ locale = (Locale)Locale.getDefault().clone();
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * The current value of the specified locale object is used for builing
+ * 'Game' objects.
+ *
+ * @param localeArg a locale object
+ */
+ public GameBuilder(Locale localeArg) {
+ assert localeArg != null;
+ locale = (Locale)localeArg.clone();
+ }
+
+ /**
+ * Builds a 'Game' object with the specified ID.
+ *
+ * If game data about the specified ID are found, then this method
+ * constructs a new 'Game' object, puts the data into the object and
+ * returns it. Otherwise, it returns null.
+ *
+ * @return the built game object, or null
+ */
+ public abstract Game build(String id)
+ throws GameBuilderException;
+
+ /**
+ * Builds the specified 'Game' object.
+ *
+ * If data about the specified game are found, this method registers
+ * the found expansions and cards to the specified game object using
+ * Game.AddExpansion() and Game.AddCard() methods.
+ *
+ * @param game a games to be built
+ * @return the argument 'game'
+ */
+ public abstract Game build(Game game)
+ throws GameBuilderException;
+
+ /**
+ * Builds one or more game objects.
+ *
+ * If game data are found, then this method constructs new 'Game'
+ * objects, puts the data into the objects and returns all of them as
+ * a list. Otherwise, it returns null.
+ *
+ * @return the built List<Game> object
+ */
+ public List<Game> buildAll()
+ throws GameBuilderException {
+ return buildAll(new ArrayList<Game>());
+ }
+
+ /**
+ * Builds one or more game objects.
+ *
+ * If data about a game in 'gameList' are found, this method adds the
+ * found expansions and cards to the corresponding specified game objects
+ * using Game.AddExpansion(), Game.AddExpansions(), Game.AddCard() and
+ * Game.AddCards() methods.
+ *
+ * If data about a game not in 'gameList' are found, this method
+ * constructs a new 'Game' object, puts the data into the object and
+ * appends the object to 'gameList'.
+ *
+ * @param gameList a list of games
+ * @return the argument 'gameList'
+ */
+ public abstract List<Game> buildAll(List<Game> gameList)
+ throws GameBuilderException;
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a string "CLASS-NAME for LOCALE"
+ */
+ @Override
+ public String toString() {
+ return String.format("%s for %s",
+ getClass().getSimpleName(), locale.toString());
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+/**
+ * <p>
+ * Exception class for GameBuilder.
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public abstract class GameBuilderException extends Exception {
+ /**
+ * Constructs an object.
+ */
+ public GameBuilderException() {
+ super();
+ }
+
+ /**
+ * Constructs an object with the specified message.
+ *
+ * @param message a message
+ */
+ public GameBuilderException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs an object with the specified message and cause.
+ *
+ * @param message a message
+ * @param cause cause
+ */
+ public GameBuilderException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructs an object with the specified cause.
+ *
+ * @param cause cause
+ */
+ public GameBuilderException(Throwable cause) {
+ super(cause);
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Locale;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+/**
+ * <p>
+ * Concrete class of 'GameBuilder'.
+ * </p>
+ *
+ * <p>
+ * Its object builds 'Game' object by reading XML files which define game
+ * data.
+ * </p>
+ *
+ * <p>
+ * For each game, it is required to prepare some XML files, a file
+ * '__game.xml' (starting with two '_') and some '<dfn>expansion</dfn>.xml'
+ * files. '__game.xml' defines basic information of the game, such as a
+ * title of the game. '<dfn>expansion</dfn>.xml' defines an expansion of
+ * the game and cards provided by the expansion.
+ * </p>
+ *
+ * <p>
+ * Those files must be placed at the same directory, and its directory must
+ * be '<dfn>game</dfn>/<dfn>language</dfn>_<dfn>country</dfn>'
+ * (e.g. 'dominon/en_US') or '<dfn>game</dfn>/<dfn>language</dfn>'
+ * (e.g. 'dominion/en'). <dfn>game</dfn> is ID of the game.
+ * '<dfn>language</dfn>' and '<dfn>country</dfn>' are values returned from
+ * getLanguage() and getCountry() methods of 'java.util.Locale' class.
+ * According with locale setting, an object of this class reads XML files
+ * at a corresponding directory.
+ * </p>
+ *
+ * <p>
+ * The following is an example of file hierarchy. They define two games
+ * 'dominon' and 'ascension'. They provide data for 'English - United States'
+ * and 'Japanese' but Japanese data lacks definition of the game 'ascension'
+ * and the expansion 'intrigue' of 'dominion'.
+ * </p>
+ *
+ * <pre><code>
+ * games/
+ * + dominion/
+ * + en_US/
+ * + __game.xml
+ * + dominion.xml
+ * + intrigue.xml
+ * + ja/
+ * + __game.xml
+ * + dominion.xml
+ * + ascension/
+ * + en_US/
+ * + __game.xml
+ * + ascension.xml
+ * </code></pre>
+ *
+ * <p>
+ * To build 'Game' object, specify the path to 'game/' directory as an
+ * argument to constructor of this class, like:
+ * </p>
+ *
+ * <pre><code>
+ * GameBuilder builder = new GameBuilderXMLFile("games");
+ * List<Game> gameList = builder.buildAll();
+ * </code></pre>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public class GameBuilderXMLFile extends GameBuilder {
+ //
+ // Expected attribute values.
+ //
+ class Expectancy {
+ String gameId;
+ String expansionId;
+ String language;
+ String country;
+
+ Expectancy() {
+ gameId = "";
+ expansionId = "";
+ language = "";
+ country = "";
+ }
+ }
+
+ // Path to a top directory of XML files.
+ private File directoryFile;
+
+ /**
+ * Constructs an object.
+ *
+ * It sets the top directory of XML files to '.' (the current directory)
+ * and sets locale to the current value of the default locale.
+ */
+ public GameBuilderXMLFile() {
+ super();
+ directoryFile = new File(".");
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * It sets the top directory of XML files to the specified path and
+ * sets locale to the current value of the default locale.
+ *
+ * @param dir path to the top directory
+ */
+ public GameBuilderXMLFile(File dir) {
+ super();
+
+ assert dir != null;
+ directoryFile = dir;
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * It sets the top directory of XML files to the specified path and
+ * sets locale to the current value of the default locale.
+ *
+ * @param dir path to the top directory
+ */
+ public GameBuilderXMLFile(String dir) {
+ this(new File(dir));
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * It sets the top directory of XML files and locale to the specified
+ * values.
+ *
+ * @param dir path to the top directory
+ * @param localeArg a locale
+ */
+ public GameBuilderXMLFile(File dir, Locale localeArg) {
+ super(localeArg);
+
+ assert dir != null;
+ directoryFile = dir;
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * It sets the top directory of XML files and locale to the specified
+ * values.
+ *
+ * @param dir path to the top directory
+ * @param localeArg a locale
+ */
+ public GameBuilderXMLFile(String dir, Locale localeArg) {
+ this(new File(dir), localeArg);
+ }
+
+ @Override
+ public Game build(String id)
+ throws GameBuilderException {
+ Expectancy exp = new Expectancy();
+ File file = findGameXMLFile(id, exp);
+ if (file == null)
+ return null;
+ Game g = readGameXMLFile(file, exp);
+ readExpansionXMLFiles(g, exp);
+ return g;
+ }
+
+ @Override
+ public Game build(Game game)
+ throws GameBuilderException {
+ Expectancy exp = new Expectancy();
+ File file = findGameXMLFile(game.getId(), exp);
+ if (file == null)
+ return game;
+ readExpansionXMLFiles(game, exp);
+ return game;
+ }
+
+ @Override
+ public List<Game> buildAll(List<Game> gameList)
+ throws GameBuilderException {
+ assert gameList != null;
+
+ int initListSize = gameList.size();
+ readXMLFiles(gameList);
+
+ ListIterator<Game> it = gameList.listIterator(initListSize);
+ while (it.hasNext()) {
+ Game g = it.next();
+ if (g.getCardsSize() == 0)
+ it.remove();
+ }
+
+ return gameList;
+ }
+
+ //
+ // XML filename.
+ //
+ private static final String XML_FILE_NAME = "__game.xml";
+
+ //
+ // XML format versions this program supports.
+ //
+ private static final String[] SUPPORTED_XML_FORMAT_VERSIONS = {"1"};
+
+ //
+ // Internal method for buildAll().
+ //
+ // It reads all '__game.xml' files under 'directoryFile', parses the
+ // data and puts the data about games into 'gameList'.
+ //
+ private void readXMLFiles(List<Game> gameList)
+ throws GameBuilderException {
+ File[] subdirs = directoryFile.listFiles();
+ if (subdirs == null)
+ return;
+
+ for (File subdir : subdirs) {
+ Expectancy exp = new Expectancy();
+ String subdirName = subdir.getName();
+ File file = findGameXMLFile(subdirName, exp);
+ if (file == null)
+ continue;
+ if (gameList.indexOf(new Game(subdirName, "", 0)) >= 0)
+ continue;
+ Game game = readGameXMLFile(file, exp);
+ readExpansionXMLFiles(game, exp);
+ gameList.add(game);
+ }
+ }
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It searches 'subDirName' in 'directoryFile' for '__game.xml' file.
+ // If found, this method returns 'File' object of the XML file.
+ // Otherwise, it returns null.
+ //
+ private File findGameXMLFile(String gameId, Expectancy exp) {
+ String language = locale.getLanguage();
+ String country = locale.getCountry();
+ char sep = File.separatorChar;
+
+ File file1 = new File(String.format("%s%c%s%c%s_%s%c%s",
+ directoryFile.getAbsolutePath(), sep, gameId, sep,
+ language, country, sep, XML_FILE_NAME));
+ if (file1.isFile()) {
+ exp.gameId = gameId;
+ exp.language = language;
+ exp.country = country;
+ return file1;
+ }
+
+ File file2 = new File(String.format("%s%c%s%c%s%c%s",
+ directoryFile.getAbsolutePath(), sep, gameId, sep,
+ language, sep, XML_FILE_NAME));
+ if (file2.isFile()) {
+ exp.gameId = gameId;
+ exp.language = language;
+ exp.country = "";
+ return file2;
+ }
+
+ return null;
+ }
+
+ //
+ // Invalid price value (used for an initial value of price variable.)
+ //
+ private static final int INVALID_SELECTION_SIZE = -1;
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It reads game data from 'file', parses the data as XML, and then puts
+ // the data about a game into 'gameList'.
+ //
+ private Game readGameXMLFile(File file, Expectancy exp)
+ throws GameBuilderException {
+ //
+ // Open a file and create an XML parser.
+ //
+ FileReader reader = null;
+ XmlPullParser parser;
+ Game game = null;
+ String fileTitle = getPartialPath(file, 2);
+ try {
+ reader = new FileReader(file);
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ factory.setNamespaceAware(false);
+ parser = factory.newPullParser();
+ parser.setInput(reader);
+ game = readGameXMLData(parser, fileTitle, exp);
+ } catch (GameBuilderXMLFileException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new GameBuilderXMLFileException(e.getCause(), fileTitle,
+ "%s", e.getMessage());
+ } finally {
+ try {
+ if (reader != null)
+ reader.close();
+ } catch (IOException e) {
+ ; // do nothing;
+ }
+ }
+
+ return game;
+ }
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It parses XML data, and then returns the data as a 'Game' object.
+ //
+ private Game readGameXMLData(XmlPullParser parser, String fileTitle,
+ Expectancy exp)
+ throws GameBuilderException {
+ //
+ // Get the first event from the XML parser.
+ //
+ Game game = null;
+ String tag = "";
+ String prevTag = "";
+ String id = null;
+ String title = null;
+ int selectionSize = INVALID_SELECTION_SIZE;
+
+ int event;
+ try {
+ event = parser.getEventType();
+ } catch (Exception e) {
+ throw new GameBuilderXMLFileException(e.getCause(), fileTitle,
+ "failed to parse XML, %s", e.getMessage());
+ }
+
+ //
+ // Inteprets the event.
+ //
+ while (event != XmlPullParser.END_DOCUMENT) {
+ int lineNo = parser.getLineNumber();
+ if (event == XmlPullParser.START_TAG) {
+ tag = parser.getName();
+ if (prevTag.equals("")) {
+ if (!tag.equals("game")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<game> expected, but got <%s>", tag);
+ }
+ validateGameStartTag(parser, fileTitle, lineNo, exp);
+ id = getAttribute(parser, "id");
+ } else if (prevTag.equals("game")) {
+ if (!tag.equals("game_title")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<game_title> expected, but got <%s>",
+ tag);
+ }
+ } else if (prevTag.equals("/game_title")) {
+ if (!tag.equals("selection_size")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<selection_size> expected, but got <%s>",
+ tag);
+ }
+ } else {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "unknown tag <%s>", tag);
+ }
+ prevTag = tag;
+ } else if (event == XmlPullParser.END_TAG) {
+ tag = "/" + parser.getName();
+ if (tag.equals("/game")) {
+ assert id != null;
+ assert title != null;
+ assert selectionSize > 0;
+ game = new Game(new Game(id, title, selectionSize));
+ }
+ prevTag = tag;
+ } else if (event == XmlPullParser.TEXT) {
+ String text = parser.getText();
+ if (tag.equals("game_title"))
+ title = text;
+ else if (tag.equals("selection_size")) {
+ try {
+ selectionSize = Integer.parseInt(text);
+ } catch (NumberFormatException e) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "not an integer for <selection_size>");
+ }
+ if (selectionSize <= 0) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<selection_size> is not greater than 0");
+ }
+ }
+ }
+
+ //
+ // Get the next event from the XML parser.
+ //
+ try {
+ event = parser.next();
+ } catch (Exception e) {
+ throw new GameBuilderXMLFileException(e.getCause(), fileTitle,
+ "failed to parse XML, %s", e.getMessage());
+ }
+ }
+
+ if (!tag.equals("/game")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ parser.getLineNumber(), "unexpected eof");
+ }
+
+ return game;
+ }
+
+ //
+ // Validate attributes of a <game> tag.
+ //
+ private void validateGameStartTag(XmlPullParser parser,
+ String fileTitle, int lineNo, Expectancy exp)
+ throws GameBuilderException {
+ //
+ // Validate 'id'.
+ //
+ String id = getAttribute(parser, "id");
+ if (id == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'id' attribute in <game>");
+ }
+ if (!id.equals(exp.gameId)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'id' is different from a directory name");
+ }
+
+ //
+ // Validate 'format_version'
+ //
+ String version = getAttribute(parser, "format_version");
+ if (version == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'format_version' attribute in <game>");
+ }
+ boolean is_supported_version = false;
+ for (String v : SUPPORTED_XML_FORMAT_VERSIONS) {
+ if (v.equals(version)) {
+ is_supported_version = true;
+ break;
+ }
+ }
+ if (!is_supported_version) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "format_version '%s' not supported", version);
+ }
+
+ //
+ // Validate 'language'.
+ //
+ String language = getAttribute(parser, "language");
+ if (language == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'language' attribute in <game>");
+ }
+ if (!language.equals(exp.language)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'language' is different from a file name");
+ }
+
+ //
+ // Validate 'country'.
+ //
+ String country = getAttribute(parser, "country");
+ if (country == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'country' attribute in <game>");
+ }
+ if (!country.equals(exp.country)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'country' is different from a file name");
+ }
+ }
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It reads all expansion-definition XML files at 'directoryFile',
+ // parses the data and puts the data about expansions and cards into
+ // 'component'.
+ //
+ private void readExpansionXMLFiles(Game game, Expectancy gameExp)
+ throws GameBuilderException {
+ //
+ // Get expected data values.
+ //
+ char sep = File.separatorChar;
+
+ //
+ // Check if a sub-directory with the locale name exists.
+ //
+ File dir;
+ if (gameExp.country.isEmpty()) {
+ dir = new File(String.format("%s%c%s%c%s",
+ directoryFile.getAbsolutePath(), sep, gameExp.gameId, sep,
+ gameExp.language));
+ } else {
+ dir = new File(String.format("%s%c%s%c%s_%s",
+ directoryFile.getAbsolutePath(), sep, gameExp.gameId, sep,
+ gameExp.language, gameExp.country));
+ }
+ if (!dir.isDirectory())
+ return;
+
+ //
+ // Read all XML files under the locale sub-directory.
+ //
+ File[] files = dir.listFiles();
+ if (files == null)
+ return;
+ for (File file : files) {
+ if (!file.isFile())
+ continue;
+ String fileName = file.getName();
+ if (fileName.startsWith("_") || !fileName.endsWith(".xml"))
+ continue;
+ Expectancy exp = new Expectancy();
+ exp.gameId = gameExp.gameId;
+ exp.expansionId = fileName.substring(0, fileName.length() - 4);
+ exp.language = gameExp.language;
+ exp.country = gameExp.country;
+ readExpansionXMLFile(game, file, exp);
+ }
+ }
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It reads data from 'file', parses the data as XML, and then puts
+ // the data about expansions and cards into 'game'.
+ //
+ private void readExpansionXMLFile(Game game, File file, Expectancy exp)
+ throws GameBuilderException {
+ //
+ // Open a file and create an XML parser.
+ //
+ FileReader reader = null;
+ XmlPullParser parser;
+ String fileTitle = getPartialPath(file, 2);
+ try {
+ reader = new FileReader(file);
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ factory.setNamespaceAware(false);
+ parser = factory.newPullParser();
+ parser.setInput(reader);
+ readExpansionXMLData(game, parser, fileTitle, exp);
+ } catch (GameBuilderXMLFileException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new GameBuilderXMLFileException(e.getCause(), fileTitle,
+ "%s", e.getMessage());
+ } finally {
+ try {
+ if (reader != null)
+ reader.close();
+ } catch (IOException e) {
+ ; // do nothing;
+ }
+ }
+ }
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It parses XML data, and then puts the data into 'game'.
+ //
+ private void readExpansionXMLData(Game game, XmlPullParser parser,
+ String fileTitle, Expectancy exp)
+ throws GameBuilderException {
+ //
+ // Get the first event from the XML parser.
+ //
+ Expansion expansion = null;
+ String tag = "";
+ String prevTag = "";
+ String expansionId = null;
+ String expansionTitle = null;
+ String cardId = null;
+ String cardTitle = null;
+ String cardPrice = null;
+ int preference = Integer.MAX_VALUE;
+ boolean expansionAdded = false;
+
+ int event;
+ try {
+ event = parser.getEventType();
+ } catch (Exception e) {
+ throw new GameBuilderXMLFileException(e.getCause(), fileTitle,
+ "failed to parse XML, %s", e.getMessage());
+ }
+
+ //
+ // Inteprets the event.
+ //
+ while (event != XmlPullParser.END_DOCUMENT) {
+ int lineNo = parser.getLineNumber();
+ if (event == XmlPullParser.START_TAG) {
+ tag = parser.getName();
+ if (prevTag.equals("")) {
+ if (!tag.equals("expansion")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<expansion> expected, but got <%s>", tag);
+ }
+ validateExpansionStartTag(parser, fileTitle, lineNo, exp);
+ expansionId = getAttribute(parser, "id");
+ } else if (prevTag.equals("expansion")) {
+ if (!tag.equals("preference")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<preference> expected, but got <%s>",
+ tag);
+ }
+ } else if (prevTag.equals("/preference")) {
+ if (!tag.equals("expansion_title")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<expansion_title> expected, but got <%s>",
+ tag);
+ }
+ } else if (prevTag.equals("/expansion_title")
+ || prevTag.equals("/card")) {
+ if (!tag.equals("card")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<card> expected, but got <%s>", tag);
+ }
+ cardId = getAttribute(parser, "id");
+ if (cardId == null) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "no 'id' attribute in <expansion>");
+ }
+ } else if (prevTag.equals("card")) {
+ if (!tag.equals("title")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<title> expected, but got <%s>", tag);
+ }
+ } else if (prevTag.equals("/title")) {
+ if (!tag.equals("price")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "<price> expected, but got <%s>", tag);
+ }
+ } else {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "unknown tag <%s>", tag);
+ }
+ prevTag = tag;
+ } else if (event == XmlPullParser.END_TAG) {
+ tag = "/" + parser.getName();
+ if (tag.equals("/expansion_title")) {
+ assert expansionId != null;
+ assert expansionTitle != null;
+ expansion = new Expansion(expansionId, expansionTitle,
+ preference);
+ expansionAdded = game.addExpansion(expansion);
+ } else if (tag.equals("/card")) {
+ assert expansion != null;
+ assert cardId != null;
+ assert cardTitle != null;
+ assert cardPrice != null;
+ Card card = new Card(cardId, cardTitle, cardPrice,
+ expansion);
+ game.addCard(card);
+ cardId = null;
+ cardTitle = null;
+ cardPrice = null;
+ }
+ prevTag = tag;
+ } else if (event == XmlPullParser.TEXT) {
+ String text = parser.getText();
+ if (tag.equals("expansion_title"))
+ expansionTitle = text;
+ else if (tag.equals("title"))
+ cardTitle = text;
+ else if (tag.equals("price"))
+ cardPrice = text;
+ else if (tag.equals("preference")) {
+ try {
+ preference = Integer.parseInt(text);
+ } catch (NumberFormatException e) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ lineNo, "not an integer for 'preference'");
+ }
+ }
+ }
+
+ //
+ // Get the next event from the XML parser.
+ //
+ try {
+ event = parser.next();
+ } catch (Exception e) {
+ throw new GameBuilderXMLFileException(e.getCause(), fileTitle,
+ "failed to parse XML, %s", e.getMessage());
+ }
+ }
+
+ if (!tag.equals("/expansion")) {
+ throw new GameBuilderXMLFileException(fileTitle,
+ parser.getLineNumber(), "unexpected eof");
+ }
+
+ if (expansionAdded && game.getCardsSize(expansion) == 0)
+ game.removeExpansion(expansion);
+ }
+
+ //
+ // Validate attributes of an <expansion> tag.
+ //
+ private void validateExpansionStartTag(XmlPullParser parser,
+ String fileTitle, int lineNo, Expectancy exp)
+ throws GameBuilderException {
+ //
+ // Validate 'id'.
+ //
+ String expansionId = getAttribute(parser, "id");
+ if (expansionId == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'id' attribute in <expansion>");
+ }
+ if (!expansionId.equals(exp.expansionId)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'id' is different from a file name");
+ }
+
+ //
+ // Validate 'game_id'.
+ //
+ String gameId = getAttribute(parser, "game_id");
+ if (gameId == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'game_id' attribute in <expansion>");
+ }
+ if (!gameId.equals(exp.gameId)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'game_id' is different from a file name");
+ }
+
+ //
+ // Validate 'format_version'
+ //
+ String version = getAttribute(parser, "format_version");
+ if (version == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'format_version' attribute in <game>");
+ }
+ boolean is_supported_version = false;
+ for (String v : SUPPORTED_XML_FORMAT_VERSIONS) {
+ if (v.equals(version)) {
+ is_supported_version = true;
+ break;
+ }
+ }
+ if (!is_supported_version) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "format_version '%s' not supported", version);
+ }
+
+ //
+ // Validate 'language'.
+ //
+ String language = getAttribute(parser, "language");
+ if (language == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'language' attribute in <expansion>");
+ }
+ if (!language.equals(exp.language)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'language' is different from a file name");
+ }
+
+ //
+ // Validate 'country'.
+ //
+ String country = getAttribute(parser, "country");
+ if (country == null) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "no 'country' attribute in <expansion>");
+ }
+ if (!country.equals(exp.country)) {
+ throw new GameBuilderXMLFileException(fileTitle, lineNo,
+ "'country' is different from a file name");
+ }
+ }
+
+ //
+ // Get a file name with last 'depth' levels of an absolute path of
+ // 'file'.
+ //
+ // For example,
+ //
+ // getPartialPath(new File("/a/b/c/e/f.txt"), 2)
+ //
+ // reutrns "c/d/f.txt", the file name ("f.txt") with last 2 levels of
+ // an absolute path ("d/e") of "/a/b/c/e/f.txt".
+ //
+ private String getPartialPath(File file, int depth) {
+ String result = file.getName();
+
+ File parent = file.getParentFile();
+ for (int i = 0; i < depth && parent != null; i++) {
+ result = parent.getName() + File.separator + result;
+ parent = parent.getParentFile();
+ }
+
+ return result;
+ }
+
+ //
+ // Internal method for build() and buildAll().
+ //
+ // It searches the current start tag (START_TAG) for the specified
+ // attribute. If found, the method returns its value. Otherwise, it
+ // returns null.
+ //
+ private String getAttribute(XmlPullParser parser, String attribute) {
+ assert attribute != null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String tag = parser.getAttributeName(i);
+ if (tag.equals(attribute))
+ return parser.getAttributeValue(i);
+ }
+ return null;
+ }
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a string "CLASS-NAME for LOCALE (reading DIR)"
+ */
+ @Override
+ public String toString() {
+ return String.format("%s (reading %s)",
+ super.toString(), directoryFile.toString());
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+/**
+ * <p>
+ * Exception class for GameBuilderXMLFile.
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public final class GameBuilderXMLFileException extends GameBuilderException {
+ private String fileTitle;
+ private int lineNo;
+ private String message;
+
+ /**
+ * Constructs an object with the specified file title, message format
+ * and its arguments.
+ *
+ * @param fileTitleArg a file title
+ * @param formatArg a message format
+ * @param args arguments to the message format.
+ */
+ public GameBuilderXMLFileException(String fileTitleArg, String formatArg,
+ Object ... args) {
+ super();
+
+ fileTitle = fileTitleArg;
+ lineNo = 0;
+ String header = String.format("%s: ", fileTitleArg);
+ message = header + String.format(formatArg, args);
+ }
+
+ /**
+ * Constructs an object with the specified file title, line number,
+ * message format and its arguments.
+ *
+ * @param fileTitleArg a file title
+ * @param lineNoArg a line number
+ * @param formatArg a message format
+ * @param args arguments to the message format.
+ */
+ public GameBuilderXMLFileException(String fileTitleArg, int lineNoArg,
+ String formatArg, Object ... args) {
+ super();
+
+ fileTitle = fileTitleArg;
+ lineNo = lineNoArg;
+ String header = String.format("%s: %d: ", fileTitleArg, lineNo);
+ message = header + String.format(formatArg, args);
+ }
+
+ /**
+ * Constructs an object with the specified cause, file title, message
+ * format and its arguments.
+ *
+ * @param causeArg cause
+ * @param fileTitleArg a file title
+ * @param formatArg a message format
+ * @param args arguments to the message format.
+ */
+ public GameBuilderXMLFileException(Throwable causeArg,
+ String fileTitleArg, String formatArg, Object ... args) {
+ super(causeArg);
+
+ fileTitle = fileTitleArg;
+ lineNo = 0;
+ String header = String.format("%s: ", fileTitleArg);
+ message = header + String.format(formatArg, args);
+ }
+
+ /**
+ * Constructs an object with the specified cause, file title, line number,
+ * message format and its arguments.
+ *
+ * @param causeArg cause
+ * @param fileTitleArg a file title
+ * @param lineNoArg a line number
+ * @param formatArg a message format
+ * @param args arguments to the message format.
+ */
+ public GameBuilderXMLFileException(Throwable causeArg,
+ String fileTitleArg, int lineNoArg, String formatArg,
+ Object ... args) {
+ super(causeArg);
+
+ fileTitle = fileTitleArg;
+ lineNo = lineNoArg;
+ String header = String.format("%s: %d: ", fileTitleArg, lineNo);
+ message = header + String.format(formatArg, args);
+ }
+
+ /**
+ * Returns a file title (path to a file relative to the top directory).
+ *
+ * @return a file title
+ */
+ public String getFileTitle() {
+ return fileTitle;
+ }
+
+ /**
+ * Returns a line number.
+ *
+ * @return a line number.
+ */
+ public int getLineNo() {
+ return lineNo;
+ }
+
+ /**
+ * Returns an error message.
+ *
+ * @return an error message.
+ */
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Returns a localized error message.
+ *
+ * @return a localized error message.
+ */
+ @Override
+ public String getLocalizedMessage() {
+ return message; // not supported yet.
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.util.List;
+import java.util.Random;
+
+import nedragtna.random.MTRandom;
+
+/**
+ * <p>
+ * Selects cards randomly.
+ * </p>
+ *
+ * <p>
+ * Its object randomly selects cards registered in a 'Game' object.
+ * </p>
+ *
+ * @author Motoyuki Kasahara
+ * @version 1.0
+ */
+public class Randomizer {
+ private Game game;
+ private Random random;
+
+ /**
+ * Constructs an object.
+ *
+ * This object randomly selects the number of 'gameArg.selectionSize()'
+ * cards from the game object 'gameArg', using the random number
+ * generator of 'MTRandom' class, implementation of Mersenne Twister.
+ * An appropriate random seed is also set to the random number generator
+ * automatically.
+ *
+ * @param gameArg a game
+ */
+ public Randomizer(Game gameArg) {
+ game = new Game(gameArg);
+ long seed = System.currentTimeMillis()
+ + Runtime.getRuntime().freeMemory();
+ random = new MTRandom(seed);
+ }
+
+ /**
+ * Constructs an object.
+ *
+ * This object randomly selects the number of 'gameArg.selectionSize()'
+ * cards from the game object 'gameArg', using the random number
+ * generator 'randomArg'.
+ *
+ * Note that this object never sets a random seed of the random number
+ * generator.
+ *
+ * @param gameArg a game
+ * @param randomArg a random number generator
+ */
+ public Randomizer(Game gameArg, Random randomArg) {
+ assert randomArg != null;
+
+ game = new Game(gameArg);
+ random = randomArg;
+ }
+
+ /**
+ * Returns the 'Game' object bound to this object.
+ *
+ * @return a 'Game' object.
+ */
+ public Game getGame() {
+ return new Game(game);
+ }
+
+ /**
+ * Returns a random number generator.
+ *
+ * @return a random number generator
+ */
+ public Random getRandom() {
+ return random;
+ }
+
+ /**
+ * Select cards randomly.
+ *
+ * This methods randomly selects a card in a 'Game' object bound to this
+ * object and adds it to 'selectedCards'. It repeats this operation
+ * until the size of 'selectedCards' reaches the limit (i.e. the
+ * selection size).
+ *
+ * It never chooses a card which has already been stored in
+ * 'selectedCards'. If there is a card which must be chosen, add
+ * the card to 'selectedCards' before call this method.
+ *
+ * When there is no more card to be chosen in 'game', it returns
+ * immediately. In this case, the size of 'selectedCards' is less than
+ * the limit.
+ *
+ * If the size of given 'selectedCards' exceeds the limit, this method
+ * randomly removes cards in it, instead.
+ *
+ * @param selectedCards a list in which selected cards are stored
+ * @return the number of selected cards, including cards initially
+ * stored in 'selectedCards'.
+ */
+ public int select(List<Card> selectedCards) {
+ assert selectedCards != null;
+
+ int nSelected = selectedCards.size();
+ int selectionSize = game.getSelectionSize();
+
+ if (nSelected < selectionSize) {
+ Game g = new Game(game);
+ g.removeCards(selectedCards);
+ int nCandidates = g.getCardsSize();
+ while (nSelected < selectionSize && nCandidates > 0) {
+ int index = random.nextInt(nCandidates);
+ Card c = g.getCard(index);
+ selectedCards.add(c);
+ g.removeCard(c);
+ nSelected++;
+ nCandidates--;
+ }
+ } else {
+ while (nSelected > selectionSize) {
+ int index = random.nextInt(nSelected);
+ selectedCards.remove(index);
+ nSelected--;
+ }
+ }
+
+ return nSelected;
+ }
+
+ /**
+ * Converts this object to a String.
+ *
+ * @return a string "CLASS-NAME for GAME"
+ */
+ @Override
+ public String toString() {
+ return String.format("%s for %s",
+ getClass().getSimpleName(), game.toString());
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class SimpleRandomizerCommand {
+ enum OperationMode {
+ RANDOMIZE, PRINT_GAMES, PRINT_EXPANSIONS, PRINT_CARDS,
+ }
+
+ //
+ // Main.
+ // Usage: command [-d DIR] [-c|-g|-x] [-E] GAME [EXPANSION...]
+ //
+ public static void main (String[] args) {
+ OperationMode operationMode = OperationMode.RANDOMIZE;
+ CardComparisonMode comparisonMode
+ = CardComparisonMode.PRICE_THEN_EXPANSION;
+
+ ArrayList<String> argsList
+ = new ArrayList<String>(Arrays.asList(args));
+ String path = ".";
+
+ //
+ // Parse arguments.
+ //
+ while (!argsList.isEmpty()) {
+ String arg = argsList.get(0);
+ if (arg.equals("--")) {
+ argsList.remove(0);
+ break;
+ } else if (arg.equals("-") || !arg.startsWith("-")) {
+ break;
+ }
+ if (arg.equals("-c"))
+ operationMode = OperationMode.PRINT_CARDS;
+ else if (arg.equals("-E"))
+ comparisonMode = CardComparisonMode.EXPANSION_THEN_PRICE;
+ else if (arg.equals("-d")) {
+ if (argsList.size() <= 1) {
+ System.err.println("missing argument to '-d'");
+ System.exit(1);
+ }
+ path = argsList.get(1);
+ argsList.remove(0);
+ } else if (arg.equals("-g")) {
+ operationMode = OperationMode.PRINT_GAMES;
+ } else if (arg.equals("-x")) {
+ operationMode = OperationMode.PRINT_EXPANSIONS;
+ } else {
+ System.err.println(String.format("unknown option '%s'",
+ argsList.get(0)));
+ System.exit(1);
+ }
+ argsList.remove(0);
+ }
+ if (argsList.isEmpty() && operationMode != OperationMode.PRINT_GAMES) {
+ System.err.println("no game ID specified");
+ System.exit(1);
+ }
+
+ //
+ // Load data of games.
+ //
+ List<Game> games = loadGames(path);
+ if (games == null)
+ System.exit(1);
+
+ if (operationMode == OperationMode.PRINT_GAMES) {
+ printGames(games);
+ System.exit(0);
+ }
+
+ //
+ // Choose game.
+ //
+ String gameId = argsList.get(0);
+ argsList.remove(0);
+ Game gameBase = selectGame(games, gameId);
+ if (gameBase == null) {
+ System.err.println("unknown game: " + gameId);
+ System.exit(1);
+ }
+
+ //
+ // Choose expansions.
+ //
+ Game game = selectExpansions(gameBase, argsList);
+ if (game == null)
+ System.exit(1);
+
+ if (operationMode == OperationMode.PRINT_EXPANSIONS) {
+ printExpansions(game.getExpansions());
+ System.exit(0);
+ } else if (operationMode == OperationMode.PRINT_CARDS) {
+ printCards(game.getCards(), comparisonMode);
+ System.exit(0);
+ }
+
+ List<Card> cards = selectCards(game);
+ if (cards == null)
+ System.exit(1);
+
+ printCards(cards, comparisonMode);
+ }
+
+ //
+ // Load data of games.
+ //
+ private static List<Game> loadGames(String path) {
+ List<Game> games = null;
+ try {
+ GameBuilder builder = new GameBuilderXMLFile(path);
+ games = builder.buildAll();
+ } catch (GameBuilderException e) {
+ System.err.println(e.getMessage());
+ return null;
+ }
+ if (games.size() == 0) {
+ System.err.println("no game registered");
+ return null;
+ }
+ return games;
+ }
+
+ //
+ // Select a game to be used.
+ //
+ private static Game selectGame(List<Game> games, String id) {
+ ListIterator<Game> it = games.listIterator();
+ while (it.hasNext()) {
+ Game g = it.next();
+ if (g.getId().equals(id))
+ return g;
+ }
+ return null;
+ }
+
+ //
+ // Select expansions to be used.
+ //
+ private static Game selectExpansions(Game gameBase,
+ List<String> ids) {
+ Game game = new Game(gameBase);
+ if (!ids.isEmpty()) {
+ List<Expansion> xl = new ArrayList<Expansion>();
+ ListIterator<String> it = ids.listIterator();
+ while (it.hasNext()) {
+ String id = it.next();
+ Expansion x = game.getExpansion(id);
+ if (x == null) {
+ System.err.println("unknown expansion: " + id);
+ return null;
+ }
+ xl.add(x);
+ }
+ game.removeOtherExpansions(xl);
+ }
+
+ return game;
+ }
+
+ //
+ // Select cards randomly.
+ //
+ private static List<Card> selectCards(Game game) {
+ Randomizer rnd = new Randomizer(game);
+ List<Card> cards = new LinkedList<Card>();
+ if (rnd.select(cards) < game.getSelectionSize())
+ System.err.println("warning: not enough cards registered");
+ return cards;
+ }
+
+ //
+ // Print games.
+ //
+ private static void printGames(List<Game> games) {
+ for (Game g : games) {
+ String msg = String.format("%-20s %-30s (%d)",
+ g.getId(), g.getTitle(), g.getSelectionSize());
+ System.out.println(msg);
+ }
+ }
+
+ //
+ // Print expansions.
+ //
+ private static void printExpansions(List<Expansion> expansions) {
+ for (Expansion x : expansions) {
+ String msg = String.format("%-20s %-30s", x.getId(),
+ x.getTitle());
+ System.out.println(msg);
+ }
+ }
+
+ //
+ // Print cards.
+ //
+ private static void printCards(List<Card> cards, CardComparisonMode mode) {
+ List<Card> cl = new ArrayList<Card>(cards);
+ Collections.sort(cl, new CardComparator(mode));
+
+ for (Card c : cl) {
+ System.out.println(c.getTitle());
+ System.out.println(String.format(" %-5s %-30s",
+ c.getPrice(), c.getExpansion().getTitle()));
+ }
+ }
+}
--- /dev/null
+package nedragtna.random;
+
+/*
+ * MTRandom : A Java implementation of the MT19937 (Mersenne Twister)
+ * pseudo random number generator algorithm based upon the
+ * original C code by Makoto Matsumoto and Takuji Nishimura.
+ * Author : David Beaumont
+ * Email : mersenne-at-www.goui.net
+ *
+ * For the original C code, see:
+ * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+ *
+ * This version, Copyright (C) 2005, David Beaumont.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+import java.util.Random;
+
+/**
+ * @version 1.0
+ * @author David Beaumont, Copyright 2005
+ * <p>
+ * A Java implementation of the MT19937 (Mersenne Twister) pseudo random
+ * number generator algorithm based upon the original C code by Makoto
+ * Matsumoto and Takuji Nishimura (see <a
+ * href="http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html">
+ * http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html</a> for more
+ * information.
+ * <p>
+ * As a subclass of java.util.Random this class provides a single
+ * canonical method next() for generating bits in the pseudo random
+ * number sequence. Anyone using this class should invoke the public
+ * inherited methods (nextInt(), nextFloat etc.) to obtain values as
+ * normal. This class should provide a drop-in replacement for the
+ * standard implementation of java.util.Random with the additional
+ * advantage of having a far longer period and the ability to use a far
+ * larger seed value.
+ * <p>
+ * This is <b>not</b> a cryptographically strong source of randomness
+ * and should <b>not</b> be used for cryptographic systems or in any
+ * other situation where true random numbers are required.
+ * <p>
+ * <!-- Creative Commons License --> <a
+ * href="http://creativecommons.org/licenses/LGPL/2.1/"><img
+ * alt="CC-GNU LGPL" border="0"
+ * src="http://creativecommons.org/images/public/cc-LGPL-a.png" /></a><br />
+ * This software is licensed under the <a
+ * href="http://creativecommons.org/licenses/LGPL/2.1/">CC-GNU LGPL</a>.
+ * <!-- /Creative Commons License -->
+ *
+ * <!-- <rdf:RDF xmlns="http://web.resource.org/cc/"
+ * xmlns:dc="http://purl.org/dc/elements/1.1/"
+ * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ *
+ * <Work rdf:about=""> <license
+ * rdf:resource="http://creativecommons.org/licenses/LGPL/2.1/" />
+ * <dc:type rdf:resource="http://purl.org/dc/dcmitype/Software" />
+ * </Work>
+ *
+ * <License rdf:about="http://creativecommons.org/licenses/LGPL/2.1/">
+ * <permits rdf:resource="http://web.resource.org/cc/Reproduction" />
+ * <permits rdf:resource="http://web.resource.org/cc/Distribution" />
+ * <requires rdf:resource="http://web.resource.org/cc/Notice" />
+ * <permits rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ * <requires rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ * <requires rdf:resource="http://web.resource.org/cc/SourceCode" />
+ * </License>
+ *
+ * </rdf:RDF> -->
+ *
+ */
+public class MTRandom extends Random {
+
+ /**
+ * Auto-generated serial version UID. Note that MTRandom does NOT support
+ * serialisation of its internal state and it may even be necessary to
+ * implement read/write methods to re-seed it properly. This is only here to
+ * make Eclipse shut up about it being missing.
+ */
+ private static final long serialVersionUID = -515082678588212038L;
+
+ // Constants used in the original C implementation
+ private final static int UPPER_MASK = 0x80000000;
+ private final static int LOWER_MASK = 0x7fffffff;
+
+ private final static int N = 624;
+ private final static int M = 397;
+ private final static int MAGIC[] = { 0x0, 0x9908b0df };
+ private final static int MAGIC_FACTOR1 = 1812433253;
+ private final static int MAGIC_FACTOR2 = 1664525;
+ private final static int MAGIC_FACTOR3 = 1566083941;
+ private final static int MAGIC_MASK1 = 0x9d2c5680;
+ private final static int MAGIC_MASK2 = 0xefc60000;
+ private final static int MAGIC_SEED = 19650218;
+ private final static long DEFAULT_SEED = 5489L;
+
+ // Internal state
+ private transient int[] mt;
+ private transient int mti;
+ private transient boolean compat = false;
+
+ // Temporary buffer used during setSeed(long)
+ private transient int[] ibuf;
+
+ /**
+ * The default constructor for an instance of MTRandom.
+ * Since the no-argument constructor of java.util.Random
+ * does not seem to call setSeed anymore (since JDK7),
+ * we need to do it manually in this constructor.
+ * For legacy purposes, the seed remains initialized by
+ * a call to System.currentTimeMillis().
+ * @author Jonathan Passerat-Palmbach
+ *
+ */
+ public MTRandom() {
+ this.setSeed(System.currentTimeMillis());
+ }
+
+ /**
+ * This version of the constructor can be used to implement identical
+ * behaviour to the original C code version of this algorithm including
+ * exactly replicating the case where the seed value had not been set prior
+ * to calling genrand_int32.
+ * <p>
+ * If the compatibility flag is set to true, then the algorithm will be
+ * seeded with the same default value as was used in the original C code.
+ * Furthermore the setSeed() method, which must take a 64 bit long value,
+ * will be limited to using only the lower 32 bits of the seed to facilitate
+ * seamless migration of existing C code into Java where identical behaviour
+ * is required.
+ * <p>
+ * Whilst useful for ensuring backwards compatibility, it is advised that
+ * this feature not be used unless specifically required, due to the
+ * reduction in strength of the seed value.
+ *
+ * @param compatible
+ * Compatibility flag for replicating original behaviour.
+ */
+ public MTRandom(boolean compatible) {
+ super(0L);
+ compat = compatible;
+ setSeed(compat ? DEFAULT_SEED : System.currentTimeMillis());
+ }
+
+ /**
+ * This version of the constructor simply initialises the class with the
+ * given 64 bit seed value. For a better random number sequence this seed
+ * value should contain as much entropy as possible.
+ *
+ * This constructor was modified due to be compliant to the JDK7's implementation
+ * of java.util.Random as explained in MTRandom()
+ * @param seed
+ * The seed value with which to initialise this class.
+ * @see MTRandom()
+ * @author Jonathan Passerat-Palmbach
+ */
+ public MTRandom(long seed) {
+ super(seed);
+ this.setSeed(seed);
+ }
+
+ /**
+ * This version of the constructor initialises the class with the given byte
+ * array. All the data will be used to initialise this instance.
+ *
+ * @param buf
+ * The non-empty byte array of seed information.
+ * @throws NullPointerException
+ * if the buffer is null.
+ * @throws IllegalArgumentException
+ * if the buffer has zero length.
+ */
+ public MTRandom(byte[] buf) {
+ super(0L);
+ setSeed(buf);
+ }
+
+ /**
+ * This version of the constructor initialises the class with the given
+ * integer array. All the data will be used to initialise this instance.
+ *
+ * @param buf
+ * The non-empty integer array of seed information.
+ * @throws NullPointerException
+ * if the buffer is null.
+ * @throws IllegalArgumentException
+ * if the buffer has zero length.
+ */
+ public MTRandom(int[] buf) {
+ super(0L);
+ setSeed(buf);
+ }
+
+ // Initializes mt[N] with a simple integer seed. This method is
+ // required as part of the Mersenne Twister algorithm but need
+ // not be made public.
+ private final void setSeed(int seed) {
+
+ // Annoying runtime check for initialisation of internal data
+ // caused by java.util.Random invoking setSeed() during init.
+ // This is unavoidable because no fields in our instance will
+ // have been initialised at this point, not even if the code
+ // were placed at the declaration of the member variable.
+ if (mt == null)
+ mt = new int[N];
+
+ // ---- Begin Mersenne Twister Algorithm ----
+ mt[0] = seed;
+ for (mti = 1; mti < N; mti++) {
+ mt[mti] = (MAGIC_FACTOR1 * (mt[mti - 1] ^ (mt[mti - 1] >>> 30)) + mti);
+ }
+ // ---- End Mersenne Twister Algorithm ----
+ }
+
+ /**
+ * This method resets the state of this instance using the 64 bits of seed
+ * data provided. Note that if the same seed data is passed to two different
+ * instances of MTRandom (both of which share the same compatibility state)
+ * then the sequence of numbers generated by both instances will be
+ * identical.
+ * <p>
+ * If this instance was initialised in 'compatibility' mode then this method
+ * will only use the lower 32 bits of any seed value passed in and will
+ * match the behaviour of the original C code exactly with respect to state
+ * initialisation.
+ *
+ * @param seed
+ * The 64 bit value used to initialise the random number
+ * generator state.
+ */
+ public final synchronized void setSeed(long seed) {
+ if (compat) {
+ setSeed((int) seed);
+ } else {
+
+ // Annoying runtime check for initialisation of internal data
+ // caused by java.util.Random invoking setSeed() during init.
+ // This is unavoidable because no fields in our instance will
+ // have been initialised at this point, not even if the code
+ // were placed at the declaration of the member variable.
+ if (ibuf == null)
+ ibuf = new int[2];
+
+ ibuf[0] = (int) seed;
+ ibuf[1] = (int) (seed >>> 32);
+ setSeed(ibuf);
+ }
+ }
+
+ /**
+ * This method resets the state of this instance using the byte array of
+ * seed data provided. Note that calling this method is equivalent to
+ * calling "setSeed(pack(buf))" and in particular will result in a new
+ * integer array being generated during the call. If you wish to retain this
+ * seed data to allow the pseudo random sequence to be restarted then it
+ * would be more efficient to use the "pack()" method to convert it into an
+ * integer array first and then use that to re-seed the instance. The
+ * behaviour of the class will be the same in both cases but it will be more
+ * efficient.
+ *
+ * @param buf
+ * The non-empty byte array of seed information.
+ * @throws NullPointerException
+ * if the buffer is null.
+ * @throws IllegalArgumentException
+ * if the buffer has zero length.
+ */
+ public final void setSeed(byte[] buf) {
+ setSeed(pack(buf));
+ }
+
+ /**
+ * This method resets the state of this instance using the integer array of
+ * seed data provided. This is the canonical way of resetting the pseudo
+ * random number sequence.
+ *
+ * @param buf
+ * The non-empty integer array of seed information.
+ * @throws NullPointerException
+ * if the buffer is null.
+ * @throws IllegalArgumentException
+ * if the buffer has zero length.
+ */
+ public final synchronized void setSeed(int[] buf) {
+ int length = buf.length;
+ if (length == 0)
+ throw new IllegalArgumentException("Seed buffer may not be empty");
+ // ---- Begin Mersenne Twister Algorithm ----
+ int i = 1, j = 0, k = (N > length ? N : length);
+ setSeed(MAGIC_SEED);
+ for (; k > 0; k--) {
+ mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >>> 30)) * MAGIC_FACTOR2))
+ + buf[j] + j;
+ i++;
+ j++;
+ if (i >= N) {
+ mt[0] = mt[N - 1];
+ i = 1;
+ }
+ if (j >= length)
+ j = 0;
+ }
+ for (k = N - 1; k > 0; k--) {
+ mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >>> 30)) * MAGIC_FACTOR3))
+ - i;
+ i++;
+ if (i >= N) {
+ mt[0] = mt[N - 1];
+ i = 1;
+ }
+ }
+ mt[0] = UPPER_MASK; // MSB is 1; assuring non-zero initial array
+ // ---- End Mersenne Twister Algorithm ----
+ }
+
+ /**
+ * This method forms the basis for generating a pseudo random number
+ * sequence from this class. If given a value of 32, this method behaves
+ * identically to the genrand_int32 function in the original C code and
+ * ensures that using the standard nextInt() function (inherited from
+ * Random) we are able to replicate behaviour exactly.
+ * <p>
+ * Note that where the number of bits requested is not equal to 32 then bits
+ * will simply be masked out from the top of the returned integer value.
+ * That is to say that:
+ *
+ * <pre>
+ * mt.setSeed(12345);
+ * int foo = mt.nextInt(16) + (mt.nextInt(16) << 16);
+ * </pre>
+ *
+ * will not give the same result as
+ *
+ * <pre>
+ * mt.setSeed(12345);
+ * int foo = mt.nextInt(32);
+ * </pre>
+ *
+ * @param bits
+ * The number of significant bits desired in the output.
+ * @return The next value in the pseudo random sequence with the specified
+ * number of bits in the lower part of the integer.
+ */
+ protected final synchronized int next(int bits) {
+ // ---- Begin Mersenne Twister Algorithm ----
+ int y, kk;
+ if (mti >= N) { // generate N words at one time
+
+ // In the original C implementation, mti is checked here
+ // to determine if initialisation has occurred; if not
+ // it initialises this instance with DEFAULT_SEED (5489).
+ // This is no longer necessary as initialisation of the
+ // Java instance must result in initialisation occurring
+ // Use the constructor MTRandom(true) to enable backwards
+ // compatible behaviour.
+
+ for (kk = 0; kk < N - M; kk++) {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
+ mt[kk] = mt[kk + M] ^ (y >>> 1) ^ MAGIC[y & 0x1];
+ }
+ for (; kk < N - 1; kk++) {
+ y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
+ mt[kk] = mt[kk + (M - N)] ^ (y >>> 1) ^ MAGIC[y & 0x1];
+ }
+ y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+ mt[N - 1] = mt[M - 1] ^ (y >>> 1) ^ MAGIC[y & 0x1];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+
+ // Tempering
+ y ^= (y >>> 11);
+ y ^= (y << 7) & MAGIC_MASK1;
+ y ^= (y << 15) & MAGIC_MASK2;
+ y ^= (y >>> 18);
+ // ---- End Mersenne Twister Algorithm ----
+ return (y >>> (32 - bits));
+ }
+
+ // This is a fairly obscure little code section to pack a
+ // byte[] into an int[] in little endian ordering.
+
+ /**
+ * This simply utility method can be used in cases where a byte array of
+ * seed data is to be used to repeatedly re-seed the random number sequence.
+ * By packing the byte array into an integer array first, using this method,
+ * and then invoking setSeed() with that; it removes the need to re-pack the
+ * byte array each time setSeed() is called.
+ * <p>
+ * If the length of the byte array is not a multiple of 4 then it is
+ * implicitly padded with zeros as necessary. For example:
+ *
+ * <pre>
+ * byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }
+ * </pre>
+ *
+ * becomes
+ *
+ * <pre>
+ * int[] { 0x04030201, 0x00000605 }
+ * </pre>
+ * <p>
+ * Note that this method will not complain if the given byte array is empty
+ * and will produce an empty integer array, but the setSeed() method will
+ * throw an exception if the empty integer array is passed to it.
+ *
+ * @param buf
+ * The non-null byte array to be packed.
+ * @return A non-null integer array of the packed bytes.
+ * @throws NullPointerException
+ * if the given byte array is null.
+ */
+ public static int[] pack(byte[] buf) {
+ int k, blen = buf.length, ilen = ((buf.length + 3) >>> 2);
+ int[] ibuf = new int[ilen];
+ for (int n = 0; n < ilen; n++) {
+ int m = (n + 1) << 2;
+ if (m > blen)
+ m = blen;
+ for (k = buf[--m] & 0xff; (m & 0x3) != 0; k = (k << 8) | buf[--m]
+ & 0xff)
+ ;
+ ibuf[n] = k;
+ }
+ return ibuf;
+ }
+}
\ No newline at end of file
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import junit.framework.*;
+import junit.textui.TestRunner;
+
+public final class CardComparatorTest extends TestCase {
+ public CardComparatorTest(String name) {
+ super(name);
+ }
+
+ //
+ // Test CardComparator().
+ //
+ public void testCostructor1() {
+ try {
+ CardComparator cc = new CardComparator();
+ assertEquals(cc.getMode(),
+ CardComparisonMode.PRICE_THEN_EXPANSION);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test CardComparator(mode) and getMode().
+ //
+ public void testCostructor2() {
+ try {
+ CardComparator cc1
+ = new CardComparator(CardComparisonMode.PRICE_THEN_EXPANSION);
+ CardComparator cc2
+ = new CardComparator(CardComparisonMode.EXPANSION_THEN_PRICE);
+
+ assertEquals(cc1.getMode(),
+ CardComparisonMode.PRICE_THEN_EXPANSION);
+ assertEquals(cc2.getMode(),
+ CardComparisonMode.EXPANSION_THEN_PRICE);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Compare() for both PRICE_THEN_EXPANSION and EXPANSION_THEN_PRICE
+ // modes.
+ //
+ private void testCmpareCommon(CardComparator cc) {
+ try {
+ Expansion x = new Expansion("", "", 1000);
+ Card c1;
+ Card c2;
+
+ //
+ // "1" <=> "1"
+ //
+ c1 = new Card("", "", "1", x);
+ c2 = new Card("", "", "1", x);
+ assertTrue(cc.compare(c1, c2) == 0);
+
+ //
+ // "9" <=> "10"
+ //
+ c1 = new Card("", "", "10", x);
+ c2 = new Card("", "", "9", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "9", x);
+ c2 = new Card("", "", "10", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "9+" <=> "10+"
+ //
+ c1 = new Card("", "", "10+", x);
+ c2 = new Card("", "", "9+", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "9+", x);
+ c2 = new Card("", "", "10+", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "9x" <=> "10x"
+ //
+ c1 = new Card("", "", "10x", x);
+ c2 = new Card("", "", "9x", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "9x", x);
+ c2 = new Card("", "", "10x", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "9+" <=> "10*"
+ //
+ c1 = new Card("", "", "10*", x);
+ c2 = new Card("", "", "9+", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "9+", x);
+ c2 = new Card("", "", "10*", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "9y" <=> "10x"
+ //
+ c1 = new Card("", "", "10x", x);
+ c2 = new Card("", "", "9y", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "9y", x);
+ c2 = new Card("", "", "10x", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "1+" <=> "1*"
+ //
+ c1 = new Card("", "", "1+", x);
+ c2 = new Card("", "", "1*", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "1*", x);
+ c2 = new Card("", "", "1+", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "+9" <=> "+10"
+ //
+ c1 = new Card("", "", "+10", x);
+ c2 = new Card("", "", "+9", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "+9", x);
+ c2 = new Card("", "", "+10", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "1" <=> "?"
+ //
+ c1 = new Card("", "", "?", x);
+ c2 = new Card("", "", "1", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "1", x);
+ c2 = new Card("", "", "?", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ //
+ // "?" <=> "?"
+ //
+ c1 = new Card("", "", "?", x);
+ c2 = new Card("", "", "?", x);
+ assertTrue(cc.compare(c1, c2) == 0);
+
+ //
+ // "*" <=> "?"
+ //
+ c1 = new Card("", "", "?", x);
+ c2 = new Card("", "", "*", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "*", x);
+ c2 = new Card("", "", "?", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Compare() in PRICE_THEN_EXPANSION mode.
+ //
+ public void testCmpare1() {
+ try {
+ CardComparator cc
+ = new CardComparator(CardComparisonMode.PRICE_THEN_EXPANSION);
+
+ //
+ // Test cases independent from mode.
+ //
+ testCmpareCommon(cc);
+
+ //
+ // Same price, different expansion.
+ //
+ Expansion x1 = new Expansion("", "", 1000);
+ Expansion x2 = new Expansion("", "", 2000);
+ Card c1;
+ Card c2;
+
+ c1 = new Card("", "", "1", x2);
+ c2 = new Card("", "", "1", x1);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "1", x1);
+ c2 = new Card("", "", "1", x2);
+ assertTrue(cc.compare(c1, c2) < 0);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Compare() in EXPANSION_THEN_PRICE mode.
+ //
+ public void testCmpare2() {
+ try {
+ CardComparator cc
+ = new CardComparator(CardComparisonMode.EXPANSION_THEN_PRICE);
+
+ //
+ // Test cases independent from mode.
+ //
+ testCmpareCommon(cc);
+
+ //
+ // Same expansion, different price.
+ //
+ Expansion x = new Expansion("", "", 1000);
+ Card c1;
+ Card c2;
+
+ c1 = new Card("", "", "2", x);
+ c2 = new Card("", "", "1", x);
+ assertTrue(cc.compare(c1, c2) > 0);
+
+ c1 = new Card("", "", "1", x);
+ c2 = new Card("", "", "2", x);
+ assertTrue(cc.compare(c1, c2) < 0);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test equals().
+ //
+ public void testEquals() {
+ try {
+ CardComparator cc1
+ = new CardComparator();
+ CardComparator cc2
+ = new CardComparator(CardComparisonMode.PRICE_THEN_EXPANSION);
+ CardComparator cc3
+ = new CardComparator(CardComparisonMode.EXPANSION_THEN_PRICE);
+
+ assertTrue(cc1.equals(cc1));
+ assertTrue(cc1.equals(cc2));
+ assertFalse(cc1.equals(cc3));
+ assertFalse(cc1.equals(CardComparisonMode.PRICE_THEN_EXPANSION));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test toString().
+ //
+ public void testToString() {
+ try {
+ CardComparator cc1
+ = new CardComparator(CardComparisonMode.PRICE_THEN_EXPANSION);
+ CardComparator cc2
+ = new CardComparator(CardComparisonMode.EXPANSION_THEN_PRICE);
+
+ String str1 = cc1.toString();
+ String str2 = cc2.toString();
+
+ assertTrue(str1.equals("CardComparator (PRICE_THEN_EXPANSION)"));
+ assertTrue(str2.equals("CardComparator (EXPANSION_THEN_PRICE)"));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(CardComparatorTest.class);
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import junit.framework.*;
+import junit.textui.TestRunner;
+
+public final class CardTest extends TestCase {
+ public CardTest(String name) {
+ super(name);
+ }
+
+ //
+ // Test Card(id, title, price, expansion).
+ //
+ public void testCostructor() {
+ try {
+ Expansion x = new Expansion("test-expansion-id", "");
+ String id = "test-card-id";
+ String title = "test-card-title";
+ String price = "test-card-price";
+
+ Card c = new Card(id, title, price, x);
+
+ assertEquals(c.getId(), id);
+ assertEquals(c.getTitle(), title);
+ assertEquals(c.getPrice(), price);
+ assertEquals(c.getExpansion(), x);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Card(card).
+ //
+ public void testCopyCostructor() {
+ try {
+ Expansion x = new Expansion("test-expansion-id", "");
+ String id = "test-card-id";
+ String title = "test-card-title";
+ String price = "test-card-price";
+
+ Card c1 = new Card(id, title, price, x);
+ Card c2 = new Card(c1);
+
+ assertEquals(c2.getId(), id);
+ assertEquals(c2.getTitle(), title);
+ assertEquals(c2.getPrice(), price);
+ assertEquals(c2.getExpansion(), x);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test getId(), getTitle(), getPrice() and getExpansion().
+ //
+ public void testGetters() {
+ try {
+ int expansionsSize = 10;
+ int cardsSize = 1000;
+ String id = "test-card-id";
+ String title = "test-card-title";
+ String price = "test-card-price";
+
+ Expansion[] expansions = new Expansion[expansionsSize];
+ for (int i = 0; i < expansionsSize; i++)
+ expansions[i] = new Expansion("test-expansion-id" + i, "");
+
+ Card[] cards = new Card[cardsSize];
+ for (int i = 0; i < cardsSize; i++) {
+ cards[i] = new Card(id + i, title + i, price + i,
+ expansions[i % expansionsSize]);
+ }
+
+ for (int i = 0; i < cardsSize; i++) {
+ assertEquals(cards[i].getId(), id + i);
+ assertEquals(cards[i].getTitle(), title + i);
+ assertEquals(cards[i].getPrice(), price + i);
+ assertEquals(cards[i].getExpansion(),
+ expansions[i % expansionsSize]);
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test equals().
+ //
+ public void testEquals() {
+ try {
+ Expansion x = new Expansion("test-expansion-id", "");
+ Card c1 = new Card("a", "a", "a", x);
+ Card c2 = new Card("a", "b", "b", x);
+ Card c3 = new Card("b", "a", "a", x);
+ String s = "a";
+
+ assertTrue(c1.equals(c1));
+ assertTrue(c1.equals(c2));
+ assertFalse(c1.equals(c3));
+ assertFalse(c1.equals(s));
+ assertFalse(c1.equals(null));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test compareTo().
+ //
+ public void testCompareTo() {
+ try {
+ Expansion x = new Expansion("test-expansion-id", "");
+ Card c1 = new Card("b", "a", "a", x);
+ Card c2 = new Card("a", "a", "a", x);
+ Card c3 = new Card("c", "a", "a", x);
+ Card c4 = new Card("b", "a", "a", x);
+
+ assertTrue(c1.compareTo(c1) == 0);
+ assertTrue(c1.compareTo(c2) > 0);
+ assertTrue(c1.compareTo(c3) < 0);
+ assertTrue(c1.compareTo(c4) == 0);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test toString().
+ //
+ public void testToString() {
+ try {
+ Expansion x = new Expansion("test-expansion-id", "");
+ String id = "test-card-id";
+ String title = "test-card-title";
+ String price = "test-card-price";
+
+ Card c = new Card(id, title, price, x);
+
+ assertEquals(c.toString(), title);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(CardTest.class);
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import junit.framework.*;
+import junit.textui.TestRunner;
+
+public final class ExpansionTest extends TestCase {
+ public ExpansionTest(String name) {
+ super(name);
+ }
+
+ //
+ // Test Expansion(id, title).
+ //
+ public void testCostructor1() {
+ try {
+ String id = "test-id";
+ String title = "test-title";
+ Expansion x = new Expansion(id, title);
+
+ assertEquals(x.getId(), id);
+ assertEquals(x.getTitle(), title);
+ assertEquals(x.getPreference(), Integer.MAX_VALUE);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Expansion(id, title, preference).
+ //
+ public void testCostructor2() {
+ try {
+ String id = "test-id";
+ String title = "test-title";
+ int preference = 1000;
+ Expansion x = new Expansion(id, title, preference);
+
+ assertEquals(x.getId(), id);
+ assertEquals(x.getTitle(), title);
+ assertEquals(x.getPreference(), preference);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Expansion(expansion).
+ //
+ public void testCopyCostructor() {
+ try {
+ String id = "test-id";
+ String title = "test-title";
+ int preference = 1000;
+
+ Expansion x1 = new Expansion(id, title, preference);
+ Expansion x2 = new Expansion(x1);
+
+ assertEquals(x2.getId(), id);
+ assertEquals(x2.getTitle(), title);
+ assertEquals(x2.getPreference(), preference);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test getId(), getTitle() and getPreference().
+ //
+ public void testGetters() {
+ try {
+ int expansionsSize = 100;
+
+ String id = "test-id";
+ String title = "test-title";
+ int preference = 1000;
+ Expansion[] expansions = new Expansion[expansionsSize];
+ for (int i = 0; i < expansionsSize; i++) {
+ expansions[i] = new Expansion("test-id" + i, title + i,
+ preference + i);
+ }
+
+ for (int i = 0; i < expansionsSize; i++) {
+ assertEquals(expansions[i].getId(), id + i);
+ assertEquals(expansions[i].getTitle(), title + i);
+ assertEquals(expansions[i].getPreference(), preference + i);
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test equals(object).
+ //
+ public void testEquals() {
+ try {
+ Expansion x1 = new Expansion("a", "a", 0);
+ Expansion x2 = new Expansion("a", "b", 0);
+ Expansion x3 = new Expansion("b", "a", 0);
+ String s = "a";
+
+ assertTrue(x1.equals(x1));
+ assertTrue(x1.equals(x2));
+ assertFalse(x1.equals(x3));
+ assertFalse(x1.equals(s));
+ assertFalse(x1.equals(null));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test compareTo(expansion).
+ //
+ public void testCompareTo() {
+ try {
+ Expansion x1 = new Expansion("b", "a", 0);
+ Expansion x2 = new Expansion("a", "a", 0);
+ Expansion x3 = new Expansion("c", "a", 0);
+ Expansion x4 = new Expansion("b", "a", 0);
+
+ assertTrue(x1.compareTo(x1) == 0);
+ assertTrue(x1.compareTo(x2) > 0);
+ assertTrue(x1.compareTo(x3) < 0);
+ assertTrue(x1.compareTo(x4) == 0);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test toString().
+ //
+ public void testToString() {
+ try {
+ String id = "test-id";
+ String title = "test-title";
+ Expansion x = new Expansion(id, title);
+
+ assertEquals(x.toString(), title);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(ExpansionTest.class);
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import junit.framework.*;
+import junit.textui.TestRunner;
+import nedragtna.random.MTRandom;
+
+public final class GameTest extends TestCase {
+ public GameTest(String name) {
+ super(name);
+ }
+
+ //
+ // Test Game().
+ //
+ public void testCostructor() {
+ try {
+ String id = "test-game-id";
+ String title = "test-game-title";
+ int selectionSize = 10;
+
+ Game g = new Game(id, title, selectionSize);
+
+ assertEquals(g.getId(), id);
+ assertEquals(g.getTitle(), title);
+ assertEquals(g.getSelectionSize(), selectionSize);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Game(game).
+ //
+ public void testCopyCostructor() {
+ try {
+ String id = "test-game-id";
+ String title = "test-game-title";
+ int selectionSize = 10;
+
+ Game g1 = new Game(id, title, selectionSize);
+ Game g2 = new Game(g1);
+
+ assertEquals(g2.getId(), id);
+ assertEquals(g2.getTitle(), title);
+ assertEquals(g2.getSelectionSize(), selectionSize);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test getId(), getTitle() and getSelectionSize().
+ //
+ public void testBasicGetters() {
+ try {
+ String id = "test-game-id";
+ String title = "test-game-title";
+ int selectionSize = 10;
+
+ int gamesSize = 10;
+ Game[] games = new Game[gamesSize];
+ for (int i = 0; i < gamesSize; i++)
+ games[i] = new Game(id + i, title + i, selectionSize + i);
+
+ for (int i = 0; i < gamesSize; i++) {
+ assertEquals(games[i].getId(), id + i);
+ assertEquals(games[i].getTitle(), title + i);
+ assertEquals(games[i].getSelectionSize(), selectionSize + i);
+ }
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test clear().
+ //
+ public void testClear() {
+ try {
+ Game g = new Game("test-game-id", "", 10);
+ Expansion x1 = new Expansion("test-expansion-id1", "", 1000);
+ Expansion x2 = new Expansion("test-expansion-id2", "", 1010);
+ g.addExpansion(x1);
+ g.addExpansion(x2);
+ g.addCard(new Card("test-card-id1", "", "", x1));
+ g.addCard(new Card("test-card-id1", "", "", x2)); // duplicate
+ g.addCard(new Card("test-card-id2", "", "", x2));
+ g.addCard(new Card("test-card-id3", "", "", x2));
+ assertTrue(g.getExpansions().size() > 0);
+ assertTrue(g.getCards().size() > 0);
+
+ g.clear();
+
+ assertTrue(g.getExpansions().size() == 0);
+ assertTrue(g.getCards().size() == 0);
+
+ //
+ // Check if duplicate cards are also removed by clear().
+ //
+ g.addCard(new Card("test-card-id1", "", "", x1));
+ g.removeCard(new Card("test-card-id1", "", "", x1));
+
+ assertTrue(g.getCards().size() == 0);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Compare 'List<Expansion>' and 'Expansion ...expansions'.
+ //
+ public boolean compareExpansions(List<Expansion> list,
+ Expansion ...expansions) {
+ int i = 0;
+ for (Expansion x : expansions) {
+ if (list.size() >= i || !x.equals(list.get(i)))
+ return false;
+ i++;
+ }
+ return true;
+ }
+
+ //
+ // Compare 'List<Card>' and 'Card ...cards'.
+ //
+ public boolean compareCards(List<Card> list, Card ...cards) {
+ int i = 0;
+ for (Card c : cards) {
+ if (list.size() >= i || !c.equals(list.get(i)))
+ return false;
+ i++;
+ }
+ return true;
+ }
+
+ //
+ // Test addExpansion().
+ //
+ public void testAddExpansion() {
+ try {
+ Game g = new Game("test-game-id", "", 10);
+
+ Expansion x1 = new Expansion("test-expansion-id1", "", 1010);
+ assertTrue(g.addExpansion(x1));
+ compareExpansions(g.getExpansions(), x1);
+
+ Expansion x2 = new Expansion("test-expansion-id2", "", 1020);
+ assertTrue(g.addExpansion(x2));
+ compareExpansions(g.getExpansions(), x1, x2);
+
+ Expansion x3 = new Expansion("test-expansion-id3", "", 1000);
+ assertTrue(g.addExpansion(x3));
+ compareExpansions(g.getExpansions(), x3, x1, x2);
+
+ Expansion x4 = new Expansion("test-expansion-id3", "?", 1030);
+ assertFalse(g.addExpansion(x4));
+ compareExpansions(g.getExpansions(), x3, x1, x2);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test addExpansions().
+ //
+ public void testAddExpansions() {
+ try {
+ Game g = new Game("test-game-id", "", 10);
+
+ List<Expansion> xl = new ArrayList<Expansion>();
+ Expansion x1 = new Expansion("test-expansion-id1", "", 1010);
+ Expansion x2 = new Expansion("test-expansion-id2", "", 1020);
+ Expansion x3 = new Expansion("test-expansion-id3", "", 1000);
+
+ assertTrue(g.addExpansion(x1));
+ compareExpansions(g.getExpansions(), x1);
+
+ assertTrue(g.addExpansion(x2));
+ compareExpansions(g.getExpansions(), x1, x2);
+
+ assertTrue(g.addExpansion(x3));
+ compareExpansions(g.getExpansions(), x3, x1, x2);
+
+ Expansion x4 = new Expansion("test-expansion-id3", "?", 1030);
+ assertFalse(g.addExpansion(x4));
+ compareExpansions(g.getExpansions(), x3, x1, x2);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test toString().
+ //
+ public void testToString() {
+ try {
+ String title = "test-game-title";
+ Game g = new Game("test-game-id", title, 10);
+ assertEquals(g.toString(), title);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(GameTest.class);
+ }
+}
--- /dev/null
+//
+// Copyright (c) 2013 Motoyuki Kasahara
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+package jp.sourceforge.gamerandomizerlib;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import junit.framework.*;
+import junit.textui.TestRunner;
+import nedragtna.random.MTRandom;
+
+public final class RandomizerTest extends TestCase {
+ public RandomizerTest(String name) {
+ super(name);
+ }
+
+ //
+ // Create a 'Game' object.
+ //
+ private Game buildGame(int selectionSize, int cardsSize) {
+ Game g = new Game("test-game-id", "", selectionSize);
+ Expansion x = new Expansion("test-expansion-id", "");
+ g.addExpansion(x);
+ for (int i = 0; i < cardsSize; i++)
+ g.addCard(new Card("test-card-id" + i, "", "", x));
+ return g;
+ }
+
+ //
+ // Test Randomizer() and getRandom().
+ //
+ public void testCostructor1() {
+ try {
+ Game g = buildGame(3, 6);
+ Randomizer randomizer = new Randomizer(g);
+ assertTrue(randomizer.getRandom() instanceof MTRandom);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test Randomizer(game, random) and getRandom().
+ //
+ public void testCostructor2() {
+ try {
+ Game g = buildGame(3, 6);
+ Random r = new Random();
+ Randomizer randomizer = new Randomizer(g, r);
+ assertTrue(randomizer.getRandom() instanceof Random);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test getGame().
+ //
+ public void getGame() {
+ try {
+ Game g1 = buildGame(3, 6);
+ Randomizer randomizer = new Randomizer(g1);
+
+ Game g2 = randomizer.getGame();
+ assertTrue(g1.equals(g2));
+
+ List<Expansion> xl1 = g1.getExpansions();
+ List<Expansion> xl2 = g2.getExpansions();
+ assertTrue(xl1.size() == xl2.size());
+ for (int i = 0; i < xl1.size(); i++)
+ assertTrue(xl1.get(i).equals(xl2.get(i)));
+
+ List<Card> cl1 = g1.getCards();
+ List<Card> cl2 = g2.getCards();
+ assertTrue(cl1.size() == cl2.size());
+ for (int i = 0; i < cl1.size(); i++)
+ assertTrue(cl1.get(i).equals(cl2.get(i)));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Validate a list of cards selected by 'Randomizer'.
+ //
+ private boolean validateSelectedCards(List<Card> selectedCards,
+ List<Card> cards) {
+ int selectedSize = selectedCards.size();
+
+ //
+ // Check if all cards in 'selectedCards' are also in 'cards'.
+ //
+ for (int i = 0; i < selectedSize; i++) {
+ Card c = selectedCards.get(i);
+ if (cards.indexOf(c) < 0)
+ return false;
+ }
+
+ //
+ // Check if all cards in 'selectedCards' are unique.
+ //
+ for (int i = 0; i < selectedSize - 1; i++) {
+ Card c = selectedCards.get(i);
+ for (int j = i + 1; j < selectedSize; j++) {
+ if (c.equals(selectedCards.get(j)))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //
+ // Test select(selectedCards) -- a simple case.
+ //
+ public void testSelect1() {
+ try {
+ int selectionSize = 3;
+ int cardsSize = 6;
+ Game g = buildGame(selectionSize, cardsSize);
+
+ Randomizer randomizer = new Randomizer(g);
+ List<Card> cl = new ArrayList<Card>();
+ int selectResult = randomizer.select(cl);
+
+ assertEquals(selectResult, selectionSize);
+ assertEquals(cl.size(), selectionSize);
+ validateSelectedCards(cl, g.getCards());
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test select(selectedCards) -- 'selectedCards' is not an empty.
+ //
+ public void testSelect2() {
+ try {
+ int selectionSize = 4;
+ int cardsSize = 8;
+ Game g = buildGame(selectionSize, cardsSize);
+
+ Randomizer randomizer = new Randomizer(g);
+ List<Card> cl = new ArrayList<Card>();
+ cl.add(g.getCard(0));
+ cl.add(g.getCard(1));
+ int selectResult = randomizer.select(cl);
+
+ assertEquals(selectResult, selectionSize);
+ assertEquals(cl.size(), selectionSize);
+ assertEquals(cl.get(0), g.getCard(0));
+ assertEquals(cl.get(1), g.getCard(1));
+ validateSelectedCards(cl, g.getCards());
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test select(selectedCards) -- 'selectedCards' is too long.
+ //
+ //
+ public void testSelect3() {
+ try {
+ int selectionSize = 3;
+ int cardsSize = 6;
+ Game g = buildGame(selectionSize, cardsSize);
+
+ Randomizer randomizer = new Randomizer(g);
+ List<Card> cl = new ArrayList<Card>(g.getCards());
+ int selectResult = randomizer.select(cl);
+
+ assertEquals(selectResult, selectionSize);
+ assertEquals(cl.size(), selectionSize);
+ validateSelectedCards(cl, g.getCards());
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test select(selectedCards) -- not enough cards.
+ //
+ public void testSelect4() {
+ try {
+ int selectionSize = 3;
+ int cardsSize = 2;
+ Game g = buildGame(selectionSize, cardsSize);
+
+ Randomizer randomizer = new Randomizer(g);
+ List<Card> cl = new ArrayList<Card>();
+ int selectResult = randomizer.select(cl);
+
+ assertEquals(selectResult, cardsSize);
+ assertEquals(cl.size(), cardsSize);
+ validateSelectedCards(cl, g.getCards());
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ //
+ // Test toString().
+ //
+ public void testToString() {
+ try {
+ Game g = buildGame(3, 6);
+ Randomizer randomizer = new Randomizer(g);
+ String endString = "Randomizer for " + g.getTitle();
+ assertTrue(randomizer.toString().endsWith(endString));
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(RandomizerTest.class);
+ }
+}
--- /dev/null
+GameRandomizer
+==============
+GameRandomizer is an application to select cards randomly for deck building
+board/card games. (One of the famous game is Dominion.)
+It is running on Android 2.x or later.
+
+GameRandomizer supports multiple games. You can switch a game easily by
+choosing a game from a list. However, it doesn't have built-in game data.
+You need to get them from this site (or somewhere), or create data by
+yourself. Putting data files on the specified folder, GameRandomizer reads
+them automatically when it starts or the "Reload data" command is requested.
+
+As of writing, data for the following games are available:
+
+* Dominion
+* Tanto Cuore
+* Heart of Crown
+* TRAINS
+* Touhou Shisouroku
+* NITROPLUS CARD MASTERS
+
+Note that GameRandomizer itself is NOT an official application for those
+games. It is NOT provided by publishers or authors of those games.
+
+GameRandomizer is a free software, you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This software is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="dominion"
+ revision="1" format_version="1" language="en" country="">
+ <game_title>Dominion</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="alchemy" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1040</preference>
+ <expansion_title>Alchemy</expansion_title>
+
+ <card id="herbalist">
+ <title>Herbalist</title>
+ <price>2</price>
+ </card>
+
+ <card id="apprentice">
+ <title>Apprentice</title>
+ <price>5</price>
+ </card>
+
+ <card id="transmute">
+ <title>Transmute</title>
+ <price>0</price>
+ </card>
+
+ <card id="vineyard">
+ <title>Vineyard</title>
+ <price>0</price>
+ </card>
+
+ <card id="apothecary">
+ <title>Apothecary</title>
+ <price>2</price>
+ </card>
+
+ <card id="scrying-pool">
+ <title>Scrying Pool</title>
+ <price>2</price>
+ </card>
+
+ <card id="university">
+ <title>University</title>
+ <price>2</price>
+ </card>
+
+ <card id="alchemist">
+ <title>Alchemist</title>
+ <price>3</price>
+ </card>
+
+ <card id="familiar">
+ <title>Familiar</title>
+ <price>3</price>
+ </card>
+
+ <card id="philosophers-stone">
+ <title>Philosopher's Stone</title>
+ <price>3</price>
+ </card>
+
+ <card id="golem">
+ <title>Golem</title>
+ <price>4</price>
+ </card>
+
+ <card id="possession">
+ <title>Possession</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="cornucopia" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1060</preference>
+ <expansion_title>Cornucopia</expansion_title>
+
+ <card id="hamlet">
+ <title>Hamlet</title>
+ <price>2</price>
+ </card>
+
+ <card id="fortune-teller">
+ <title>Fortune Teller</title>
+ <price>3</price>
+ </card>
+
+ <card id="menagerie">
+ <title>Menagerie</title>
+ <price>3</price>
+ </card>
+
+ <card id="farming-village">
+ <title>Farming Village</title>
+ <price>4</price>
+ </card>
+
+ <card id="horse-traders">
+ <title>Horse Traders</title>
+ <price>4</price>
+ </card>
+
+ <card id="remake">
+ <title>Remake</title>
+ <price>4</price>
+ </card>
+
+ <card id="tournament">
+ <title>Tournament</title>
+ <price>4</price>
+ </card>
+
+ <card id="young-witch">
+ <title>Young Witch</title>
+ <price>4</price>
+ </card>
+
+ <card id="harvest">
+ <title>Harvest</title>
+ <price>5</price>
+ </card>
+
+ <card id="horn-of-plenty">
+ <title>Horn of Plenty</title>
+ <price>5</price>
+ </card>
+
+ <card id="hunting-party">
+ <title>Hunting Party</title>
+ <price>5</price>
+ </card>
+
+ <card id="jester">
+ <title>Jester</title>
+ <price>5</price>
+ </card>
+
+ <card id="fairgrounds">
+ <title>Fairgrounds</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="dark-ages" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1080</preference>
+ <expansion_title>Dark Ages</expansion_title>
+
+ <card id="poor-house">
+ <title>Poor House</title>
+ <price>1</price>
+ </card>
+
+ <card id="beggar">
+ <title>Beggar</title>
+ <price>2</price>
+ </card>
+
+ <card id="squire">
+ <title>Squire</title>
+ <price>2</price>
+ </card>
+
+ <card id="vagrant">
+ <title>Vagrant</title>
+ <price>2</price>
+ </card>
+
+ <card id="forager">
+ <title>Forager</title>
+ <price>3</price>
+ </card>
+
+ <card id="hermit">
+ <title>Hermit</title>
+ <price>3</price>
+ </card>
+
+ <card id="market-square">
+ <title>Market Square</title>
+ <price>3</price>
+ </card>
+
+ <card id="sage">
+ <title>Sage</title>
+ <price>3</price>
+ </card>
+
+ <card id="storeroom">
+ <title>Storeroom</title>
+ <price>3</price>
+ </card>
+
+ <card id="urchin">
+ <title>Urchin</title>
+ <price>3</price>
+ </card>
+
+ <card id="armory">
+ <title>Armory</title>
+ <price>4</price>
+ </card>
+
+ <card id="death-cart">
+ <title>Death Cart</title>
+ <price>4</price>
+ </card>
+
+ <card id="feodum">
+ <title>Feodum</title>
+ <price>4</price>
+ </card>
+
+ <card id="fortress">
+ <title>Fortress</title>
+ <price>4</price>
+ </card>
+
+ <card id="ironmonger">
+ <title>Ironmonger</title>
+ <price>4</price>
+ </card>
+
+ <card id="marauder">
+ <title>Marauder</title>
+ <price>4</price>
+ </card>
+
+ <card id="procession">
+ <title>Procession</title>
+ <price>4</price>
+ </card>
+
+ <card id="rats">
+ <title>Rats</title>
+ <price>4</price>
+ </card>
+
+ <card id="scavenger">
+ <title>Scavenger</title>
+ <price>4</price>
+ </card>
+
+ <card id="wandering-minstrel">
+ <title>Wandering Minstrel</title>
+ <price>4</price>
+ </card>
+
+ <card id="band-of-misfits">
+ <title>Band of Misfits</title>
+ <price>5</price>
+ </card>
+
+ <card id="bandit-camp">
+ <title>Bandit Camp</title>
+ <price>5</price>
+ </card>
+
+ <card id="catacombs">
+ <title>Catacombs</title>
+ <price>5</price>
+ </card>
+
+ <card id="count">
+ <title>Count</title>
+ <price>5</price>
+ </card>
+
+ <card id="counterfeit">
+ <title>Counterfeit</title>
+ <price>5</price>
+ </card>
+
+ <card id="cultist">
+ <title>Cultist</title>
+ <price>5</price>
+ </card>
+
+ <card id="graverobber">
+ <title>Graverobber</title>
+ <price>5</price>
+ </card>
+
+ <card id="junk-dealer">
+ <title>Junk Dealer</title>
+ <price>5</price>
+ </card>
+
+ <card id="mystic">
+ <title>Mystic</title>
+ <price>5</price>
+ </card>
+
+ <card id="pillage">
+ <title>Pillage</title>
+ <price>5</price>
+ </card>
+
+ <card id="rebuild">
+ <title>Rebuild</title>
+ <price>5</price>
+ </card>
+
+ <card id="rogue">
+ <title>Rogue</title>
+ <price>5</price>
+ </card>
+
+ <card id="altar">
+ <title>Altar</title>
+ <price>6</price>
+ </card>
+
+ <card id="hunting-grounds">
+ <title>Hunting Grounds</title>
+ <price>6</price>
+ </card>
+
+ <card id="knights">
+ <title>Knights</title>
+ <price>?</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="dominion" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1010</preference>
+ <expansion_title>Dominion</expansion_title>
+
+ <card id="cellar">
+ <title>Cellar</title>
+ <price>2</price>
+ </card>
+
+ <card id="chapel">
+ <title>Chapel</title>
+ <price>2</price>
+ </card>
+
+ <card id="moat">
+ <title>Moat</title>
+ <price>2</price>
+ </card>
+
+ <card id="chancellor">
+ <title>Chancellor</title>
+ <price>3</price>
+ </card>
+
+ <card id="village">
+ <title>Village</title>
+ <price>3</price>
+ </card>
+
+ <card id="woodcutter">
+ <title>Woodcutter</title>
+ <price>3</price>
+ </card>
+
+ <card id="workshop">
+ <title>Workshop</title>
+ <price>3</price>
+ </card>
+
+ <card id="bureaucrat">
+ <title>Bureaucrat</title>
+ <price>4</price>
+ </card>
+
+ <card id="feast">
+ <title>Feast</title>
+ <price>4</price>
+ </card>
+
+ <card id="gardens">
+ <title>Gardens</title>
+ <price>4</price>
+ </card>
+
+ <card id="militia">
+ <title>Militia</title>
+ <price>4</price>
+ </card>
+
+ <card id="moneylender">
+ <title>Moneylender</title>
+ <price>4</price>
+ </card>
+
+ <card id="remodel">
+ <title>Remodel</title>
+ <price>4</price>
+ </card>
+
+ <card id="smithy">
+ <title>Smithy</title>
+ <price>4</price>
+ </card>
+
+ <card id="spy">
+ <title>Spy</title>
+ <price>4</price>
+ </card>
+
+ <card id="thief">
+ <title>Thief</title>
+ <price>4</price>
+ </card>
+
+ <card id="throne-room">
+ <title>Throne Room</title>
+ <price>4</price>
+ </card>
+
+ <card id="council-room">
+ <title>Council Room</title>
+ <price>5</price>
+ </card>
+
+ <card id="festival">
+ <title>Festival</title>
+ <price>5</price>
+ </card>
+
+ <card id="laboratory">
+ <title>Laboratory</title>
+ <price>5</price>
+ </card>
+
+ <card id="library">
+ <title>Library</title>
+ <price>5</price>
+ </card>
+
+ <card id="market">
+ <title>Market</title>
+ <price>5</price>
+ </card>
+
+ <card id="mine">
+ <title>Mine</title>
+ <price>5</price>
+ </card>
+
+ <card id="witch">
+ <title>Witch</title>
+ <price>5</price>
+ </card>
+
+ <card id="adventurer">
+ <title>Adventurer</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="guilds" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1090</preference>
+ <expansion_title>Guilds</expansion_title>
+
+ <card id="candlestick-maker">
+ <title>Candlestick Maker</title>
+ <price>2</price>
+ </card>
+
+ <card id="stonemason">
+ <title>Stonemason</title>
+ <price>2+</price>
+ </card>
+
+ <card id="doctor">
+ <title>Doctor</title>
+ <price>3+</price>
+ </card>
+
+ <card id="masterpiece">
+ <title>Masterpiece</title>
+ <price>3+</price>
+ </card>
+
+ <card id="advisor">
+ <title>Advisor</title>
+ <price>4</price>
+ </card>
+
+ <card id="herald">
+ <title>Herald</title>
+ <price>4+</price>
+ </card>
+
+ <card id="plaza">
+ <title>Plaza</title>
+ <price>4</price>
+ </card>
+
+ <card id="taxman">
+ <title>Taxman</title>
+ <price>4</price>
+ </card>
+
+ <card id="baker">
+ <title>Baker</title>
+ <price>5</price>
+ </card>
+
+ <card id="butcher">
+ <title>Butcher</title>
+ <price>5</price>
+ </card>
+
+ <card id="journeyman">
+ <title>Journeyman</title>
+ <price>5</price>
+ </card>
+
+ <card id="merchant-guild">
+ <title>Merchant Guild</title>
+ <price>5</price>
+ </card>
+
+ <card id="soothsayer">
+ <title>Soothsayer</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="hinterlands" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1070</preference>
+ <expansion_title>Hinterlands</expansion_title>
+
+ <card id="crossroads">
+ <title>Crossroads</title>
+ <price>2</price>
+ </card>
+
+ <card id="duchess">
+ <title>Duchess</title>
+ <price>2</price>
+ </card>
+
+ <card id="fools-gold">
+ <title>Fool's Gold</title>
+ <price>2</price>
+ </card>
+
+ <card id="develop">
+ <title>Develop</title>
+ <price>3</price>
+ </card>
+
+ <card id="oasis">
+ <title>Oasis</title>
+ <price>3</price>
+ </card>
+
+ <card id="oracle">
+ <title>Oracle</title>
+ <price>3</price>
+ </card>
+
+ <card id="scheme">
+ <title>Scheme</title>
+ <price>3</price>
+ </card>
+
+ <card id="tunnel">
+ <title>Tunnel</title>
+ <price>3</price>
+ </card>
+
+ <card id="jack-of-all-trades">
+ <title>Jack of All Trades</title>
+ <price>4</price>
+ </card>
+
+ <card id="noble-brigand">
+ <title>Noble Brigand</title>
+ <price>4</price>
+ </card>
+
+ <card id="nomad-camp">
+ <title>Nomad Camp</title>
+ <price>Action</price>
+ </card>
+
+ <card id="silk-road">
+ <title>Silk Road</title>
+ <price>4</price>
+ </card>
+
+ <card id="spice-merchant">
+ <title>Spice Merchant</title>
+ <price>4</price>
+ </card>
+
+ <card id="trader">
+ <title>Trader</title>
+ <price>4</price>
+ </card>
+
+ <card id="cache">
+ <title>Cache</title>
+ <price>5</price>
+ </card>
+
+ <card id="cartographer">
+ <title>Cartographer</title>
+ <price>5</price>
+ </card>
+
+ <card id="embassy">
+ <title>Embassy</title>
+ <price>5</price>
+ </card>
+
+ <card id="haggler">
+ <title>Haggler</title>
+ <price>5</price>
+ </card>
+
+ <card id="highway">
+ <title>Highway</title>
+ <price>5</price>
+ </card>
+
+ <card id="ill-gotten-gains">
+ <title>Ill-Gotten Gains</title>
+ <price>5</price>
+ </card>
+
+ <card id="inn">
+ <title>Inn</title>
+ <price>5</price>
+ </card>
+
+ <card id="mandarin">
+ <title>Mandarin</title>
+ <price>5</price>
+ </card>
+
+ <card id="margrave">
+ <title>Margrave</title>
+ <price>5</price>
+ </card>
+
+ <card id="stables">
+ <title>Stables</title>
+ <price>5</price>
+ </card>
+
+ <card id="border-village">
+ <title>Border Village</title>
+ <price>6</price>
+ </card>
+
+ <card id="farmland">
+ <title>Farmland</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="intrigue" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1020</preference>
+ <expansion_title>Intrigue</expansion_title>
+
+ <card id="courtyard">
+ <title>Courtyard</title>
+ <price>2</price>
+ </card>
+
+ <card id="pawn">
+ <title>Pawn</title>
+ <price>2</price>
+ </card>
+
+ <card id="secret-chamber">
+ <title>Secret Chamber</title>
+ <price>2</price>
+ </card>
+
+ <card id="great-hall">
+ <title>Great Hall</title>
+ <price>3</price>
+ </card>
+
+ <card id="masquerade">
+ <title>Masquerade</title>
+ <price>3</price>
+ </card>
+
+ <card id="shanty-town">
+ <title>Shanty Town</title>
+ <price>3</price>
+ </card>
+
+ <card id="steward">
+ <title>Steward</title>
+ <price>3</price>
+ </card>
+
+ <card id="swindler">
+ <title>Swindler</title>
+ <price>3</price>
+ </card>
+
+ <card id="wishing-well">
+ <title>Wishing Well</title>
+ <price>3</price>
+ </card>
+
+ <card id="baron">
+ <title>Baron</title>
+ <price>4</price>
+ </card>
+
+ <card id="bridge">
+ <title>Bridge</title>
+ <price>4</price>
+ </card>
+
+ <card id="conspirator">
+ <title>Conspirator</title>
+ <price>4</price>
+ </card>
+
+ <card id="coppersmith">
+ <title>Coppersmith</title>
+ <price>4</price>
+ </card>
+
+ <card id="ironworks">
+ <title>Ironworks</title>
+ <price>4</price>
+ </card>
+
+ <card id="mining-village">
+ <title>Mining Village</title>
+ <price>4</price>
+ </card>
+
+ <card id="scout">
+ <title>Scout</title>
+ <price>4</price>
+ </card>
+
+ <card id="duke">
+ <title>Duke</title>
+ <price>5</price>
+ </card>
+
+ <card id="minion">
+ <title>Minion</title>
+ <price>5</price>
+ </card>
+
+ <card id="saboteur">
+ <title>Saboteur</title>
+ <price>5</price>
+ </card>
+
+ <card id="torturer">
+ <title>Torturer</title>
+ <price>5</price>
+ </card>
+
+ <card id="trading-post">
+ <title>Trading Post</title>
+ <price>5</price>
+ </card>
+
+ <card id="tribute">
+ <title>Tribute</title>
+ <price>5</price>
+ </card>
+
+ <card id="upgrade">
+ <title>Upgrade</title>
+ <price>5</price>
+ </card>
+
+ <card id="harem">
+ <title>Harem</title>
+ <price>6</price>
+ </card>
+
+ <card id="nobles">
+ <title>Nobles</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-black-market" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>2020</preference>
+ <expansion_title>Black Market (promo)</expansion_title>
+
+ <card id="black-market">
+ <title>Black Market</title>
+ <price>3</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-envoy" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>2010</preference>
+ <expansion_title>Envoy (promo)</expansion_title>
+
+ <card id="envoy">
+ <title>Envoy</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-governor" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>2050</preference>
+ <expansion_title>Governor (promo)</expansion_title>
+
+ <card id="governor">
+ <title>Governor</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-stash" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>2030</preference>
+ <expansion_title>Stash (promo)</expansion_title>
+
+ <card id="stash">
+ <title>Stash</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-walled-village" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>2040</preference>
+ <expansion_title>Walled Village (promo)</expansion_title>
+
+ <card id="walled-village">
+ <title>Walled Village</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="prosperity" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1050</preference>
+ <expansion_title>Prosperity</expansion_title>
+
+ <card id="loan">
+ <title>Loan</title>
+ <price>3</price>
+ </card>
+
+ <card id="trade-route">
+ <title>Trade Route</title>
+ <price>3</price>
+ </card>
+
+ <card id="watchtower">
+ <title>Watchtower</title>
+ <price>3</price>
+ </card>
+
+ <card id="bishop">
+ <title>Bishop</title>
+ <price>4</price>
+ </card>
+
+ <card id="monument">
+ <title>Monument</title>
+ <price>4</price>
+ </card>
+
+ <card id="quarry">
+ <title>Quarry</title>
+ <price>4</price>
+ </card>
+
+ <card id="talisman">
+ <title>Talisman</title>
+ <price>4</price>
+ </card>
+
+ <card id="workers-village">
+ <title>Worker's Village</title>
+ <price>4</price>
+ </card>
+
+ <card id="city">
+ <title>City</title>
+ <price>5</price>
+ </card>
+
+ <card id="contraband">
+ <title>Contraband</title>
+ <price>5</price>
+ </card>
+
+ <card id="counting-house">
+ <title>Counting House</title>
+ <price>5</price>
+ </card>
+
+ <card id="mint">
+ <title>Mint</title>
+ <price>5</price>
+ </card>
+
+ <card id="mountebank">
+ <title>Mountebank</title>
+ <price>5</price>
+ </card>
+
+ <card id="rabble">
+ <title>Rabble</title>
+ <price>5</price>
+ </card>
+
+ <card id="royal-seal">
+ <title>Royal Seal</title>
+ <price>5</price>
+ </card>
+
+ <card id="vault">
+ <title>Vault</title>
+ <price>5</price>
+ </card>
+
+ <card id="venture">
+ <title>Venture</title>
+ <price>5</price>
+ </card>
+
+ <card id="goons">
+ <title>Goons</title>
+ <price>6</price>
+ </card>
+
+ <card id="grand-market">
+ <title>Grand Market</title>
+ <price>6</price>
+ </card>
+
+ <card id="hoard">
+ <title>Hoard</title>
+ <price>6</price>
+ </card>
+
+ <card id="bank">
+ <title>Bank</title>
+ <price>7</price>
+ </card>
+
+ <card id="expand">
+ <title>Expand</title>
+ <price>7</price>
+ </card>
+
+ <card id="forge">
+ <title>Forge</title>
+ <price>7</price>
+ </card>
+
+ <card id="kings-court">
+ <title>King's Court</title>
+ <price>7</price>
+ </card>
+
+ <card id="peddler">
+ <title>Peddler</title>
+ <price>8*</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="seaside" game_id="dominion"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1030</preference>
+ <expansion_title>Seaside</expansion_title>
+
+ <card id="embargo">
+ <title>Embargo</title>
+ <price>2</price>
+ </card>
+
+ <card id="haven">
+ <title>Haven</title>
+ <price>2</price>
+ </card>
+
+ <card id="lighthouse">
+ <title>Lighthouse</title>
+ <price>2</price>
+ </card>
+
+ <card id="native-village">
+ <title>Native Village</title>
+ <price>2</price>
+ </card>
+
+ <card id="pearl-diver">
+ <title>Pearl Diver</title>
+ <price>2</price>
+ </card>
+
+ <card id="ambassador">
+ <title>Ambassador</title>
+ <price>3</price>
+ </card>
+
+ <card id="fishing-village">
+ <title>Fishing Village</title>
+ <price>3</price>
+ </card>
+
+ <card id="lookout">
+ <title>Lookout</title>
+ <price>3</price>
+ </card>
+
+ <card id="smugglers">
+ <title>Smugglers</title>
+ <price>3</price>
+ </card>
+
+ <card id="warehouse">
+ <title>Warehouse</title>
+ <price>3</price>
+ </card>
+
+ <card id="caravan">
+ <title>Caravan</title>
+ <price>4</price>
+ </card>
+
+ <card id="cutpurse">
+ <title>Cutpurse</title>
+ <price>4</price>
+ </card>
+
+ <card id="island">
+ <title>Island</title>
+ <price>4</price>
+ </card>
+
+ <card id="navigator">
+ <title>Navigator</title>
+ <price>4</price>
+ </card>
+
+ <card id="pirate-ship">
+ <title>Pirate Ship</title>
+ <price>4</price>
+ </card>
+
+ <card id="salvager">
+ <title>Salvager</title>
+ <price>4</price>
+ </card>
+
+ <card id="sea-hag">
+ <title>Sea Hag</title>
+ <price>4</price>
+ </card>
+
+ <card id="treasure-map">
+ <title>Treasure Map</title>
+ <price>4</price>
+ </card>
+
+ <card id="bazaar">
+ <title>Bazaar</title>
+ <price>5</price>
+ </card>
+
+ <card id="explorer">
+ <title>Explorer</title>
+ <price>5</price>
+ </card>
+
+ <card id="ghost-ship">
+ <title>Ghost Ship</title>
+ <price>5</price>
+ </card>
+
+ <card id="merchant-ship">
+ <title>Merchant Ship</title>
+ <price>5</price>
+ </card>
+
+ <card id="outpost">
+ <title>Outpost</title>
+ <price>5</price>
+ </card>
+
+ <card id="tactician">
+ <title>Tactician</title>
+ <price>5</price>
+ </card>
+
+ <card id="treasury">
+ <title>Treasury</title>
+ <price>5</price>
+ </card>
+
+ <card id="wharf">
+ <title>Wharf</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+ <game_title>ドミニオン</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="alchemy" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1040</preference>
+ <expansion_title>錬金術</expansion_title>
+
+ <card id="herbalist">
+ <title>薬草商</title>
+ <price>2</price>
+ </card>
+
+ <card id="apprentice">
+ <title>弟子</title>
+ <price>5</price>
+ </card>
+
+ <card id="transmute">
+ <title>変成</title>
+ <price>0</price>
+ </card>
+
+ <card id="vineyard">
+ <title>ブドウ園</title>
+ <price>0</price>
+ </card>
+
+ <card id="apothecary">
+ <title>薬師</title>
+ <price>2</price>
+ </card>
+
+ <card id="scrying-pool">
+ <title>念視の泉</title>
+ <price>2</price>
+ </card>
+
+ <card id="university">
+ <title>大学</title>
+ <price>2</price>
+ </card>
+
+ <card id="alchemist">
+ <title>錬金術師</title>
+ <price>3</price>
+ </card>
+
+ <card id="familiar">
+ <title>使い魔</title>
+ <price>3</price>
+ </card>
+
+ <card id="philosophers-stone">
+ <title>賢者の石</title>
+ <price>3</price>
+ </card>
+
+ <card id="golem">
+ <title>ゴーレム</title>
+ <price>4</price>
+ </card>
+
+ <card id="possession">
+ <title>支配</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="cornucopia" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1060</preference>
+ <expansion_title>収穫祭</expansion_title>
+
+ <card id="hamlet">
+ <title>村落</title>
+ <price>2</price>
+ </card>
+
+ <card id="fortune-teller">
+ <title>占い師</title>
+ <price>3</price>
+ </card>
+
+ <card id="menagerie">
+ <title>移動動物園</title>
+ <price>3</price>
+ </card>
+
+ <card id="farming-village">
+ <title>農村</title>
+ <price>4</price>
+ </card>
+
+ <card id="horse-traders">
+ <title>馬商人</title>
+ <price>4</price>
+ </card>
+
+ <card id="remake">
+ <title>再建</title>
+ <price>4</price>
+ </card>
+
+ <card id="tournament">
+ <title>馬上槍試合</title>
+ <price>4</price>
+ </card>
+
+ <card id="young-witch">
+ <title>魔女娘</title>
+ <price>4</price>
+ </card>
+
+ <card id="harvest">
+ <title>収穫</title>
+ <price>5</price>
+ </card>
+
+ <card id="horn-of-plenty">
+ <title>豊穣の角笛</title>
+ <price>5</price>
+ </card>
+
+ <card id="hunting-party">
+ <title>狩猟団</title>
+ <price>5</price>
+ </card>
+
+ <card id="jester">
+ <title>道化師</title>
+ <price>5</price>
+ </card>
+
+ <card id="fairgrounds">
+ <title>品評会</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="dark-ages" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1080</preference>
+ <expansion_title>暗黒時代</expansion_title>
+
+ <card id="poor-house">
+ <title>救貧院</title>
+ <price>1</price>
+ </card>
+
+ <card id="beggar">
+ <title>物乞い</title>
+ <price>2</price>
+ </card>
+
+ <card id="squire">
+ <title>従者</title>
+ <price>2</price>
+ </card>
+
+ <card id="vagrant">
+ <title>浮浪者</title>
+ <price>2</price>
+ </card>
+
+ <card id="forager">
+ <title>採集者</title>
+ <price>3</price>
+ </card>
+
+ <card id="hermit">
+ <title>隠遁者</title>
+ <price>3</price>
+ </card>
+
+ <card id="market-square">
+ <title>青空市場</title>
+ <price>3</price>
+ </card>
+
+ <card id="sage">
+ <title>賢者</title>
+ <price>3</price>
+ </card>
+
+ <card id="storeroom">
+ <title>物置</title>
+ <price>3</price>
+ </card>
+
+ <card id="urchin">
+ <title>浮浪児</title>
+ <price>3</price>
+ </card>
+
+ <card id="armory">
+ <title>武器庫</title>
+ <price>4</price>
+ </card>
+
+ <card id="death-cart">
+ <title>死の荷車</title>
+ <price>4</price>
+ </card>
+
+ <card id="feodum">
+ <title>封土</title>
+ <price>4</price>
+ </card>
+
+ <card id="fortress">
+ <title>城塞</title>
+ <price>4</price>
+ </card>
+
+ <card id="ironmonger">
+ <title>金物商</title>
+ <price>4</price>
+ </card>
+
+ <card id="marauder">
+ <title>襲撃者</title>
+ <price>4</price>
+ </card>
+
+ <card id="procession">
+ <title>行進</title>
+ <price>4</price>
+ </card>
+
+ <card id="rats">
+ <title>ネズミ</title>
+ <price>4</price>
+ </card>
+
+ <card id="scavenger">
+ <title>ゴミあさり</title>
+ <price>4</price>
+ </card>
+
+ <card id="wandering-minstrel">
+ <title>吟遊詩人</title>
+ <price>4</price>
+ </card>
+
+ <card id="band-of-misfits">
+ <title>はみだし者</title>
+ <price>5</price>
+ </card>
+
+ <card id="bandit-camp">
+ <title>山賊の宿営地</title>
+ <price>5</price>
+ </card>
+
+ <card id="catacombs">
+ <title>地下墓所</title>
+ <price>5</price>
+ </card>
+
+ <card id="count">
+ <title>伯爵</title>
+ <price>5</price>
+ </card>
+
+ <card id="counterfeit">
+ <title>偽造通貨</title>
+ <price>5</price>
+ </card>
+
+ <card id="cultist">
+ <title>狂信者</title>
+ <price>5</price>
+ </card>
+
+ <card id="graverobber">
+ <title>墓暴き</title>
+ <price>5</price>
+ </card>
+
+ <card id="junk-dealer">
+ <title>屑屋</title>
+ <price>5</price>
+ </card>
+
+ <card id="mystic">
+ <title>秘術師</title>
+ <price>5</price>
+ </card>
+
+ <card id="pillage">
+ <title>略奪</title>
+ <price>5</price>
+ </card>
+
+ <card id="rebuild">
+ <title>建て直し</title>
+ <price>5</price>
+ </card>
+
+ <card id="rogue">
+ <title>盗賊</title>
+ <price>5</price>
+ </card>
+
+ <card id="altar">
+ <title>祭壇</title>
+ <price>6</price>
+ </card>
+
+ <card id="hunting-grounds">
+ <title>狩場</title>
+ <price>6</price>
+ </card>
+
+ <card id="knights">
+ <title>騎士</title>
+ <price>?</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="dominion" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1010</preference>
+ <expansion_title>ドミニオン</expansion_title>
+
+ <card id="cellar">
+ <title>地下貯蔵庫</title>
+ <price>2</price>
+ </card>
+
+ <card id="chapel">
+ <title>礼拝堂</title>
+ <price>2</price>
+ </card>
+
+ <card id="moat">
+ <title>堀</title>
+ <price>2</price>
+ </card>
+
+ <card id="chancellor">
+ <title>宰相</title>
+ <price>3</price>
+ </card>
+
+ <card id="village">
+ <title>村</title>
+ <price>3</price>
+ </card>
+
+ <card id="woodcutter">
+ <title>木こり</title>
+ <price>3</price>
+ </card>
+
+ <card id="workshop">
+ <title>工房</title>
+ <price>3</price>
+ </card>
+
+ <card id="bureaucrat">
+ <title>役人</title>
+ <price>4</price>
+ </card>
+
+ <card id="feast">
+ <title>祝宴</title>
+ <price>4</price>
+ </card>
+
+ <card id="gardens">
+ <title>庭園</title>
+ <price>4</price>
+ </card>
+
+ <card id="militia">
+ <title>民兵</title>
+ <price>4</price>
+ </card>
+
+ <card id="moneylender">
+ <title>金貸し</title>
+ <price>4</price>
+ </card>
+
+ <card id="remodel">
+ <title>改築</title>
+ <price>4</price>
+ </card>
+
+ <card id="smithy">
+ <title>鍛冶屋</title>
+ <price>4</price>
+ </card>
+
+ <card id="spy">
+ <title>密偵</title>
+ <price>4</price>
+ </card>
+
+ <card id="thief">
+ <title>泥棒</title>
+ <price>4</price>
+ </card>
+
+ <card id="throne-room">
+ <title>玉座の間</title>
+ <price>4</price>
+ </card>
+
+ <card id="council-room">
+ <title>議事堂</title>
+ <price>5</price>
+ </card>
+
+ <card id="festival">
+ <title>祝祭</title>
+ <price>5</price>
+ </card>
+
+ <card id="laboratory">
+ <title>研究所</title>
+ <price>5</price>
+ </card>
+
+ <card id="library">
+ <title>書庫</title>
+ <price>5</price>
+ </card>
+
+ <card id="market">
+ <title>市場</title>
+ <price>5</price>
+ </card>
+
+ <card id="mine">
+ <title>鉱山</title>
+ <price>5</price>
+ </card>
+
+ <card id="witch">
+ <title>魔女</title>
+ <price>5</price>
+ </card>
+
+ <card id="adventurer">
+ <title>冒険者</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="guilds" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1090</preference>
+ <expansion_title>ギルド</expansion_title>
+
+ <card id="candlestick-maker">
+ <title>蝋燭職人</title>
+ <price>2</price>
+ </card>
+
+ <card id="stonemason">
+ <title>石工</title>
+ <price>2+</price>
+ </card>
+
+ <card id="doctor">
+ <title>医者</title>
+ <price>3+</price>
+ </card>
+
+ <card id="masterpiece">
+ <title>名品</title>
+ <price>3+</price>
+ </card>
+
+ <card id="advisor">
+ <title>助言者</title>
+ <price>4</price>
+ </card>
+
+ <card id="herald">
+ <title>伝令官</title>
+ <price>4+</price>
+ </card>
+
+ <card id="plaza">
+ <title>広場</title>
+ <price>4</price>
+ </card>
+
+ <card id="taxman">
+ <title>収税吏</title>
+ <price>4</price>
+ </card>
+
+ <card id="baker">
+ <title>パン屋</title>
+ <price>5</price>
+ </card>
+
+ <card id="butcher">
+ <title>肉屋</title>
+ <price>5</price>
+ </card>
+
+ <card id="journeyman">
+ <title>熟練工</title>
+ <price>5</price>
+ </card>
+
+ <card id="merchant-guild">
+ <title>商人ギルド</title>
+ <price>5</price>
+ </card>
+
+ <card id="soothsayer">
+ <title>予言者</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="hinterlands" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1070</preference>
+ <expansion_title>異郷</expansion_title>
+
+ <card id="crossroads">
+ <title>岐路</title>
+ <price>2</price>
+ </card>
+
+ <card id="duchess">
+ <title>公爵夫人</title>
+ <price>2</price>
+ </card>
+
+ <card id="fools-gold">
+ <title>愚者の黄金</title>
+ <price>2</price>
+ </card>
+
+ <card id="develop">
+ <title>開発</title>
+ <price>3</price>
+ </card>
+
+ <card id="oasis">
+ <title>オアシス</title>
+ <price>3</price>
+ </card>
+
+ <card id="oracle">
+ <title>神託</title>
+ <price>3</price>
+ </card>
+
+ <card id="scheme">
+ <title>画策</title>
+ <price>3</price>
+ </card>
+
+ <card id="tunnel">
+ <title>坑道</title>
+ <price>3</price>
+ </card>
+
+ <card id="jack-of-all-trades">
+ <title>よろずや</title>
+ <price>4</price>
+ </card>
+
+ <card id="noble-brigand">
+ <title>義賊</title>
+ <price>4</price>
+ </card>
+
+ <card id="nomad-camp">
+ <title>遊牧民の野営地</title>
+ <price>Action</price>
+ </card>
+
+ <card id="silk-road">
+ <title>シルクロード</title>
+ <price>4</price>
+ </card>
+
+ <card id="spice-merchant">
+ <title>香辛料商人</title>
+ <price>4</price>
+ </card>
+
+ <card id="trader">
+ <title>交易人</title>
+ <price>4</price>
+ </card>
+
+ <card id="cache">
+ <title>埋蔵金</title>
+ <price>5</price>
+ </card>
+
+ <card id="cartographer">
+ <title>地図職人</title>
+ <price>5</price>
+ </card>
+
+ <card id="embassy">
+ <title>大使館</title>
+ <price>5</price>
+ </card>
+
+ <card id="haggler">
+ <title>値切り屋</title>
+ <price>5</price>
+ </card>
+
+ <card id="highway">
+ <title>街道</title>
+ <price>5</price>
+ </card>
+
+ <card id="ill-gotten-gains">
+ <title>不正利得</title>
+ <price>5</price>
+ </card>
+
+ <card id="inn">
+ <title>宿屋</title>
+ <price>5</price>
+ </card>
+
+ <card id="mandarin">
+ <title>官吏</title>
+ <price>5</price>
+ </card>
+
+ <card id="margrave">
+ <title>辺境伯</title>
+ <price>5</price>
+ </card>
+
+ <card id="stables">
+ <title>厩舎</title>
+ <price>5</price>
+ </card>
+
+ <card id="border-village">
+ <title>国境の村</title>
+ <price>6</price>
+ </card>
+
+ <card id="farmland">
+ <title>農地</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="intrigue" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1020</preference>
+ <expansion_title>陰謀</expansion_title>
+
+ <card id="courtyard">
+ <title>中庭</title>
+ <price>2</price>
+ </card>
+
+ <card id="pawn">
+ <title>手先</title>
+ <price>2</price>
+ </card>
+
+ <card id="secret-chamber">
+ <title>秘密の部屋</title>
+ <price>2</price>
+ </card>
+
+ <card id="great-hall">
+ <title>大広間</title>
+ <price>3</price>
+ </card>
+
+ <card id="masquerade">
+ <title>仮面舞踏会</title>
+ <price>3</price>
+ </card>
+
+ <card id="shanty-town">
+ <title>貧民街</title>
+ <price>3</price>
+ </card>
+
+ <card id="steward">
+ <title>執事</title>
+ <price>3</price>
+ </card>
+
+ <card id="swindler">
+ <title>詐欺師</title>
+ <price>3</price>
+ </card>
+
+ <card id="wishing-well">
+ <title>願いの井戸</title>
+ <price>3</price>
+ </card>
+
+ <card id="baron">
+ <title>男爵</title>
+ <price>4</price>
+ </card>
+
+ <card id="bridge">
+ <title>橋</title>
+ <price>4</price>
+ </card>
+
+ <card id="conspirator">
+ <title>共謀者</title>
+ <price>4</price>
+ </card>
+
+ <card id="coppersmith">
+ <title>銅細工師</title>
+ <price>4</price>
+ </card>
+
+ <card id="ironworks">
+ <title>鉄工所</title>
+ <price>4</price>
+ </card>
+
+ <card id="mining-village">
+ <title>鉱山の村</title>
+ <price>4</price>
+ </card>
+
+ <card id="scout">
+ <title>偵察員</title>
+ <price>4</price>
+ </card>
+
+ <card id="duke">
+ <title>公爵</title>
+ <price>5</price>
+ </card>
+
+ <card id="minion">
+ <title>寵臣</title>
+ <price>5</price>
+ </card>
+
+ <card id="saboteur">
+ <title>破壊工作員</title>
+ <price>5</price>
+ </card>
+
+ <card id="torturer">
+ <title>拷問人</title>
+ <price>5</price>
+ </card>
+
+ <card id="trading-post">
+ <title>交易場</title>
+ <price>5</price>
+ </card>
+
+ <card id="tribute">
+ <title>貢物</title>
+ <price>5</price>
+ </card>
+
+ <card id="upgrade">
+ <title>改良</title>
+ <price>5</price>
+ </card>
+
+ <card id="harem">
+ <title>ハーレム</title>
+ <price>6</price>
+ </card>
+
+ <card id="nobles">
+ <title>貴族</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-black-market" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2020</preference>
+ <expansion_title>闇市場 (プロモ)</expansion_title>
+
+ <card id="black-market">
+ <title>闇市場</title>
+ <price>3</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-envoy" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2010</preference>
+ <expansion_title>公使 (プロモ)</expansion_title>
+
+ <card id="envoy">
+ <title>公使</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-governor" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2050</preference>
+ <expansion_title>総督 (プロモ)</expansion_title>
+
+ <card id="governor">
+ <title>総督</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-stash" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2030</preference>
+ <expansion_title>へそくり (プロモ)</expansion_title>
+
+ <card id="stash">
+ <title>へそくり</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-walled-village" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2040</preference>
+ <expansion_title>囲郭村 (プロモ)</expansion_title>
+
+ <card id="walled-village">
+ <title>囲郭村</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="prosperity" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1050</preference>
+ <expansion_title>繁栄</expansion_title>
+
+ <card id="loan">
+ <title>借金</title>
+ <price>3</price>
+ </card>
+
+ <card id="trade-route">
+ <title>交易路</title>
+ <price>3</price>
+ </card>
+
+ <card id="watchtower">
+ <title>望楼</title>
+ <price>3</price>
+ </card>
+
+ <card id="bishop">
+ <title>司教</title>
+ <price>4</price>
+ </card>
+
+ <card id="monument">
+ <title>記念碑</title>
+ <price>4</price>
+ </card>
+
+ <card id="quarry">
+ <title>石切場</title>
+ <price>4</price>
+ </card>
+
+ <card id="talisman">
+ <title>護符</title>
+ <price>4</price>
+ </card>
+
+ <card id="workers-village">
+ <title>労働者の村</title>
+ <price>4</price>
+ </card>
+
+ <card id="city">
+ <title>都市</title>
+ <price>5</price>
+ </card>
+
+ <card id="contraband">
+ <title>禁制品</title>
+ <price>5</price>
+ </card>
+
+ <card id="counting-house">
+ <title>会計所</title>
+ <price>5</price>
+ </card>
+
+ <card id="mint">
+ <title>造幣所</title>
+ <price>5</price>
+ </card>
+
+ <card id="mountebank">
+ <title>香具師</title>
+ <price>5</price>
+ </card>
+
+ <card id="rabble">
+ <title>大衆</title>
+ <price>5</price>
+ </card>
+
+ <card id="royal-seal">
+ <title>玉璽</title>
+ <price>5</price>
+ </card>
+
+ <card id="vault">
+ <title>保管庫</title>
+ <price>5</price>
+ </card>
+
+ <card id="venture">
+ <title>投機</title>
+ <price>5</price>
+ </card>
+
+ <card id="goons">
+ <title>ならず者</title>
+ <price>6</price>
+ </card>
+
+ <card id="grand-market">
+ <title>大市場</title>
+ <price>6</price>
+ </card>
+
+ <card id="hoard">
+ <title>隠し財産</title>
+ <price>6</price>
+ </card>
+
+ <card id="bank">
+ <title>銀行</title>
+ <price>7</price>
+ </card>
+
+ <card id="expand">
+ <title>拡張</title>
+ <price>7</price>
+ </card>
+
+ <card id="forge">
+ <title>鍛造</title>
+ <price>7</price>
+ </card>
+
+ <card id="kings-court">
+ <title>宮廷</title>
+ <price>7</price>
+ </card>
+
+ <card id="peddler">
+ <title>行商人</title>
+ <price>8*</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="seaside" game_id="dominion"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1030</preference>
+ <expansion_title>海辺</expansion_title>
+
+ <card id="embargo">
+ <title>抑留</title>
+ <price>2</price>
+ </card>
+
+ <card id="haven">
+ <title>停泊所</title>
+ <price>2</price>
+ </card>
+
+ <card id="lighthouse">
+ <title>灯台</title>
+ <price>2</price>
+ </card>
+
+ <card id="native-village">
+ <title>原住民の村</title>
+ <price>2</price>
+ </card>
+
+ <card id="pearl-diver">
+ <title>真珠採り</title>
+ <price>2</price>
+ </card>
+
+ <card id="ambassador">
+ <title>大使</title>
+ <price>3</price>
+ </card>
+
+ <card id="fishing-village">
+ <title>漁村</title>
+ <price>3</price>
+ </card>
+
+ <card id="lookout">
+ <title>見張り</title>
+ <price>3</price>
+ </card>
+
+ <card id="smugglers">
+ <title>密輸人</title>
+ <price>3</price>
+ </card>
+
+ <card id="warehouse">
+ <title>倉庫</title>
+ <price>3</price>
+ </card>
+
+ <card id="caravan">
+ <title>隊商</title>
+ <price>4</price>
+ </card>
+
+ <card id="cutpurse">
+ <title>巾着切り</title>
+ <price>4</price>
+ </card>
+
+ <card id="island">
+ <title>島</title>
+ <price>4</price>
+ </card>
+
+ <card id="navigator">
+ <title>航海士</title>
+ <price>4</price>
+ </card>
+
+ <card id="pirate-ship">
+ <title>海賊船</title>
+ <price>4</price>
+ </card>
+
+ <card id="salvager">
+ <title>引揚水夫</title>
+ <price>4</price>
+ </card>
+
+ <card id="sea-hag">
+ <title>海の妖婆</title>
+ <price>4</price>
+ </card>
+
+ <card id="treasure-map">
+ <title>宝の地図</title>
+ <price>4</price>
+ </card>
+
+ <card id="bazaar">
+ <title>バザー</title>
+ <price>5</price>
+ </card>
+
+ <card id="explorer">
+ <title>探検家</title>
+ <price>5</price>
+ </card>
+
+ <card id="ghost-ship">
+ <title>幽霊船</title>
+ <price>5</price>
+ </card>
+
+ <card id="merchant-ship">
+ <title>商船</title>
+ <price>5</price>
+ </card>
+
+ <card id="outpost">
+ <title>前哨地</title>
+ <price>5</price>
+ </card>
+
+ <card id="tactician">
+ <title>策士</title>
+ <price>5</price>
+ </card>
+
+ <card id="treasury">
+ <title>宝物庫</title>
+ <price>5</price>
+ </card>
+
+ <card id="wharf">
+ <title>船着場</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+#! /bin/sh
+#
+# Copyright (c) 2013 Motoyuki Kasahara
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+######################################################################
+#
+# NAME
+# gamedata-zip.sh - create a zip file of game data for GameRandomizer
+#
+# SYNPOSIS
+# gamedata-zip.sh [OPTION...] GAME-ID
+#
+# DESCRIPTION
+# 'gamedata-zip.sh' packs game data files under the '<GAME-ID>'
+# directory as '<GAME-ID>-<VERSION>.zip'. VERSION is gotten
+# from '__version.txt' file at the '<GAME-ID>' directory.
+#
+# It recognizes the following options:
+#
+# -h Show usage of this tool, then exit.
+# -o DIR Output a zip file on DIR. (Default: .)
+# -v Verbose mode.
+#
+VERSION_FILE="__version.txt"
+
+OUTPUT_DIR=.
+VERBOSE=false
+
+GAME=
+
+#
+# Show an argument error message and then exit.
+#
+argument_error() {
+ echo "$1" 1>&2
+ echo "Try '$0 -h' for more information" 1>&2
+ exit 1
+}
+
+#
+# Show help then exit.
+#
+show_help() {
+ echo "Usage: $0 [OPTION...] GAME-ID"
+ echo "Options"
+ echo " -h Show this help, then exit"
+ echo " -o DIR Output a zip file on DIR. (default: .)"
+ echo " -v Verbose mode"
+ exit 0
+}
+
+#
+# Parse command line arguments.
+#
+parse_arguments() {
+ local OPT=
+ while [ $# -gt 0 -o "X$OPT" != X ]; do
+ if [ "X$OPT" = X ]; then
+ if [ "X$1" = 'X--' ]; then
+ shift
+ break
+ fi
+ expr match "$1" '^-..*$' > /dev/null 2>&1 || break
+ OPT=`echo "X$1" | sed -e 's/^X-//'`
+ shift
+ fi
+
+ case "$OPT" in
+ o*)
+ if [ "X$OPT" = "Xo" ]; then
+ [ $# -eq 0 ] && argument_error "$0: missing argument to '-o'"
+ OUTPUT_DIR="$2"
+ shift
+ else
+ OUTPUT_DIR=`echo "X$1" | sed -e 's/^X.//'`
+ fi
+ OPT=
+ ;;
+ h*)
+ exit 0
+ ;;
+ v*)
+ VERBOSE=true
+ OPT=`echo "X$OPT" | sed -e 's/^X.//'`
+ ;;
+ *)
+ OPT=`echo "X$OPT" | sed -e 's/^X\(.\).*$/\1/'`
+ argument_error "$0: invalid option '-$OPT'"
+ esac
+ done
+
+ case $# in
+ 0)
+ argument_error "too few argument"
+ ;;
+ 1)
+ GAME="$1"
+ ;;
+ *) argument_error "too many arguments"
+ ;;
+ esac
+}
+
+create_zip_file() {
+ local VERSION_FILE_PATH="$GAME/$VERSION_FILE"
+ local VERSION=`head -1 $VERSION_FILE_PATH`
+ if [ $? -ne 0 ] ;then
+ echo "$0: failed to read the file: $VERSION_FILE_PATH" 1>&2
+ exit 1
+ fi
+
+ local ZIP_FILE_PATH="${OUTPUT_DIR}/${GAME}-${VERSION}.zip"
+ $VERBOSE && echo "Create $ZIP_FILE_PATH"
+
+ local ZIP_VERBOSE_OPT=-q
+ $VERBOSE && ZIP_VERBOSE_OPT=-v
+ zip -9 $ZIP_VERBOSE_OPT \
+ $ZIP_FILE_PATH $VERSION_FILE_PATH $GAME/*/*.xml
+ if [ $? -ne 0 ] ;then
+ echo "$0: failed to create the zip file: $ZIP_FILE_PATH" 1>&2
+ rm -f $ZIP_FILE_PATH
+ exit 1
+ fi
+
+ $VERBOSE && echo "Create $ZIP_FILE_PATH ... done"
+}
+
+parse_arguments "$@"
+create_zip_file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="heart-of-crown"
+ revision="1" format_version="1" language="en" country="">
+ <game_title>Heart of Crown</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="basic-set" game_id="heart-of-crown"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1010</preference>
+ <expansion_title>Basic Set</expansion_title>
+
+ <card id="kifu">
+ <title>Donation</title>
+ <price>2</price>
+ </card>
+
+ <card id="hayauma">
+ <title>Prized Racehorse</title>
+ <price>2</price>
+ </card>
+
+ <card id="negai-no-izumi">
+ <title>Fountain of Wishes</title>
+ <price>2</price>
+ </card>
+
+ <card id="sekkou">
+ <title>Scout</title>
+ <price>2</price>
+ </card>
+
+ <card id="jouheki">
+ <title>Castle Walls</title>
+ <price>2</price>
+ </card>
+
+ <card id="shoushuu-reijou">
+ <title>Draft Notice</title>
+ <price>3</price>
+ </card>
+
+ <card id="yakihata-nougyou">
+ <title>Shifting Cultivation</title>
+ <price>3</price>
+ </card>
+
+ <card id="kouekisen">
+ <title>Trade Vessel</title>
+ <price>3</price>
+ </card>
+
+ <card id="hajouzuchi">
+ <title>Battering Ram</title>
+ <price>3</price>
+ </card>
+
+ <card id="umoreta-zaihou">
+ <title>Buried Treasure</title>
+ <price>3</price>
+ </card>
+
+ <card id="goyou-shounin">
+ <title>Government Purveyor</title>
+ <price>3</price>
+ </card>
+
+ <card id="baishuu-kousaku">
+ <title>Bribery</title>
+ <price>3</price>
+ </card>
+
+ <card id="kakurega">
+ <title>Hideout</title>
+ <price>3</price>
+ </card>
+
+ <card id="mahou-no-gofu">
+ <title>Magical Amulet</title>
+ <price>3</price>
+ </card>
+
+ <card id="shinobi">
+ <title>Shinobi</title>
+ <price>4</price>
+ </card>
+
+ <card id="kanekashi">
+ <title>Moneylender</title>
+ <price>4</price>
+ </card>
+
+ <card id="toshokan">
+ <title>Library</title>
+ <price>4</price>
+ </card>
+
+ <card id="hoshiyomi-no-majo">
+ <title>Witch of the Star Song</title>
+ <price>4</price>
+ </card>
+
+ <card id="toshi-kaihatsu">
+ <title>Urban Development</title>
+ <price>4</price>
+ </card>
+
+ <card id="hokyuu-butai">
+ <title>Supply Unit</title>
+ <price>4</price>
+ </card>
+
+ <card id="oitaterareta-majuu">
+ <title>Exorcise the Evil Beast</title>
+ <price>4</price>
+ </card>
+
+ <card id="hohei-daitai">
+ <title>Infantry Battalion</title>
+ <price>4</price>
+ </card>
+
+ <card id="miryoujutsu-no-majo">
+ <title>Witch of the Art of Charming</title>
+ <price>4</price>
+ </card>
+
+ <card id="ginkou">
+ <title>Bank</title>
+ <price>5</price>
+ </card>
+
+ <card id="boukensha">
+ <title>Adventurer</title>
+ <price>5</price>
+ </card>
+
+ <card id="renkinjutsushi">
+ <title>Alchemist</title>
+ <price>5</price>
+ </card>
+
+ <card id="konoe-kishidan">
+ <title>Imperial Knights</title>
+ <price>5</price>
+ </card>
+
+ <card id="juso-no-majo">
+ <title>Curse Witch</title>
+ <price>5</price>
+ </card>
+
+ <card id="koushituryou">
+ <title>Imperial Estate</title>
+ <price>5</price>
+ </card>
+
+ <card id="uwasazuki-no-koushaku-fujin">
+ <title>Gossip-hungry Duchess</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="heart-of-crown"
+ revision="1" format_version="1" language="ja" country="">
+ <game_title>ハートオブクラウン</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="basic-set" game_id="heart-of-crown"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1010</preference>
+ <expansion_title>基本セット</expansion_title>
+
+ <card id="kifu">
+ <title>寄付</title>
+ <price>2</price>
+ </card>
+
+ <card id="hayauma">
+ <title>早馬</title>
+ <price>2</price>
+ </card>
+
+ <card id="negai-no-izumi">
+ <title>願いの泉</title>
+ <price>2</price>
+ </card>
+
+ <card id="sekkou">
+ <title>斥候</title>
+ <price>2</price>
+ </card>
+
+ <card id="jouheki">
+ <title>城壁</title>
+ <price>2</price>
+ </card>
+
+ <card id="shoushuu-reijou">
+ <title>召集令状</title>
+ <price>3</price>
+ </card>
+
+ <card id="yakihata-nougyou">
+ <title>焼き畑農業</title>
+ <price>3</price>
+ </card>
+
+ <card id="kouekisen">
+ <title>交易船</title>
+ <price>3</price>
+ </card>
+
+ <card id="hajouzuchi">
+ <title>破城槌</title>
+ <price>3</price>
+ </card>
+
+ <card id="umoreta-zaihou">
+ <title>埋もれた財宝</title>
+ <price>3</price>
+ </card>
+
+ <card id="goyou-shounin">
+ <title>御用商人</title>
+ <price>3</price>
+ </card>
+
+ <card id="baishuu-kousaku">
+ <title>買収工作</title>
+ <price>3</price>
+ </card>
+
+ <card id="kakurega">
+ <title>隠れ家</title>
+ <price>3</price>
+ </card>
+
+ <card id="mahou-no-gofu">
+ <title>魔法の護符</title>
+ <price>3</price>
+ </card>
+
+ <card id="shinobi">
+ <title>シノビ</title>
+ <price>4</price>
+ </card>
+
+ <card id="kanekashi">
+ <title>金貸し</title>
+ <price>4</price>
+ </card>
+
+ <card id="toshokan">
+ <title>図書館</title>
+ <price>4</price>
+ </card>
+
+ <card id="hoshiyomi-no-majo">
+ <title>星詠みの魔女</title>
+ <price>4</price>
+ </card>
+
+ <card id="toshi-kaihatsu">
+ <title>都市開発</title>
+ <price>4</price>
+ </card>
+
+ <card id="hokyuu-butai">
+ <title>補給部隊</title>
+ <price>4</price>
+ </card>
+
+ <card id="oitaterareta-majuu">
+ <title>追い立てられた魔獣</title>
+ <price>4</price>
+ </card>
+
+ <card id="hohei-daitai">
+ <title>歩兵大隊</title>
+ <price>4</price>
+ </card>
+
+ <card id="miryoujutsu-no-majo">
+ <title>魅了術の魔女</title>
+ <price>4</price>
+ </card>
+
+ <card id="ginkou">
+ <title>銀行</title>
+ <price>5</price>
+ </card>
+
+ <card id="boukensha">
+ <title>冒険者</title>
+ <price>5</price>
+ </card>
+
+ <card id="renkinjutsushi">
+ <title>錬金術師</title>
+ <price>5</price>
+ </card>
+
+ <card id="konoe-kishidan">
+ <title>近衛騎士団</title>
+ <price>5</price>
+ </card>
+
+ <card id="juso-no-majo">
+ <title>呪詛の魔女</title>
+ <price>5</price>
+ </card>
+
+ <card id="koushituryou">
+ <title>皇室領</title>
+ <price>5</price>
+ </card>
+
+ <card id="uwasazuki-no-koushaku-fujin">
+ <title>噂好きの公爵夫人</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="fairy-garden" game_id="heart-of-crown"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1040</preference>
+ <expansion_title>フェアリーガーデン</expansion_title>
+
+ <card id="junrei">
+ <title>巡礼</title>
+ <price>2</price>
+ </card>
+
+ <card id="iemori-no-seirei">
+ <title>家守の精霊</title>
+ <price>2</price>
+ </card>
+
+ <card id="denrei">
+ <title>伝令</title>
+ <price>2</price>
+ </card>
+
+ <card id="harukaze-no-yousei">
+ <title>春風の妖精</title>
+ <price>2</price>
+ </card>
+
+ <card id="mittei">
+ <title>密偵</title>
+ <price>2</price>
+ </card>
+
+ <card id="shisyo">
+ <title>司書</title>
+ <price>3</price>
+ </card>
+
+ <card id="shukufuku">
+ <title>祝福</title>
+ <price>3</price>
+ </card>
+
+ <card id="hoshimiko-no-shintaku">
+ <title>星巫女の託宣</title>
+ <price>3</price>
+ </card>
+
+ <card id="leaf-fairy">
+ <title>リーフフェアリー</title>
+ <price>3</price>
+ </card>
+
+ <card id="tabigeinin">
+ <title>旅芸人</title>
+ <price>3</price>
+ </card>
+
+ <card id="guild-master">
+ <title>ギルドマスター</title>
+ <price>3</price>
+ </card>
+
+ <card id="tsuji-uranaishi">
+ <title>辻占い師</title>
+ <price>4</price>
+ </card>
+
+ <card id="goryouchi">
+ <title>御料地</title>
+ <price>4</price>
+ </card>
+
+ <card id="dainouen">
+ <title>大農園</title>
+ <price>4</price>
+ </card>
+
+ <card id="shousendan">
+ <title>商船団</title>
+ <price>4</price>
+ </card>
+
+ <card id="gyoushounin">
+ <title>行商人</title>
+ <price>4</price>
+ </card>
+
+ <card id="brownie">
+ <title>ブラウニー</title>
+ <price>4</price>
+ </card>
+
+ <card id="nymph">
+ <title>ニンフ</title>
+ <price>4</price>
+ </card>
+
+ <card id="hyousetsu-no-seirei">
+ <title>氷雪の精霊</title>
+ <price>4</price>
+ </card>
+
+ <card id="ishiyumitai">
+ <title>石弓隊</title>
+ <price>4</price>
+ </card>
+
+ <card id="kenchi-yakunin">
+ <title>検地役人</title>
+ <price>4</price>
+ </card>
+
+ <card id="koueki-toshi">
+ <title>交易都市</title>
+ <price>5</price>
+ </card>
+
+ <card id="shitsuji">
+ <title>執事</title>
+ <price>5</price>
+ </card>
+
+ <card id="shuukakusai">
+ <title>収穫祭</title>
+ <price>5</price>
+ </card>
+
+ <card id="gappei">
+ <title>合併</title>
+ <price>5</price>
+ </card>
+
+ <card id="seidou-kishi">
+ <title>聖堂騎士</title>
+ <price>5</price>
+ </card>
+
+ <card id="onizoku-no-senshi">
+ <title>鬼族の戦士</title>
+ <price>5</price>
+ </card>
+
+ <card id="chouzeinin">
+ <title>徴税人</title>
+ <price>5</price>
+ </card>
+
+ <card id="maid-chou">
+ <title>メイド長</title>
+ <price>5</price>
+ </card>
+
+ <card id="saibankan">
+ <title>裁判官</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="hokugen-no-majo" game_id="heart-of-crown"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1030</preference>
+ <expansion_title>北限の魔女</expansion_title>
+
+ <card id="cait-sith">
+ <title>ケットシー</title>
+ <price>2</price>
+ </card>
+
+ <card id="kouun-no-ginka">
+ <title>幸運の銀貨</title>
+ <price>2</price>
+ </card>
+
+ <card id="senrei">
+ <title>洗礼</title>
+ <price>3</price>
+ </card>
+
+ <card id="meiba">
+ <title>名馬</title>
+ <price>3</price>
+ </card>
+
+ <card id="noroi-no-ningyou">
+ <title>呪いの人形</title>
+ <price>3</price>
+ </card>
+
+ <card id="dwarf-no-houseki-shokunin">
+ <title>ドワーフの宝石職人</title>
+ <price>4</price>
+ </card>
+
+ <card id="elf-no-sogekishu">
+ <title>エルフの狙撃手</title>
+ <price>4</price>
+ </card>
+
+ <card id="kyuutei-tousou">
+ <title>宮廷闘争</title>
+ <price>4</price>
+ </card>
+
+ <card id="goushou">
+ <title>豪商</title>
+ <price>5</price>
+ </card>
+
+ <card id="chihou-yakunin">
+ <title>地方役人</title>
+ <price>5</price>
+ </card>
+
+ <card id="kizoku-no-hitorimusume">
+ <title>貴族の一人娘</title>
+ <price>5</price>
+ </card>
+
+ <card id="kougyou-toshi">
+ <title>工業都市</title>
+ <price>6</price>
+ </card>
+
+ <card id="dokusen">
+ <title>独占</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="kyokutou-henkyouryou" game_id="heart-of-crown"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1020</preference>
+ <expansion_title>極東辺境領</expansion_title>
+
+ <card id="okanezuki-no-yousei">
+ <title>お金好きの妖精</title>
+ <price>2</price>
+ </card>
+
+ <card id="denshobato">
+ <title>伝書鳩</title>
+ <price>3</price>
+ </card>
+
+ <card id="kazei">
+ <title>課税</title>
+ <price>3</price>
+ </card>
+
+ <card id="boueki-shounin">
+ <title>貿易商人</title>
+ <price>3</price>
+ </card>
+
+ <card id="kyuuheitai">
+ <title>弓兵隊</title>
+ <price>3</price>
+ </card>
+
+ <card id="minatomachi">
+ <title>港町</title>
+ <price>4</price>
+ </card>
+
+ <card id="kouzan-toshi">
+ <title>鉱山都市</title>
+ <price>4</price>
+ </card>
+
+ <card id="minarai-majo">
+ <title>見習い魔女</title>
+ <price>4</price>
+ </card>
+
+ <card id="samurai">
+ <title>サムライ</title>
+ <price>4</price>
+ </card>
+
+ <card id="kunoichi">
+ <title>クノイチ</title>
+ <price>4</price>
+ </card>
+
+ <card id="ketsumei">
+ <title>結盟</title>
+ <price>5</price>
+ </card>
+
+ <card id="warifu">
+ <title>割り符</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+ <game_title>NITROPLUS CARD MASTERS</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="multi" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1030</preference>
+ <expansion_title>MULTI</expansion_title>
+
+ <card id="multi-001">
+ <title>新人監視官「常守朱」</title>
+ <price>2</price>
+ </card>
+
+ <card id="multi-002">
+ <title>深淵の向こう側「願いの叶う家」</title>
+ <price>2</price>
+ </card>
+
+ <card id="multi-003">
+ <title>抜け落ちた頁「ネクロノミコンの断片」</title>
+ <price>2</price>
+ </card>
+
+ <card id="multi-004">
+ <title>和をもって尊しとす「難民村」</title>
+ <price>3</price>
+ </card>
+
+ <card id="multi-005">
+ <title>草原の民「風のうしろを歩むもの」</title>
+ <price>3</price>
+ </card>
+
+ <card id="multi-006">
+ <title>オアシス</title>
+ <price>3</price>
+ </card>
+
+ <card id="multi-007">
+ <title>瀧川商事社長「瀧川弓」</title>
+ <price>3</price>
+ </card>
+
+ <card id="multi-008">
+ <title>イノヴェルチの吸血鬼「ヴァンパイア三銃士」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-009">
+ <title>シスター「ライカ」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-010">
+ <title>機動バトラー「ガンヴァレル」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-011">
+ <title>たぬき変化「葉っぱ」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-012">
+ <title>復讐の狂犬「ドライ」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-013">
+ <title>権謀術数「サイス=マスター」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-014">
+ <title>鳥兜総監「ピウス」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-015">
+ <title>対魔特殊部隊「鳥兜」</title>
+ <price>4</price>
+ </card>
+
+ <card id="multi-016">
+ <title>インフェルノ女幹部「クロウディア・マッキェネン」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-017">
+ <title>未来型理系姉妹「久我山若佳菜・深佳」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-018">
+ <title>ジェノサイド・クロスファイア「破壊ロボ」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-019">
+ <title>現実化する妄想嫁「星来オルジェル」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-020">
+ <title>ドミネーターの射手「公安局刑事課」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-021">
+ <title>クリスマスの精霊「プレゼント」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-022">
+ <title>ぬいぐるみストラップ「ゲロカエルん」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-023">
+ <title>“武帝”渉外役「オーリガ」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-024">
+ <title>鬼眼麗人「劉豪軍」</title>
+ <price>5</price>
+ </card>
+
+ <card id="multi-025">
+ <title>紫電掌「孔濤羅」</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="nitroplus" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1010</preference>
+ <expansion_title>NITROPLUS</expansion_title>
+
+ <card id="nitroplus-001">
+ <title>人類の守護者「イグニス」</title>
+ <price>2</price>
+ </card>
+
+ <card id="nitroplus-002">
+ <title>塵は塵に灰は灰に「モーラ」</title>
+ <price>2</price>
+ </card>
+
+ <card id="nitroplus-003">
+ <title>とある竜の恋の歌「ドラゴン」</title>
+ <price>2</price>
+ </card>
+
+ <card id="nitroplus-004">
+ <title>マスコットガール「すーぱーそに子」</title>
+ <price>3</price>
+ </card>
+
+ <card id="nitroplus-005">
+ <title>SMG「スピカ・ミラ・ガーネット」</title>
+ <price>3</price>
+ </card>
+
+ <card id="nitroplus-006">
+ <title>天下布武「湊斗光」</title>
+ <price>3</price>
+ </card>
+
+ <card id="nitroplus-007">
+ <title>未練を穿つ天使「アンリ」</title>
+ <price>3</price>
+ </card>
+
+ <card id="nitroplus-008">
+ <title>ファントム「アイン」</title>
+ <price>3</price>
+ </card>
+
+ <card id="nitroplus-009">
+ <title>賞金首「黒のフランコ・名前のない女」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-010">
+ <title>ギガロマニアックス「咲畑梨深」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-011">
+ <title>XAT「アマンダ」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-012">
+ <title>UN.K.O「うんこマン(仮)」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-013">
+ <title>螺旋禍る世界「アナザーブラッド」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-014">
+ <title>世界を侵す恋「沙耶」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-015">
+ <title>コ・ジェネレーション「電脳都市秋葉原」</title>
+ <price>4</price>
+ </card>
+
+ <card id="nitroplus-016">
+ <title>カゴメアソビ「沙紅羅」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-017">
+ <title>始まりに至る「第四次聖杯戦争」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-018">
+ <title>魂魄転写「ルイリー」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-019">
+ <title>秘儀「アンナ」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-020">
+ <title>王の覚醒「六本木フォート」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-021">
+ <title>未来ガジェット発明「ラボメン」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-022">
+ <title>銅銭積み「湊斗景明」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-023">
+ <title>護国の不死「石馬戒厳」</title>
+ <price>5</price>
+ </card>
+
+ <card id="nitroplus-024">
+ <title>ハーレム</title>
+ <price>6</price>
+ </card>
+
+ <card id="nitroplus-025">
+ <title>魔を断つ剣「デモンベイン」</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="plus" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1020</preference>
+ <expansion_title>+ (PLUS)</expansion_title>
+
+ <card id="plus-001">
+ <title>サイバーエージェント「麻生純子」</title>
+ <price>2</price>
+ </card>
+
+ <card id="plus-002">
+ <title>魔法少女の契約「キュゥべえ」</title>
+ <price>2</price>
+ </card>
+
+ <card id="plus-003">
+ <title>オペラ座の怪人「ツヴァイ」</title>
+ <price>3</price>
+ </card>
+
+ <card id="plus-004">
+ <title>ネコミミメイド喫茶「伊都夏大学園祭」</title>
+ <price>3</price>
+ </card>
+
+ <card id="plus-005">
+ <title>第一宇宙速度リーダー「富士見鈴」</title>
+ <price>4</price>
+ </card>
+
+ <card id="plus-006">
+ <title>天才少女「牧瀬紅莉栖</title>
+ <price>4</price>
+ </card>
+
+ <card id="plus-007">
+ <title>禿鷹「リリィ・サルバターナ」</title>
+ <price>4</price>
+ </card>
+
+ <card id="plus-008">
+ <title>妄想引きこもり「西條拓巳」</title>
+ <price>4</price>
+ </card>
+
+ <card id="plus-009">
+ <title>幽宮「メゾン・フォレドー」</title>
+ <price>4</price>
+ </card>
+
+ <card id="plus-010">
+ <title>堀越公方「足利茶々丸」</title>
+ <price>4</price>
+ </card>
+
+ <card id="plus-011">
+ <title>人形曲馬団チルチェンセス「ルナリア」</title>
+ <price>5</price>
+ </card>
+
+ <card id="plus-012">
+ <title>きまぐれ天使「キャロル」</title>
+ <price>5</price>
+ </card>
+
+ <card id="plus-013">
+ <title>背徳の獣「マスターテリオン」</title>
+ <price>5</price>
+ </card>
+
+ <card id="plus-014">
+ <title>魔剣・鍔目返し「武田赤音」</title>
+ <price>5</price>
+ </card>
+
+ <card id="plus-015">
+ <title>交易場「コミマ」</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-001" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2010</preference>
+ <expansion_title>瀧澤琴乃 (プロモ)</expansion_title>
+
+ <card id="promo-001">
+ <title>劒冑研師「瀧澤琴乃」</title>
+ <price>3</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-002" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2020</preference>
+ <expansion_title>小竜景光 (プロモ)</expansion_title>
+
+ <card id="promo-002">
+ <title>倶利伽羅「楠木六代目・小竜景光」</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-003" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2030</preference>
+ <expansion_title>スクルージ (プロモ)</expansion_title>
+
+ <card id="promo-003">
+ <title>強化ゲノム実験体「スクルージ」</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-004" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2040</preference>
+ <expansion_title>アルフィー (プロモ)</expansion_title>
+
+ <card id="promo-004">
+ <title>よろずや「アルフィー」</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-005" game_id="nitroplus"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2050</preference>
+ <expansion_title>槙島聖護 (プロモ)</expansion_title>
+
+ <card id="promo-005">
+ <title>犯罪プロデューサー「槙島聖護」</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="tanto-cuore"
+ revision="1" format_version="1" language="en" country="">
+ <game_title>Tanto Cuore</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="expanding-the-house" game_id="tanto-cuore"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1020</preference>
+ <expansion_title>Expanding the house</expansion_title>
+
+ <card id="tiffany-wise">
+ <title>Tiffany Wise</title>
+ <price>7</price>
+ </card>
+
+ <card id="carillon-vandoor">
+ <title>Carillon Vandoor</title>
+ <price>5</price>
+ </card>
+
+ <card id="francine-barbier">
+ <title>Francine Barbier</title>
+ <price>5</price>
+ </card>
+
+ <card id="renee-r-rieussec">
+ <title>Renée R Rieussec</title>
+ <price>5</price>
+ </card>
+
+ <card id="domino-bonaparte">
+ <title>Domino Bonaparte</title>
+ <price>4</price>
+ </card>
+
+ <card id="amaretto-renard">
+ <title>Amaretto Renard</title>
+ <price>4</price>
+ </card>
+
+ <card id="victoria-calderan">
+ <title>Victoria Calderan</title>
+ <price>4</price>
+ </card>
+
+ <card id="emily-raymond">
+ <title>Emily Raymond</title>
+ <price>4</price>
+ </card>
+
+ <card id="rutile-der-sar">
+ <title>Rutile der Sar</title>
+ <price>3</price>
+ </card>
+
+ <card id="phyllis-lumley">
+ <title>Phyllis Lumley</title>
+ <price>3</price>
+ </card>
+
+ <card id="lilac-hawkwind">
+ <title>Lilac Hawkwind</title>
+ <price>3</price>
+ </card>
+
+ <card id="felicity-horn">
+ <title>Felicity Horn</title>
+ <price>3</price>
+ </card>
+
+ <card id="suzuna-kamikawa">
+ <title>Suzuna Kamikawa</title>
+ <price>3</price>
+ </card>
+
+ <card id="grace-saulsbury">
+ <title>Grace Saulsbury</title>
+ <price>3</price>
+ </card>
+
+ <card id="pauline-dumond">
+ <title>Pauline Dumond</title>
+ <price>2</price>
+ </card>
+
+ <card id="ririko-hiiragi">
+ <title>Ririko Hiiragi</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="romantic-vacation" game_id="tanto-cuore"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1030</preference>
+ <expansion_title>Romantic Vacation</expansion_title>
+
+ <card id="frida-viento">
+ <title>Frida Viento</title>
+ <price>7</price>
+ </card>
+
+ <card id="laura">
+ <title>Laura</title>
+ <price>6</price>
+ </card>
+
+ <card id="clorinde-sea">
+ <title>Clorinde Sea</title>
+ <price>6</price>
+ </card>
+
+ <card id="lydia-leon">
+ <title>Lydia Leon</title>
+ <price>5</price>
+ </card>
+
+ <card id="florence-spring">
+ <title>Florence Spring</title>
+ <price>5</price>
+ </card>
+
+ <card id="cynthia-lakes">
+ <title>Cynthia Lakes</title>
+ <price>5</price>
+ </card>
+
+ <card id="caldina-alley">
+ <title>Caldina Alley</title>
+ <price>4</price>
+ </card>
+
+ <card id="riya-naragasi">
+ <title>Riya Naragasi</title>
+ <price>4</price>
+ </card>
+
+ <card id="chinatsu-kooriyama">
+ <title>Chinatsu Kooriyama</title>
+ <price>4</price>
+ </card>
+
+ <card id="fea-primrose">
+ <title>Fea Primrose</title>
+ <price>4</price>
+ </card>
+
+ <card id="romina-vautrin">
+ <title>Romina Vautrin</title>
+ <price>4</price>
+ </card>
+
+ <card id="daphne-coraille">
+ <title>Daphne Coraille</title>
+ <price>3</price>
+ </card>
+
+ <card id="margareta-torrente">
+ <title>Margareta Torrente</title>
+ <price>3</price>
+ </card>
+
+ <card id="germaine-mahle">
+ <title>Germaine Mahle</title>
+ <price>3</price>
+ </card>
+
+ <card id="evita-catala">
+ <title>Evita Catala</title>
+ <price>3</price>
+ </card>
+
+ <card id="valencia-pretre">
+ <title>Valencia Prêtre</title>
+ <price>3</price>
+ </card>
+
+ <card id="hyacinth-arrow">
+ <title>Hyacinth Arrow</title>
+ <price>2</price>
+ </card>
+
+ <card id="nonnette-large">
+ <title>Nonnette Large</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="tanto-cuore" game_id="tanto-cuore"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1010</preference>
+ <expansion_title>Tanto Cuore</expansion_title>
+
+ <card id="anise-greenaway">
+ <title>Anise Greenaway</title>
+ <price>7</price>
+ </card>
+
+ <card id="opheliagrail">
+ <title>Ophelia Grail</title>
+ <price>6</price>
+ </card>
+
+ <card id="sainsbury-lockwood">
+ <title>Sainsbury Lockwood</title>
+ <price>5</price>
+ </card>
+
+ <card id="tenalys-trent">
+ <title>Tenalys Trent </title>
+ <price>5</price>
+ </card>
+
+ <card id="natsumi-fujikawa">
+ <title>Natsumi Fujikawa</title>
+ <price>5</price>
+ </card>
+
+ <card id="nena-wilder">
+ <title>Nena Wilder</title>
+ <price>5</price>
+ </card>
+
+ <card id="esquine-foret">
+ <title>Esquine Forét</title>
+ <price>4</price>
+ </card>
+
+ <card id="genevieve-daubigny">
+ <title>Genevieve Daubigny</title>
+ <price>4</price>
+ </card>
+
+ <card id="moine-de-lefevre">
+ <title>Moine de Lefévre</title>
+ <price>4</price>
+ </card>
+
+ <card id="eliza-rosewater">
+ <title>Eliza Rosewater</title>
+ <price>3</price>
+ </card>
+
+ <card id="kagari-ichinomiya">
+ <title>Kagari Ichinomiya</title>
+ <price>3</price>
+ </card>
+
+ <card id="claire-saint-juste">
+ <title>Claire Saint-Juste</title>
+ <price>3</price>
+ </card>
+
+ <card id="safran-virginie">
+ <title>Safran Virginie</title>
+ <price>3</price>
+ </card>
+
+ <card id="azure-crescent">
+ <title>Azure Crescent</title>
+ <price>2</price>
+ </card>
+
+ <card id="viola-crescent">
+ <title>Viola Crescent</title>
+ <price>2</price>
+ </card>
+
+ <card id="rouge-crescent">
+ <title>Rouge Crescent</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="tanto-cuore"
+ revision="1" format_version="1" language="ja" country="">
+ <game_title>たんとくおーれ</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="expanding-the-house" game_id="tanto-cuore"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1020</preference>
+ <expansion_title>増築開始♪</expansion_title>
+
+ <card id="tiffany-wise">
+ <title>ティファニー・ワイズ</title>
+ <price>7</price>
+ </card>
+
+ <card id="carillon-vandoor">
+ <title>カリヨン・ファンドール</title>
+ <price>5</price>
+ </card>
+
+ <card id="francine-barbier">
+ <title>フランシーン・バルビエ</title>
+ <price>5</price>
+ </card>
+
+ <card id="renee-r-rieussec">
+ <title>レネ・R・リューセック</title>
+ <price>5</price>
+ </card>
+
+ <card id="domino-bonaparte">
+ <title>ドミノ・ボナパルト</title>
+ <price>4</price>
+ </card>
+
+ <card id="amaretto-renard">
+ <title>アマレット・ルナール</title>
+ <price>4</price>
+ </card>
+
+ <card id="victoria-calderan">
+ <title>ヴィクトリア・カルデラン</title>
+ <price>4</price>
+ </card>
+
+ <card id="emily-raymond">
+ <title>エミリー・レイモンド</title>
+ <price>4</price>
+ </card>
+
+ <card id="rutile-der-sar">
+ <title>ルチル・デル=サール</title>
+ <price>3</price>
+ </card>
+
+ <card id="phyllis-lumley">
+ <title>フィリス・ラムレイ</title>
+ <price>3</price>
+ </card>
+
+ <card id="lilac-hawkwind">
+ <title>ライラック・ホークウインド</title>
+ <price>3</price>
+ </card>
+
+ <card id="felicity-horn">
+ <title>フェリシティー・ホーン</title>
+ <price>3</price>
+ </card>
+
+ <card id="suzuna-kamikawa">
+ <title>スズナ・カミカワ</title>
+ <price>3</price>
+ </card>
+
+ <card id="grace-saulsbury">
+ <title>グレイス・ソールズベリ</title>
+ <price>3</price>
+ </card>
+
+ <card id="pauline-dumond">
+ <title>ポーリーヌ・デュモン</title>
+ <price>2</price>
+ </card>
+
+ <card id="ririko-hiiragi">
+ <title>リリコ・ヒイラギ</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="romantic-vacation" game_id="tanto-cuore"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1030</preference>
+ <expansion_title>ドキドキ バケーション</expansion_title>
+
+ <card id="frida-viento">
+ <title>フリーダ・ビエント</title>
+ <price>7</price>
+ </card>
+
+ <card id="laura">
+ <title>ラウラ</title>
+ <price>6</price>
+ </card>
+
+ <card id="clorinde-sea">
+ <title>クロリンド・シー</title>
+ <price>6</price>
+ </card>
+
+ <card id="lydia-leon">
+ <title>リディア・レオン</title>
+ <price>5</price>
+ </card>
+
+ <card id="florence-spring">
+ <title>フローレンス・スプリング</title>
+ <price>5</price>
+ </card>
+
+ <card id="cynthia-lakes">
+ <title>シンシア・レイクス</title>
+ <price>5</price>
+ </card>
+
+ <card id="caldina-alley">
+ <title>カルディナ・アレイ</title>
+ <price>4</price>
+ </card>
+
+ <card id="riya-naragasi">
+ <title>リーヤ・ナラガシ</title>
+ <price>4</price>
+ </card>
+
+ <card id="chinatsu-kooriyama">
+ <title>チナツ・コオリヤマ</title>
+ <price>4</price>
+ </card>
+
+ <card id="fea-primrose">
+ <title>フィー・プリムローズ</title>
+ <price>4</price>
+ </card>
+
+ <card id="romina-vautrin">
+ <title>ロミナ・ヴォートラン</title>
+ <price>4</price>
+ </card>
+
+ <card id="daphne-coraille">
+ <title>ダフネ・コライユ</title>
+ <price>3</price>
+ </card>
+
+ <card id="margareta-torrente">
+ <title>マルガレータ・トルレンテ</title>
+ <price>3</price>
+ </card>
+
+ <card id="germaine-mahle">
+ <title>ジェルメーヌ・マーレ</title>
+ <price>3</price>
+ </card>
+
+ <card id="evita-catala">
+ <title>エヴィータ・カタラ</title>
+ <price>3</price>
+ </card>
+
+ <card id="valencia-pretre">
+ <title>バレンシア・プレートル</title>
+ <price>3</price>
+ </card>
+
+ <card id="hyacinth-arrow">
+ <title>ヒヤシンス・アロー</title>
+ <price>2</price>
+ </card>
+
+ <card id="nonnette-large">
+ <title>ノネット・ラルジュ</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="tanto-cuore" game_id="tanto-cuore"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1010</preference>
+ <expansion_title>たんとくおーれ</expansion_title>
+
+ <card id="anise-greenaway">
+ <title>アニス・グリーナウェイ</title>
+ <price>7</price>
+ </card>
+
+ <card id="opheliagrail">
+ <title>オフィリア・グレイル</title>
+ <price>6</price>
+ </card>
+
+ <card id="sainsbury-lockwood">
+ <title>セインズベリー・ロックウッド</title>
+ <price>5</price>
+ </card>
+
+ <card id="tenalys-trent">
+ <title>ティナリス・トレント</title>
+ <price>5</price>
+ </card>
+
+ <card id="natsumi-fujikawa">
+ <title>ナツミ・フジカワ</title>
+ <price>5</price>
+ </card>
+
+ <card id="nena-wilder">
+ <title>ネーナ・ワイルダー</title>
+ <price>5</price>
+ </card>
+
+ <card id="esquine-foret">
+ <title>エスキーヌ・フォレ</title>
+ <price>4</price>
+ </card>
+
+ <card id="genevieve-daubigny">
+ <title>ジュヌヴィエーヴ・ドービニー</title>
+ <price>4</price>
+ </card>
+
+ <card id="moine-de-lefevre">
+ <title>モワンヌ・ド・ルフェーブル</title>
+ <price>4</price>
+ </card>
+
+ <card id="eliza-rosewater">
+ <title>イライザ・ローズウォーター</title>
+ <price>3</price>
+ </card>
+
+ <card id="kagari-ichinomiya">
+ <title>カガリ・イチノミヤ</title>
+ <price>3</price>
+ </card>
+
+ <card id="claire-saint-juste">
+ <title>クレール・サン=ジュスト</title>
+ <price>3</price>
+ </card>
+
+ <card id="safran-virginie">
+ <title>サフラン・ヴィルジニー</title>
+ <price>3</price>
+ </card>
+
+ <card id="azure-crescent">
+ <title>アスール・クレセント</title>
+ <price>2</price>
+ </card>
+
+ <card id="viola-crescent">
+ <title>ヴィオーラ・クレセント</title>
+ <price>2</price>
+ </card>
+
+ <card id="rouge-crescent">
+ <title>ルージュ・クレセント</title>
+ <price>2</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+ <game_title>東方祀爭録</game_title>
+ <selection_size>10</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="eiyashou" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1040</preference>
+ <expansion_title>東方永夜抄編</expansion_title>
+
+ <card id="eiyashou-001">
+ <title>闇に蠢く光の蟲「リグル・ナイトバグ」</title>
+ <price>2</price>
+ </card>
+
+ <card id="eiyashou-002">
+ <title>地上の兎「因幡てゐ」</title>
+ <price>2</price>
+ </card>
+
+ <card id="eiyashou-003">
+ <title>夜雀の怪「ミスティア・ローレライ」</title>
+ <price>3</price>
+ </card>
+
+ <card id="eiyashou-004">
+ <title>知識と歴史の半獣「上白沢慧音」</title>
+ <price>3</price>
+ </card>
+
+ <card id="eiyashou-005">
+ <title>国符「三種の神器」</title>
+ <price>3</price>
+ </card>
+
+ <card id="eiyashou-006">
+ <title>狂気の月の兎「鈴仙・優曇華院・イナバ」</title>
+ <price>3</price>
+ </card>
+
+ <card id="eiyashou-007">
+ <title>散符「真実の月」</title>
+ <price>3</price>
+ </card>
+
+ <card id="eiyashou-008">
+ <title>夢幻の紅魔チーム</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-009">
+ <title>幽冥の住人チーム</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-010">
+ <title>蟲符「リトルバグストーム」</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-011">
+ <title>夜雀「真夜中のコーラスマスター」</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-012">
+ <title>五つの難題</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-013">
+ <title>永夜返し</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-014">
+ <title>蓬莱の人の形「藤原妹紅」</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-015">
+ <title>魅惑の新酒肴「焼き八目鰻」</title>
+ <price>4</price>
+ </card>
+
+ <card id="eiyashou-016">
+ <title>禁呪の詠唱チーム</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-017">
+ <title>月の頭脳「八意永琳」</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-018">
+ <title>天呪「アポロ13」</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-019">
+ <title>永遠と須臾の罪人「蓬莱山輝夜」</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-020">
+ <title>歴史喰い「上白沢慧音」</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-021">
+ <title>新史「新幻想史 -ネクストヒストリー-」</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-022">
+ <title>「パゼストバイフェニックス」</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-023">
+ <title>蓬莱の薬</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-024">
+ <title>人間の里</title>
+ <price>5</price>
+ </card>
+
+ <card id="eiyashou-025">
+ <title>幻想の結界チーム</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="extra" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1030</preference>
+ <expansion_title>特別拡張編(EXTRA)</expansion_title>
+
+ <card id="extra-001">
+ <title>小さなスイートポイズン「メディスン・メランコリー」</title>
+ <price>2</price>
+ </card>
+
+ <card id="extra-002">
+ <title>輝ける日の光「サニーミルク」</title>
+ <price>3</price>
+ </card>
+
+ <card id="extra-003">
+ <title>静かなる月の光「ルナチャイルド」</title>
+ <price>3</price>
+ </card>
+
+ <card id="extra-004">
+ <title>降り注ぐ星の光「スターサファイア」</title>
+ <price>3</price>
+ </card>
+
+ <card id="extra-005">
+ <title>無縁塚</title>
+ <price>3</price>
+ </card>
+
+ <card id="extra-006">
+ <title>三途の水先案内人「小野塚小町」</title>
+ <price>4</price>
+ </card>
+
+ <card id="extra-007">
+ <title>美しき緋の衣「永江衣玖」</title>
+ <price>4</price>
+ </card>
+
+ <card id="extra-008">
+ <title>非想非非想天の娘「比那名居天子」</title>
+ <price>4</price>
+ </card>
+
+ <card id="extra-009">
+ <title>片腕有角の仙人「茨木華仙」</title>
+ <price>4</price>
+ </card>
+
+ <card id="extra-010">
+ <title>有頂天</title>
+ <price>4</price>
+ </card>
+
+ <card id="extra-011">
+ <title>萃まる夢、幻、そして百鬼夜行「伊吹萃香」</title>
+ <price>5</price>
+ </card>
+
+ <card id="extra-012">
+ <title>伝統の幻想ブン屋「射命丸文」</title>
+ <price>5</price>
+ </card>
+
+ <card id="extra-013">
+ <title>四季のフラワーマスター「風見幽香」</title>
+ <price>5</price>
+ </card>
+
+ <card id="extra-014">
+ <title>楽園の最高裁判長「四季映姫・ヤマザナドゥ」</title>
+ <price>5</price>
+ </card>
+
+ <card id="extra-015">
+ <title>今どきの念写記者「姫海棠はたて」</title>
+ <price>5</price>
+ </card>
+
+ <card id="extra-016">
+ <title>酒虫の壺</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="fuujinroku" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1050</preference>
+ <expansion_title>東方風神録編</expansion_title>
+
+ <card id="fuujinroku-001">
+ <title>秘神流し雛「鍵山雛」</title>
+ <price>2</price>
+ </card>
+
+ <card id="fuujinroku-002">
+ <title>寂しさと終焉の象徴「秋静葉」</title>
+ <price>3</price>
+ </card>
+
+ <card id="fuujinroku-003">
+ <title>守矢神社</title>
+ <price>3</price>
+ </card>
+
+ <card id="fuujinroku-004">
+ <title>創符「ペインフロー」</title>
+ <price>4</price>
+ </card>
+
+ <card id="fuujinroku-005">
+ <title>河童「のびーるアーム」</title>
+ <price>4</price>
+ </card>
+
+ <card id="fuujinroku-006">
+ <title>下っ端哨戒天狗「犬走椛」</title>
+ <price>4</price>
+ </card>
+
+ <card id="fuujinroku-007">
+ <title>里に最も近い天狗「射命丸文」</title>
+ <price>4</price>
+ </card>
+
+ <card id="fuujinroku-008">
+ <title>祀られる風の人間「東風谷早苗」</title>
+ <price>4</price>
+ </card>
+
+ <card id="fuujinroku-009">
+ <title>豊かさと稔りの象徴「秋穣子」</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-010">
+ <title>豊符「オヲトシハーベスター」</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-011">
+ <title>超妖怪弾頭「河城にとり」</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-012">
+ <title>神祭「エクスパンデッド・オンバシラ」</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-013">
+ <title>土着神の頂点「洩矢諏訪子」</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-014">
+ <title>祟符「ミシャグジさま」</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-015">
+ <title>ブロークンアミュレット</title>
+ <price>5</price>
+ </card>
+
+ <card id="fuujinroku-016">
+ <title>山坂と湖の権化「八坂神奈子」</title>
+ <price>6</price>
+ </card>
+
+ <card id="fuujinroku-017">
+ <title>風神の湖</title>
+ <price>6</price>
+ </card>
+
+ <card id="fuujinroku-018">
+ <title>「幻想風靡」</title>
+ <price>7</price>
+ </card>
+
+ <card id="fuujinroku-019">
+ <title>秘術「グレイソーマタージ」</title>
+ <price>7</price>
+ </card>
+
+ <card id="fuujinroku-020">
+ <title>筒粥神事</title>
+ <price>7</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="koumakyou" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1010</preference>
+ <expansion_title>東方紅魔郷編</expansion_title>
+
+ <card id="koumakyou-001">
+ <title>宵闇の妖怪「ルーミア」</title>
+ <price>2</price>
+ </card>
+
+ <card id="koumakyou-002">
+ <title>闇符「ディマーケイション」</title>
+ <price>2</price>
+ </card>
+
+ <card id="koumakyou-003">
+ <title>凍符「パーフェクトフリーズ」</title>
+ <price>2</price>
+ </card>
+
+ <card id="koumakyou-004">
+ <title>魔法の森</title>
+ <price>3</price>
+ </card>
+
+ <card id="koumakyou-005">
+ <title>大妖精</title>
+ <price>3</price>
+ </card>
+
+ <card id="koumakyou-006">
+ <title>湖上の氷精「チルノ」</title>
+ <price>3</price>
+ </card>
+
+ <card id="koumakyou-007">
+ <title>華人小娘「紅美鈴」</title>
+ <price>3</price>
+ </card>
+
+ <card id="koumakyou-008">
+ <title>奇術「ミスディレクション」</title>
+ <price>3</price>
+ </card>
+
+ <card id="koumakyou-009">
+ <title>霊符「夢想封印」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-010">
+ <title>夢符「封魔陣」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-011">
+ <title>奇妙な魔法使い「霧雨魔理沙」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-012">
+ <title>ミニ八卦炉</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-013">
+ <title>恋符「マスタースパーク」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-014">
+ <title>彩符「彩光乱舞」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-015">
+ <title>知識と日陰の少女「パチュリー・ノーレッジ」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-016">
+ <title>火水木金土符「賢者の石」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-017">
+ <title>悪魔の妹「フランドール・スカーレット」</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-018">
+ <title>紅魔館地下室</title>
+ <price>4</price>
+ </card>
+
+ <card id="koumakyou-019">
+ <title>博麗神社の巫女さん「博麗霊夢」</title>
+ <price>5</price>
+ </card>
+
+ <card id="koumakyou-020">
+ <title>信仰を量る賽銭箱</title>
+ <price>5</price>
+ </card>
+
+ <card id="koumakyou-021">
+ <title>小悪魔</title>
+ <price>5</price>
+ </card>
+
+ <card id="koumakyou-022">
+ <title>紅魔館のメイド「十六夜咲夜」</title>
+ <price>5</price>
+ </card>
+
+ <card id="koumakyou-023">
+ <title>永遠に紅い幼き月「レミリア・スカーレット」</title>
+ <price>5</price>
+ </card>
+
+ <card id="koumakyou-024">
+ <title>神槍「スピア・ザ・グングニル」</title>
+ <price>5</price>
+ </card>
+
+ <card id="koumakyou-025">
+ <title>禁忌「レーヴァテイン」</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-001" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2010</preference>
+ <expansion_title>稗田阿求 (プロモ)</expansion_title>
+
+ <card id="promo-001">
+ <title>九代目阿礼乙女「稗田阿求」</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-002" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2020</preference>
+ <expansion_title>八雲紫 (プロモ)</expansion_title>
+
+ <card id="promo-002">
+ <title>割と困ったちゃん「八雲紫」</title>
+ <price>3</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-003" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2030</preference>
+ <expansion_title>森近霖之助 (プロモ)</expansion_title>
+
+ <card id="promo-003">
+ <title>香霖堂店主「森近霖之助」</title>
+ <price>8*</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-004" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2040</preference>
+ <expansion_title>宇佐見蓮子 (プロモ)</expansion_title>
+
+ <card id="promo-004">
+ <title>秘封倶楽部「宇佐見蓮子」</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="promo-005" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>2050</preference>
+ <expansion_title>マエリベリー・ハーン (プロモ)</expansion_title>
+
+ <card id="promo-005">
+ <title>秘封倶楽部「マエリベリー・ハーン」</title>
+ <price>4</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="youyoumu" game_id="touhou-shisouroku"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1020</preference>
+ <expansion_title>東方妖々夢編</expansion_title>
+
+ <card id="youyoumu-001">
+ <title>冬の忘れ物「レティ・ホワイトロック」</title>
+ <price>2</price>
+ </card>
+
+ <card id="youyoumu-002">
+ <title>式符「飛翔晴明」</title>
+ <price>2</price>
+ </card>
+
+ <card id="youyoumu-003">
+ <title>騒霊キーボーディスト「リリカ・プリズムリバー」</title>
+ <price>2</price>
+ </card>
+
+ <card id="youyoumu-004">
+ <title>罔両「八雲紫の神隠し」</title>
+ <price>2</price>
+ </card>
+
+ <card id="youyoumu-005">
+ <title>氷の妖怪「チルノ」</title>
+ <price>3</price>
+ </card>
+
+ <card id="youyoumu-006">
+ <title>七色の人形使い「アリス・マーガトロイド」</title>
+ <price>3</price>
+ </card>
+
+ <card id="youyoumu-007">
+ <title>騒霊トランペッター「メルラン・プリズムリバー」</title>
+ <price>3</price>
+ </card>
+
+ <card id="youyoumu-008">
+ <title>半分幻の庭師「魂魄妖夢」</title>
+ <price>3</price>
+ </card>
+
+ <card id="youyoumu-009">
+ <title>式輝「プリンセス天狐 -Illusion-」</title>
+ <price>3</price>
+ </card>
+
+ <card id="youyoumu-010">
+ <title>寒符「リンガリングコールド」</title>
+ <price>4</price>
+ </card>
+
+ <card id="youyoumu-011">
+ <title>春を運ぶ妖精「リリーホワイト」</title>
+ <price>4</price>
+ </card>
+
+ <card id="youyoumu-012">
+ <title>騒霊ヴァイオリニスト「ルナサ・プリズムリバー」</title>
+ <price>4</price>
+ </card>
+
+ <card id="youyoumu-013">
+ <title>獄界剣「二百由旬の一閃」</title>
+ <price>4</price>
+ </card>
+
+ <card id="youyoumu-014">
+ <title>桜符「完全なる墨染の桜」</title>
+ <price>4</price>
+ </card>
+
+ <card id="youyoumu-015">
+ <title>幽明結界</title>
+ <price>4</price>
+ </card>
+
+ <card id="youyoumu-016">
+ <title>楽園の素敵な巫女「博麗霊夢」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-017">
+ <title>普通の黒魔術少女「霧雨魔理沙」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-018">
+ <title>完全で瀟洒な従者「十六夜咲夜」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-019">
+ <title>すきま妖怪の式の式「橙」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-020">
+ <title>咒詛「魔彩光の上海人形」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-021">
+ <title>大合葬「霊車コンチェルトグロッソ」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-022">
+ <title>幽冥楼閣の亡霊少女「西行寺幽々子」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-023">
+ <title>すきま妖怪の式「八雲藍」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-024">
+ <title>神隠しの主犯「八雲紫」</title>
+ <price>5</price>
+ </card>
+
+ <card id="youyoumu-025">
+ <title>花見は神社で</title>
+ <price>6</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="trains"
+ revision="1" format_version="1" language="en" country="">
+ <game_title>TRAINS</game_title>
+ <selection_size>8</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="trains" game_id="trains"
+ revision="1" format_version="1" language="en" country="">
+
+ <preference>1010</preference>
+ <expansion_title>TRAINS</expansion_title>
+
+ <card id="steel-bridge">
+ <title>Steel bridge</title>
+ <price>4</price>
+ </card>
+
+ <card id="tunnel">
+ <title>Tunnel</title>
+ <price>5</price>
+ </card>
+
+ <card id="viaduct">
+ <title>Viaduct</title>
+ <price>5</price>
+ </card>
+
+ <card id="collaboration">
+ <title>Collaboration</title>
+ <price>5</price>
+ </card>
+
+ <card id="underground-digging">
+ <title>Underground digging</title>
+ <price>7</price>
+ </card>
+
+ <card id="land-fill">
+ <title>Land fill</title>
+ <price>2</price>
+ </card>
+
+ <card id="passing-station">
+ <title>Passing station</title>
+ <price>2</price>
+ </card>
+
+ <card id="garage">
+ <title>Garage</title>
+ <price>3</price>
+ </card>
+
+ <card id="material-dump-site">
+ <title>Material dump site</title>
+ <price>5</price>
+ </card>
+
+ <card id="signal-spot">
+ <title>Signal spot</title>
+ <price>5</price>
+ </card>
+
+ <card id="control-room">
+ <title>Control room</title>
+ <price>7</price>
+ </card>
+
+ <card id="pulling">
+ <title>Pulling</title>
+ <price>3</price>
+ </card>
+
+ <card id="first-train">
+ <title>First train</title>
+ <price>5</price>
+ </card>
+
+ <card id="wagon-factory">
+ <title>Wagon factory</title>
+ <price>5</price>
+ </card>
+
+ <card id="ironworks">
+ <title>Ironworks</title>
+ <price>4</price>
+ </card>
+
+ <card id="freight-train">
+ <title>Freight train</title>
+ <price>4</price>
+ </card>
+
+ <card id="conductor-area">
+ <title>Conductor area</title>
+ <price>2</price>
+ </card>
+
+ <card id="rapid-train">
+ <title>Rapid train</title>
+ <price>2</price>
+ </card>
+
+ <card id="switchback">
+ <title>Switchback</title>
+ <price>3</price>
+ </card>
+
+ <card id="information-central">
+ <title>Information central</title>
+ <price>4</price>
+ </card>
+
+ <card id="amusement-park">
+ <title>Amusement park</title>
+ <price>4</price>
+ </card>
+
+ <card id="tourist-train">
+ <title>Tourist train</title>
+ <price>4</price>
+ </card>
+
+ <card id="mail-train">
+ <title>Mail train</title>
+ <price>4</price>
+ </card>
+
+ <card id="holiday-timetable">
+ <title>Holiday timetable</title>
+ <price>3</price>
+ </card>
+
+ <card id="command-central">
+ <title>Command central</title>
+ <price>3</price>
+ </card>
+
+ <card id="stationmaster-office">
+ <title>Stationmaster office</title>
+ <price>4</price>
+ </card>
+
+ <card id="maintenance-factory">
+ <title>Maintenance factory</title>
+ <price>5</price>
+ </card>
+
+ <card id="signals">
+ <title>Signals</title>
+ <price>2</price>
+ </card>
+
+ <card id="station-crew">
+ <title>Station crew</title>
+ <price>2</price>
+ </card>
+
+ <card id="temporary-timetable">
+ <title>Temporary timetable</title>
+ <price>5</price>
+ </card>
+</expansion>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<game id="trains"
+ revision="1" format_version="1" language="ja" country="">
+ <game_title>TRAINS</game_title>
+ <selection_size>8</selection_size>
+</game>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<expansion id="trains" game_id="trains"
+ revision="1" format_version="1" language="ja" country="">
+
+ <preference>1010</preference>
+ <expansion_title>TRAINS</expansion_title>
+
+ <card id="steel-bridge">
+ <title>鉄橋</title>
+ <price>4</price>
+ </card>
+
+ <card id="tunnel">
+ <title>トンネル</title>
+ <price>5</price>
+ </card>
+
+ <card id="viaduct">
+ <title>高架化</title>
+ <price>5</price>
+ </card>
+
+ <card id="collaboration">
+ <title>相互乗入</title>
+ <price>5</price>
+ </card>
+
+ <card id="underground-digging">
+ <title>地下化</title>
+ <price>7</price>
+ </card>
+
+ <card id="land-fill">
+ <title>埋め立て</title>
+ <price>2</price>
+ </card>
+
+ <card id="passing-station">
+ <title>退避駅</title>
+ <price>2</price>
+ </card>
+
+ <card id="garage">
+ <title>車庫</title>
+ <price>3</price>
+ </card>
+
+ <card id="material-dump-site">
+ <title>資材置場</title>
+ <price>5</price>
+ </card>
+
+ <card id="signal-spot">
+ <title>信号場</title>
+ <price>5</price>
+ </card>
+
+ <card id="control-room">
+ <title>制御室</title>
+ <price>7</price>
+ </card>
+
+ <card id="pulling">
+ <title>牽引</title>
+ <price>3</price>
+ </card>
+
+ <card id="first-train">
+ <title>一番列車</title>
+ <price>5</price>
+ </card>
+
+ <card id="wagon-factory">
+ <title>車輌工場</title>
+ <price>5</price>
+ </card>
+
+ <card id="ironworks">
+ <title>製鉄所</title>
+ <price>4</price>
+ </card>
+
+ <card id="freight-train">
+ <title>貨物列車</title>
+ <price>4</price>
+ </card>
+
+ <card id="conductor-area">
+ <title>車掌区</title>
+ <price>2</price>
+ </card>
+
+ <card id="rapid-train">
+ <title>快速列車</title>
+ <price>2</price>
+ </card>
+
+ <card id="switchback">
+ <title>スイッチバック</title>
+ <price>3</price>
+ </card>
+
+ <card id="information-central">
+ <title>案内所</title>
+ <price>4</price>
+ </card>
+
+ <card id="amusement-park">
+ <title>遊園地</title>
+ <price>4</price>
+ </card>
+
+ <card id="tourist-train">
+ <title>観光列車</title>
+ <price>4</price>
+ </card>
+
+ <card id="mail-train">
+ <title>郵便列車</title>
+ <price>4</price>
+ </card>
+
+ <card id="holiday-timetable">
+ <title>休日ダイヤ</title>
+ <price>3</price>
+ </card>
+
+ <card id="command-central">
+ <title>指令所</title>
+ <price>3</price>
+ </card>
+
+ <card id="stationmaster-office">
+ <title>駅長室</title>
+ <price>4</price>
+ </card>
+
+ <card id="maintenance-factory">
+ <title>整備工場</title>
+ <price>5</price>
+ </card>
+
+ <card id="signals">
+ <title>信号機</title>
+ <price>2</price>
+ </card>
+
+ <card id="station-crew">
+ <title>駅員</title>
+ <price>2</price>
+ </card>
+
+ <card id="temporary-timetable">
+ <title>臨時ダイヤ</title>
+ <price>5</price>
+ </card>
+</expansion>