OSDN Git Service

Initial commit.
authorMotoyuki Kasahara <m-kasahr>
Sat, 19 Oct 2013 12:03:37 +0000 (21:03 +0900)
committerMotoyuki Kasahara <m-kasahr>
Sat, 19 Oct 2013 12:03:37 +0000 (21:03 +0900)
137 files changed:
.gitignore [new file with mode: 0644]
GameRandomizer/.classpath [new file with mode: 0755]
GameRandomizer/.project [new file with mode: 0755]
GameRandomizer/AndroidManifest.xml [new file with mode: 0644]
GameRandomizer/ic_launcher-web.png [new file with mode: 0644]
GameRandomizer/libs/android-support-v4.jar [new file with mode: 0644]
GameRandomizer/proguard-project.txt [new file with mode: 0644]
GameRandomizer/project.properties [new file with mode: 0644]
GameRandomizer/res/drawable-hdpi/ic_launcher.png [new file with mode: 0644]
GameRandomizer/res/drawable-mdpi/ic_launcher.png [new file with mode: 0644]
GameRandomizer/res/drawable-xhdpi/ic_launcher.png [new file with mode: 0644]
GameRandomizer/res/drawable-xxhdpi/ic_launcher.png [new file with mode: 0644]
GameRandomizer/res/drawable/gamerandomizer.svg [new file with mode: 0644]
GameRandomizer/res/drawable/shape_select_button.xml [new file with mode: 0644]
GameRandomizer/res/layout/activity_card_list.xml [new file with mode: 0644]
GameRandomizer/res/layout/activity_directory_selection.xml [new file with mode: 0644]
GameRandomizer/res/layout/activity_main.xml [new file with mode: 0644]
GameRandomizer/res/layout/card_list.xml [new file with mode: 0644]
GameRandomizer/res/layout/expansion_list.xml [new file with mode: 0644]
GameRandomizer/res/menu/card_list.xml [new file with mode: 0644]
GameRandomizer/res/menu/card_list_option_menu.xml [new file with mode: 0644]
GameRandomizer/res/menu/main.xml [new file with mode: 0644]
GameRandomizer/res/menu/main_option_menu.xml [new file with mode: 0644]
GameRandomizer/res/values-ja/strings.xml [new file with mode: 0644]
GameRandomizer/res/values-sw600dp/dimens.xml [new file with mode: 0644]
GameRandomizer/res/values-sw720dp-land/dimens.xml [new file with mode: 0644]
GameRandomizer/res/values-v11/styles.xml [new file with mode: 0644]
GameRandomizer/res/values-v14/styles.xml [new file with mode: 0644]
GameRandomizer/res/values/dimens.xml [new file with mode: 0644]
GameRandomizer/res/values/strings.xml [new file with mode: 0644]
GameRandomizer/res/values/styles.xml [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListActivity.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListAdapter.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListItem.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/DataDirectoryPathFileReader.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/DataDirectoryPathFileWriter.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/DirectorySelectionActivity.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/ExpansionListAdapter.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/ExpansionListItem.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/MainActivity.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/MoreActivity.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/StatusFileReader.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizer/StatusFileWriter.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Card.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/CardComparator.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/CardComparisonMode.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Expansion.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Game.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilder.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderException.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderXMLFile.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderXMLFileException.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Randomizer.java [new file with mode: 0644]
GameRandomizer/src/jp/sourceforge/gamerandomizerlib/SimpleRandomizerCommand.java [new file with mode: 0644]
GameRandomizer/src/nedragtna/random/MTRandom.java [new file with mode: 0644]
GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/CardComparatorTest.java [new file with mode: 0644]
GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/CardTest.java [new file with mode: 0644]
GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/ExpansionTest.java [new file with mode: 0644]
GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/GameTest.java [new file with mode: 0644]
GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/RandomizerTest.java [new file with mode: 0644]
README [new file with mode: 0644]
gamedata/dominion/__version.txt [new file with mode: 0644]
gamedata/dominion/en/__game.xml [new file with mode: 0644]
gamedata/dominion/en/alchemy.xml [new file with mode: 0644]
gamedata/dominion/en/cornucopia.xml [new file with mode: 0644]
gamedata/dominion/en/dark-ages.xml [new file with mode: 0644]
gamedata/dominion/en/dominion.xml [new file with mode: 0644]
gamedata/dominion/en/guilds.xml [new file with mode: 0644]
gamedata/dominion/en/hinterlands.xml [new file with mode: 0644]
gamedata/dominion/en/intrigue.xml [new file with mode: 0644]
gamedata/dominion/en/promo-black-market.xml [new file with mode: 0644]
gamedata/dominion/en/promo-envoy.xml [new file with mode: 0644]
gamedata/dominion/en/promo-governor.xml [new file with mode: 0644]
gamedata/dominion/en/promo-stash.xml [new file with mode: 0644]
gamedata/dominion/en/promo-walled-village.xml [new file with mode: 0644]
gamedata/dominion/en/prosperity.xml [new file with mode: 0644]
gamedata/dominion/en/seaside.xml [new file with mode: 0644]
gamedata/dominion/ja/__game.xml [new file with mode: 0644]
gamedata/dominion/ja/alchemy.xml [new file with mode: 0644]
gamedata/dominion/ja/cornucopia.xml [new file with mode: 0644]
gamedata/dominion/ja/dark-ages.xml [new file with mode: 0644]
gamedata/dominion/ja/dominion.xml [new file with mode: 0644]
gamedata/dominion/ja/guilds.xml [new file with mode: 0644]
gamedata/dominion/ja/hinterlands.xml [new file with mode: 0644]
gamedata/dominion/ja/intrigue.xml [new file with mode: 0644]
gamedata/dominion/ja/promo-black-market.xml [new file with mode: 0644]
gamedata/dominion/ja/promo-envoy.xml [new file with mode: 0644]
gamedata/dominion/ja/promo-governor.xml [new file with mode: 0644]
gamedata/dominion/ja/promo-stash.xml [new file with mode: 0644]
gamedata/dominion/ja/promo-walled-village.xml [new file with mode: 0644]
gamedata/dominion/ja/prosperity.xml [new file with mode: 0644]
gamedata/dominion/ja/seaside.xml [new file with mode: 0644]
gamedata/gamedata-release.sh [new file with mode: 0755]
gamedata/heart-of-crown/__version.txt [new file with mode: 0644]
gamedata/heart-of-crown/en/__game.xml [new file with mode: 0644]
gamedata/heart-of-crown/en/basic-set.xml [new file with mode: 0644]
gamedata/heart-of-crown/ja/__game.xml [new file with mode: 0644]
gamedata/heart-of-crown/ja/basic-set.xml [new file with mode: 0644]
gamedata/heart-of-crown/ja/fairy-garden.xml [new file with mode: 0644]
gamedata/heart-of-crown/ja/hokugen-no-majo.xml [new file with mode: 0644]
gamedata/heart-of-crown/ja/kyokutou-henkyouryou.xml [new file with mode: 0644]
gamedata/nitroplus/__version.txt [new file with mode: 0644]
gamedata/nitroplus/ja/__game.xml [new file with mode: 0644]
gamedata/nitroplus/ja/multi.xml [new file with mode: 0644]
gamedata/nitroplus/ja/nitroplus.xml [new file with mode: 0644]
gamedata/nitroplus/ja/plus.xml [new file with mode: 0644]
gamedata/nitroplus/ja/promo-001.xml [new file with mode: 0644]
gamedata/nitroplus/ja/promo-002.xml [new file with mode: 0644]
gamedata/nitroplus/ja/promo-003.xml [new file with mode: 0644]
gamedata/nitroplus/ja/promo-004.xml [new file with mode: 0644]
gamedata/nitroplus/ja/promo-005.xml [new file with mode: 0644]
gamedata/tanto-cuore/__version.txt [new file with mode: 0644]
gamedata/tanto-cuore/en/__game.xml [new file with mode: 0644]
gamedata/tanto-cuore/en/expanding-the-house.xml [new file with mode: 0644]
gamedata/tanto-cuore/en/romantic-vacation.xml [new file with mode: 0644]
gamedata/tanto-cuore/en/tanto-cuore.xml [new file with mode: 0644]
gamedata/tanto-cuore/ja/__game.xml [new file with mode: 0644]
gamedata/tanto-cuore/ja/expanding-the-house.xml [new file with mode: 0644]
gamedata/tanto-cuore/ja/romantic-vacation.xml [new file with mode: 0644]
gamedata/tanto-cuore/ja/tanto-cuore.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/__version.txt [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/__game.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/eiyashou.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/extra.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/fuujinroku.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/koumakyou.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/promo-001.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/promo-002.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/promo-003.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/promo-004.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/promo-005.xml [new file with mode: 0644]
gamedata/touhou-shisouroku/ja/youyoumu.xml [new file with mode: 0644]
gamedata/trains/__version.txt [new file with mode: 0644]
gamedata/trains/en/__game.xml [new file with mode: 0644]
gamedata/trains/en/trains.xml [new file with mode: 0644]
gamedata/trains/ja/__game.xml [new file with mode: 0644]
gamedata/trains/ja/trains.xml [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..054fed3
--- /dev/null
@@ -0,0 +1,3 @@
+GameRandomizer/bin/
+GameRandomizer/gen/
+gamedata/*.zip
diff --git a/GameRandomizer/.classpath b/GameRandomizer/.classpath
new file mode 100755 (executable)
index 0000000..26bdfa6
--- /dev/null
@@ -0,0 +1,9 @@
+<?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
diff --git a/GameRandomizer/.project b/GameRandomizer/.project
new file mode 100755 (executable)
index 0000000..45e0abd
--- /dev/null
@@ -0,0 +1,33 @@
+<?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
diff --git a/GameRandomizer/AndroidManifest.xml b/GameRandomizer/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..5b9714b
--- /dev/null
@@ -0,0 +1,40 @@
+<?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
diff --git a/GameRandomizer/ic_launcher-web.png b/GameRandomizer/ic_launcher-web.png
new file mode 100644 (file)
index 0000000..76bfaab
Binary files /dev/null and b/GameRandomizer/ic_launcher-web.png differ
diff --git a/GameRandomizer/libs/android-support-v4.jar b/GameRandomizer/libs/android-support-v4.jar
new file mode 100644 (file)
index 0000000..cf12d28
Binary files /dev/null and b/GameRandomizer/libs/android-support-v4.jar differ
diff --git a/GameRandomizer/proguard-project.txt b/GameRandomizer/proguard-project.txt
new file mode 100644 (file)
index 0000000..f2fe155
--- /dev/null
@@ -0,0 +1,20 @@
+# 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 *;
+#}
diff --git a/GameRandomizer/project.properties b/GameRandomizer/project.properties
new file mode 100644 (file)
index 0000000..ce39f2d
--- /dev/null
@@ -0,0 +1,14 @@
+# 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
diff --git a/GameRandomizer/res/drawable-hdpi/ic_launcher.png b/GameRandomizer/res/drawable-hdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..1853bf8
Binary files /dev/null and b/GameRandomizer/res/drawable-hdpi/ic_launcher.png differ
diff --git a/GameRandomizer/res/drawable-mdpi/ic_launcher.png b/GameRandomizer/res/drawable-mdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..830691e
Binary files /dev/null and b/GameRandomizer/res/drawable-mdpi/ic_launcher.png differ
diff --git a/GameRandomizer/res/drawable-xhdpi/ic_launcher.png b/GameRandomizer/res/drawable-xhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..6955e58
Binary files /dev/null and b/GameRandomizer/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/GameRandomizer/res/drawable-xxhdpi/ic_launcher.png b/GameRandomizer/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644 (file)
index 0000000..b1f4ab1
Binary files /dev/null and b/GameRandomizer/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/GameRandomizer/res/drawable/gamerandomizer.svg b/GameRandomizer/res/drawable/gamerandomizer.svg
new file mode 100644 (file)
index 0000000..985533b
--- /dev/null
@@ -0,0 +1,222 @@
+<?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>
diff --git a/GameRandomizer/res/drawable/shape_select_button.xml b/GameRandomizer/res/drawable/shape_select_button.xml
new file mode 100644 (file)
index 0000000..858aae4
--- /dev/null
@@ -0,0 +1,20 @@
+<?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
diff --git a/GameRandomizer/res/layout/activity_card_list.xml b/GameRandomizer/res/layout/activity_card_list.xml
new file mode 100644 (file)
index 0000000..2de6c2b
--- /dev/null
@@ -0,0 +1,49 @@
+<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
diff --git a/GameRandomizer/res/layout/activity_directory_selection.xml b/GameRandomizer/res/layout/activity_directory_selection.xml
new file mode 100644 (file)
index 0000000..eb85c22
--- /dev/null
@@ -0,0 +1,51 @@
+<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
diff --git a/GameRandomizer/res/layout/activity_main.xml b/GameRandomizer/res/layout/activity_main.xml
new file mode 100644 (file)
index 0000000..f41a7c1
--- /dev/null
@@ -0,0 +1,59 @@
+<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
diff --git a/GameRandomizer/res/layout/card_list.xml b/GameRandomizer/res/layout/card_list.xml
new file mode 100644 (file)
index 0000000..f168812
--- /dev/null
@@ -0,0 +1,72 @@
+<?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
diff --git a/GameRandomizer/res/layout/expansion_list.xml b/GameRandomizer/res/layout/expansion_list.xml
new file mode 100644 (file)
index 0000000..770836f
--- /dev/null
@@ -0,0 +1,29 @@
+<?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
diff --git a/GameRandomizer/res/menu/card_list.xml b/GameRandomizer/res/menu/card_list.xml
new file mode 100644 (file)
index 0000000..d122a4b
--- /dev/null
@@ -0,0 +1,9 @@
+<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
diff --git a/GameRandomizer/res/menu/card_list_option_menu.xml b/GameRandomizer/res/menu/card_list_option_menu.xml
new file mode 100644 (file)
index 0000000..4a94b8e
--- /dev/null
@@ -0,0 +1,13 @@
+<?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
diff --git a/GameRandomizer/res/menu/main.xml b/GameRandomizer/res/menu/main.xml
new file mode 100644 (file)
index 0000000..d122a4b
--- /dev/null
@@ -0,0 +1,9 @@
+<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
diff --git a/GameRandomizer/res/menu/main_option_menu.xml b/GameRandomizer/res/menu/main_option_menu.xml
new file mode 100644 (file)
index 0000000..fd7bddd
--- /dev/null
@@ -0,0 +1,23 @@
+<?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
diff --git a/GameRandomizer/res/values-ja/strings.xml b/GameRandomizer/res/values-ja/strings.xml
new file mode 100644 (file)
index 0000000..32eb871
--- /dev/null
@@ -0,0 +1,31 @@
+<?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
diff --git a/GameRandomizer/res/values-sw600dp/dimens.xml b/GameRandomizer/res/values-sw600dp/dimens.xml
new file mode 100644 (file)
index 0000000..fb8a236
--- /dev/null
@@ -0,0 +1,8 @@
+<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
diff --git a/GameRandomizer/res/values-sw720dp-land/dimens.xml b/GameRandomizer/res/values-sw720dp-land/dimens.xml
new file mode 100644 (file)
index 0000000..6e88f11
--- /dev/null
@@ -0,0 +1,9 @@
+<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
diff --git a/GameRandomizer/res/values-v11/styles.xml b/GameRandomizer/res/values-v11/styles.xml
new file mode 100644 (file)
index 0000000..3a70f58
--- /dev/null
@@ -0,0 +1,11 @@
+<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
diff --git a/GameRandomizer/res/values-v14/styles.xml b/GameRandomizer/res/values-v14/styles.xml
new file mode 100644 (file)
index 0000000..3484fbf
--- /dev/null
@@ -0,0 +1,12 @@
+<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
diff --git a/GameRandomizer/res/values/dimens.xml b/GameRandomizer/res/values/dimens.xml
new file mode 100644 (file)
index 0000000..2e0e2ae
--- /dev/null
@@ -0,0 +1,7 @@
+<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
diff --git a/GameRandomizer/res/values/strings.xml b/GameRandomizer/res/values/strings.xml
new file mode 100644 (file)
index 0000000..1edd3bd
--- /dev/null
@@ -0,0 +1,34 @@
+<?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 &#8220;%s&#8221;</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 &amp; 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
diff --git a/GameRandomizer/res/values/styles.xml b/GameRandomizer/res/values/styles.xml
new file mode 100644 (file)
index 0000000..3713c78
--- /dev/null
@@ -0,0 +1,20 @@
+<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
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListActivity.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListActivity.java
new file mode 100644 (file)
index 0000000..bb53236
--- /dev/null
@@ -0,0 +1,504 @@
+//\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
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListAdapter.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListAdapter.java
new file mode 100644 (file)
index 0000000..2b22948
--- /dev/null
@@ -0,0 +1,67 @@
+//
+// 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;
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListItem.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/CardListItem.java
new file mode 100644 (file)
index 0000000..f4bf023
--- /dev/null
@@ -0,0 +1,83 @@
+//
+// 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];
+               }
+       };       
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/DataDirectoryPathFileReader.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/DataDirectoryPathFileReader.java
new file mode 100644 (file)
index 0000000..cd9e55f
--- /dev/null
@@ -0,0 +1,61 @@
+//
+// 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();                 
+               }
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/DataDirectoryPathFileWriter.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/DataDirectoryPathFileWriter.java
new file mode 100644 (file)
index 0000000..d27cf3d
--- /dev/null
@@ -0,0 +1,62 @@
+//
+// 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
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/DirectorySelectionActivity.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/DirectorySelectionActivity.java
new file mode 100644 (file)
index 0000000..8b795e3
--- /dev/null
@@ -0,0 +1,239 @@
+//\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
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/ExpansionListAdapter.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/ExpansionListAdapter.java
new file mode 100644 (file)
index 0000000..ea7b23e
--- /dev/null
@@ -0,0 +1,59 @@
+//
+// 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;
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/ExpansionListItem.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/ExpansionListItem.java
new file mode 100644 (file)
index 0000000..40fcd29
--- /dev/null
@@ -0,0 +1,70 @@
+//
+// 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];
+               }
+       };       
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/MainActivity.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/MainActivity.java
new file mode 100644 (file)
index 0000000..a8894e8
--- /dev/null
@@ -0,0 +1,696 @@
+//
+// 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();
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/MoreActivity.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/MoreActivity.java
new file mode 100644 (file)
index 0000000..add939b
--- /dev/null
@@ -0,0 +1,253 @@
+//\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
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/StatusFileReader.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/StatusFileReader.java
new file mode 100644 (file)
index 0000000..f2c61de
--- /dev/null
@@ -0,0 +1,149 @@
+//
+// 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();
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizer/StatusFileWriter.java b/GameRandomizer/src/jp/sourceforge/gamerandomizer/StatusFileWriter.java
new file mode 100644 (file)
index 0000000..69c7f40
--- /dev/null
@@ -0,0 +1,125 @@
+//
+// 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();
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Card.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Card.java
new file mode 100644 (file)
index 0000000..d18fbdf
--- /dev/null
@@ -0,0 +1,168 @@
+//
+// 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;
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/CardComparator.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/CardComparator.java
new file mode 100644 (file)
index 0000000..18f87cf
--- /dev/null
@@ -0,0 +1,185 @@
+//
+// 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());
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/CardComparisonMode.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/CardComparisonMode.java
new file mode 100644 (file)
index 0000000..8c3a942
--- /dev/null
@@ -0,0 +1,21 @@
+//
+// 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
+};
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Expansion.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Expansion.java
new file mode 100644 (file)
index 0000000..a384e40
--- /dev/null
@@ -0,0 +1,176 @@
+//
+// 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;
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Game.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Game.java
new file mode 100644 (file)
index 0000000..19eea4c
--- /dev/null
@@ -0,0 +1,676 @@
+//
+// 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;
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilder.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilder.java
new file mode 100644 (file)
index 0000000..3945ce8
--- /dev/null
@@ -0,0 +1,129 @@
+//
+// 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&lt;Game&gt; 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());
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderException.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderException.java
new file mode 100644 (file)
index 0000000..d92b0b3
--- /dev/null
@@ -0,0 +1,62 @@
+//
+// 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);
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderXMLFile.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderXMLFile.java
new file mode 100644 (file)
index 0000000..13d0924
--- /dev/null
@@ -0,0 +1,866 @@
+//
+// 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(&quot;games&quot;);
+ *   List&lt;Game&gt; 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());
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderXMLFileException.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/GameBuilderXMLFileException.java
new file mode 100644 (file)
index 0000000..0744e8a
--- /dev/null
@@ -0,0 +1,146 @@
+//
+// 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.
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Randomizer.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/Randomizer.java
new file mode 100644 (file)
index 0000000..86c63fb
--- /dev/null
@@ -0,0 +1,158 @@
+//
+// 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());
+       }
+}
diff --git a/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/SimpleRandomizerCommand.java b/GameRandomizer/src/jp/sourceforge/gamerandomizerlib/SimpleRandomizerCommand.java
new file mode 100644 (file)
index 0000000..b646519
--- /dev/null
@@ -0,0 +1,229 @@
+//
+// 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()));
+               }
+       }
+}
diff --git a/GameRandomizer/src/nedragtna/random/MTRandom.java b/GameRandomizer/src/nedragtna/random/MTRandom.java
new file mode 100644 (file)
index 0000000..ad85d8e
--- /dev/null
@@ -0,0 +1,442 @@
+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) &lt;&lt; 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
diff --git a/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/CardComparatorTest.java b/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/CardComparatorTest.java
new file mode 100644 (file)
index 0000000..0af1035
--- /dev/null
@@ -0,0 +1,296 @@
+//
+// 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);
+       }
+}
diff --git a/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/CardTest.java b/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/CardTest.java
new file mode 100644 (file)
index 0000000..adaec69
--- /dev/null
@@ -0,0 +1,165 @@
+//
+// 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);
+       }
+}
diff --git a/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/ExpansionTest.java b/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/ExpansionTest.java
new file mode 100644 (file)
index 0000000..8edb23b
--- /dev/null
@@ -0,0 +1,165 @@
+//
+// 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);
+       }
+}
diff --git a/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/GameTest.java b/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/GameTest.java
new file mode 100644 (file)
index 0000000..cc3e39e
--- /dev/null
@@ -0,0 +1,227 @@
+//
+// 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);
+       }
+}
diff --git a/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/RandomizerTest.java b/GameRandomizer/srctest/jp/sourceforge/gamerandomizerlib/RandomizerTest.java
new file mode 100644 (file)
index 0000000..63a2d4e
--- /dev/null
@@ -0,0 +1,233 @@
+//
+// 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);
+       }
+}
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..440b78e
--- /dev/null
+++ b/README
@@ -0,0 +1,34 @@
+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.
diff --git a/gamedata/dominion/__version.txt b/gamedata/dominion/__version.txt
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/gamedata/dominion/en/__game.xml b/gamedata/dominion/en/__game.xml
new file mode 100644 (file)
index 0000000..51ef7c5
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/dominion/en/alchemy.xml b/gamedata/dominion/en/alchemy.xml
new file mode 100644 (file)
index 0000000..32a4ba8
--- /dev/null
@@ -0,0 +1,67 @@
+<?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>
diff --git a/gamedata/dominion/en/cornucopia.xml b/gamedata/dominion/en/cornucopia.xml
new file mode 100644 (file)
index 0000000..1cc2e0f
--- /dev/null
@@ -0,0 +1,72 @@
+<?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>
diff --git a/gamedata/dominion/en/dark-ages.xml b/gamedata/dominion/en/dark-ages.xml
new file mode 100644 (file)
index 0000000..f80c499
--- /dev/null
@@ -0,0 +1,182 @@
+<?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>
diff --git a/gamedata/dominion/en/dominion.xml b/gamedata/dominion/en/dominion.xml
new file mode 100644 (file)
index 0000000..7e4aa62
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/dominion/en/guilds.xml b/gamedata/dominion/en/guilds.xml
new file mode 100644 (file)
index 0000000..d46aef0
--- /dev/null
@@ -0,0 +1,72 @@
+<?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>
diff --git a/gamedata/dominion/en/hinterlands.xml b/gamedata/dominion/en/hinterlands.xml
new file mode 100644 (file)
index 0000000..b2b75c9
--- /dev/null
@@ -0,0 +1,137 @@
+<?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>
diff --git a/gamedata/dominion/en/intrigue.xml b/gamedata/dominion/en/intrigue.xml
new file mode 100644 (file)
index 0000000..eab89b6
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/dominion/en/promo-black-market.xml b/gamedata/dominion/en/promo-black-market.xml
new file mode 100644 (file)
index 0000000..db1504e
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/en/promo-envoy.xml b/gamedata/dominion/en/promo-envoy.xml
new file mode 100644 (file)
index 0000000..1eec920
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/en/promo-governor.xml b/gamedata/dominion/en/promo-governor.xml
new file mode 100644 (file)
index 0000000..f275d95
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/en/promo-stash.xml b/gamedata/dominion/en/promo-stash.xml
new file mode 100644 (file)
index 0000000..d1ac0b8
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/en/promo-walled-village.xml b/gamedata/dominion/en/promo-walled-village.xml
new file mode 100644 (file)
index 0000000..26aed48
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/en/prosperity.xml b/gamedata/dominion/en/prosperity.xml
new file mode 100644 (file)
index 0000000..86b8353
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/dominion/en/seaside.xml b/gamedata/dominion/en/seaside.xml
new file mode 100644 (file)
index 0000000..d39247e
--- /dev/null
@@ -0,0 +1,137 @@
+<?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>
diff --git a/gamedata/dominion/ja/__game.xml b/gamedata/dominion/ja/__game.xml
new file mode 100644 (file)
index 0000000..63d24fa
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/dominion/ja/alchemy.xml b/gamedata/dominion/ja/alchemy.xml
new file mode 100644 (file)
index 0000000..48fd300
--- /dev/null
@@ -0,0 +1,67 @@
+<?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>
diff --git a/gamedata/dominion/ja/cornucopia.xml b/gamedata/dominion/ja/cornucopia.xml
new file mode 100644 (file)
index 0000000..17ae618
--- /dev/null
@@ -0,0 +1,72 @@
+<?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>
diff --git a/gamedata/dominion/ja/dark-ages.xml b/gamedata/dominion/ja/dark-ages.xml
new file mode 100644 (file)
index 0000000..1091ab4
--- /dev/null
@@ -0,0 +1,182 @@
+<?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>
diff --git a/gamedata/dominion/ja/dominion.xml b/gamedata/dominion/ja/dominion.xml
new file mode 100644 (file)
index 0000000..4ae6ac4
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/dominion/ja/guilds.xml b/gamedata/dominion/ja/guilds.xml
new file mode 100644 (file)
index 0000000..f2d0373
--- /dev/null
@@ -0,0 +1,72 @@
+<?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>
diff --git a/gamedata/dominion/ja/hinterlands.xml b/gamedata/dominion/ja/hinterlands.xml
new file mode 100644 (file)
index 0000000..66c6b2b
--- /dev/null
@@ -0,0 +1,137 @@
+<?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>
diff --git a/gamedata/dominion/ja/intrigue.xml b/gamedata/dominion/ja/intrigue.xml
new file mode 100644 (file)
index 0000000..2bd6f13
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/dominion/ja/promo-black-market.xml b/gamedata/dominion/ja/promo-black-market.xml
new file mode 100644 (file)
index 0000000..8db2a9e
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/ja/promo-envoy.xml b/gamedata/dominion/ja/promo-envoy.xml
new file mode 100644 (file)
index 0000000..ede78fb
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/ja/promo-governor.xml b/gamedata/dominion/ja/promo-governor.xml
new file mode 100644 (file)
index 0000000..be4900b
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/ja/promo-stash.xml b/gamedata/dominion/ja/promo-stash.xml
new file mode 100644 (file)
index 0000000..5cb0985
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/ja/promo-walled-village.xml b/gamedata/dominion/ja/promo-walled-village.xml
new file mode 100644 (file)
index 0000000..fd872b5
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/dominion/ja/prosperity.xml b/gamedata/dominion/ja/prosperity.xml
new file mode 100644 (file)
index 0000000..9b1521b
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/dominion/ja/seaside.xml b/gamedata/dominion/ja/seaside.xml
new file mode 100644 (file)
index 0000000..5bd2833
--- /dev/null
@@ -0,0 +1,137 @@
+<?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>
diff --git a/gamedata/gamedata-release.sh b/gamedata/gamedata-release.sh
new file mode 100755 (executable)
index 0000000..cb7afd0
--- /dev/null
@@ -0,0 +1,142 @@
+#! /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
diff --git a/gamedata/heart-of-crown/__version.txt b/gamedata/heart-of-crown/__version.txt
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/gamedata/heart-of-crown/en/__game.xml b/gamedata/heart-of-crown/en/__game.xml
new file mode 100644 (file)
index 0000000..f3e6e80
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/heart-of-crown/en/basic-set.xml b/gamedata/heart-of-crown/en/basic-set.xml
new file mode 100644 (file)
index 0000000..24b5c7c
--- /dev/null
@@ -0,0 +1,157 @@
+<?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>
diff --git a/gamedata/heart-of-crown/ja/__game.xml b/gamedata/heart-of-crown/ja/__game.xml
new file mode 100644 (file)
index 0000000..b195590
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/heart-of-crown/ja/basic-set.xml b/gamedata/heart-of-crown/ja/basic-set.xml
new file mode 100644 (file)
index 0000000..d57fc51
--- /dev/null
@@ -0,0 +1,157 @@
+<?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>
diff --git a/gamedata/heart-of-crown/ja/fairy-garden.xml b/gamedata/heart-of-crown/ja/fairy-garden.xml
new file mode 100644 (file)
index 0000000..d41a75e
--- /dev/null
@@ -0,0 +1,157 @@
+<?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>
diff --git a/gamedata/heart-of-crown/ja/hokugen-no-majo.xml b/gamedata/heart-of-crown/ja/hokugen-no-majo.xml
new file mode 100644 (file)
index 0000000..b6bb9f5
--- /dev/null
@@ -0,0 +1,72 @@
+<?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>
diff --git a/gamedata/heart-of-crown/ja/kyokutou-henkyouryou.xml b/gamedata/heart-of-crown/ja/kyokutou-henkyouryou.xml
new file mode 100644 (file)
index 0000000..3d9638a
--- /dev/null
@@ -0,0 +1,67 @@
+<?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>
diff --git a/gamedata/nitroplus/__version.txt b/gamedata/nitroplus/__version.txt
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/gamedata/nitroplus/ja/__game.xml b/gamedata/nitroplus/ja/__game.xml
new file mode 100644 (file)
index 0000000..16bd3ad
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/multi.xml b/gamedata/nitroplus/ja/multi.xml
new file mode 100644 (file)
index 0000000..c77a220
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/nitroplus.xml b/gamedata/nitroplus/ja/nitroplus.xml
new file mode 100644 (file)
index 0000000..f9371dc
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/plus.xml b/gamedata/nitroplus/ja/plus.xml
new file mode 100644 (file)
index 0000000..71dab5b
--- /dev/null
@@ -0,0 +1,82 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/promo-001.xml b/gamedata/nitroplus/ja/promo-001.xml
new file mode 100644 (file)
index 0000000..5d0e61c
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/promo-002.xml b/gamedata/nitroplus/ja/promo-002.xml
new file mode 100644 (file)
index 0000000..b50e73d
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/promo-003.xml b/gamedata/nitroplus/ja/promo-003.xml
new file mode 100644 (file)
index 0000000..2d82266
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/promo-004.xml b/gamedata/nitroplus/ja/promo-004.xml
new file mode 100644 (file)
index 0000000..f8f7305
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/nitroplus/ja/promo-005.xml b/gamedata/nitroplus/ja/promo-005.xml
new file mode 100644 (file)
index 0000000..cdcaffa
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/tanto-cuore/__version.txt b/gamedata/tanto-cuore/__version.txt
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/gamedata/tanto-cuore/en/__game.xml b/gamedata/tanto-cuore/en/__game.xml
new file mode 100644 (file)
index 0000000..bdd770b
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/tanto-cuore/en/expanding-the-house.xml b/gamedata/tanto-cuore/en/expanding-the-house.xml
new file mode 100644 (file)
index 0000000..e2ea777
--- /dev/null
@@ -0,0 +1,87 @@
+<?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>
diff --git a/gamedata/tanto-cuore/en/romantic-vacation.xml b/gamedata/tanto-cuore/en/romantic-vacation.xml
new file mode 100644 (file)
index 0000000..41efa4f
--- /dev/null
@@ -0,0 +1,97 @@
+<?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>
diff --git a/gamedata/tanto-cuore/en/tanto-cuore.xml b/gamedata/tanto-cuore/en/tanto-cuore.xml
new file mode 100644 (file)
index 0000000..11ab6c6
--- /dev/null
@@ -0,0 +1,87 @@
+<?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>
diff --git a/gamedata/tanto-cuore/ja/__game.xml b/gamedata/tanto-cuore/ja/__game.xml
new file mode 100644 (file)
index 0000000..524644b
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/tanto-cuore/ja/expanding-the-house.xml b/gamedata/tanto-cuore/ja/expanding-the-house.xml
new file mode 100644 (file)
index 0000000..c328546
--- /dev/null
@@ -0,0 +1,87 @@
+<?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>
diff --git a/gamedata/tanto-cuore/ja/romantic-vacation.xml b/gamedata/tanto-cuore/ja/romantic-vacation.xml
new file mode 100644 (file)
index 0000000..a806f00
--- /dev/null
@@ -0,0 +1,97 @@
+<?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>
diff --git a/gamedata/tanto-cuore/ja/tanto-cuore.xml b/gamedata/tanto-cuore/ja/tanto-cuore.xml
new file mode 100644 (file)
index 0000000..bdcf512
--- /dev/null
@@ -0,0 +1,87 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/__version.txt b/gamedata/touhou-shisouroku/__version.txt
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/gamedata/touhou-shisouroku/ja/__game.xml b/gamedata/touhou-shisouroku/ja/__game.xml
new file mode 100644 (file)
index 0000000..bc2a51c
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/eiyashou.xml b/gamedata/touhou-shisouroku/ja/eiyashou.xml
new file mode 100644 (file)
index 0000000..fe1f83b
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/extra.xml b/gamedata/touhou-shisouroku/ja/extra.xml
new file mode 100644 (file)
index 0000000..0fea519
--- /dev/null
@@ -0,0 +1,87 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/fuujinroku.xml b/gamedata/touhou-shisouroku/ja/fuujinroku.xml
new file mode 100644 (file)
index 0000000..76e07f7
--- /dev/null
@@ -0,0 +1,107 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/koumakyou.xml b/gamedata/touhou-shisouroku/ja/koumakyou.xml
new file mode 100644 (file)
index 0000000..6610b89
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/promo-001.xml b/gamedata/touhou-shisouroku/ja/promo-001.xml
new file mode 100644 (file)
index 0000000..86acc7f
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/promo-002.xml b/gamedata/touhou-shisouroku/ja/promo-002.xml
new file mode 100644 (file)
index 0000000..2928395
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/promo-003.xml b/gamedata/touhou-shisouroku/ja/promo-003.xml
new file mode 100644 (file)
index 0000000..8aa19cd
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/promo-004.xml b/gamedata/touhou-shisouroku/ja/promo-004.xml
new file mode 100644 (file)
index 0000000..3b64829
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/promo-005.xml b/gamedata/touhou-shisouroku/ja/promo-005.xml
new file mode 100644 (file)
index 0000000..09bb4ec
--- /dev/null
@@ -0,0 +1,12 @@
+<?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>
diff --git a/gamedata/touhou-shisouroku/ja/youyoumu.xml b/gamedata/touhou-shisouroku/ja/youyoumu.xml
new file mode 100644 (file)
index 0000000..0ece440
--- /dev/null
@@ -0,0 +1,132 @@
+<?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>
diff --git a/gamedata/trains/__version.txt b/gamedata/trains/__version.txt
new file mode 100644 (file)
index 0000000..d3827e7
--- /dev/null
@@ -0,0 +1 @@
+1.0
diff --git a/gamedata/trains/en/__game.xml b/gamedata/trains/en/__game.xml
new file mode 100644 (file)
index 0000000..86a54c7
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/trains/en/trains.xml b/gamedata/trains/en/trains.xml
new file mode 100644 (file)
index 0000000..3acb11d
--- /dev/null
@@ -0,0 +1,157 @@
+<?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>
diff --git a/gamedata/trains/ja/__game.xml b/gamedata/trains/ja/__game.xml
new file mode 100644 (file)
index 0000000..cce7c21
--- /dev/null
@@ -0,0 +1,6 @@
+<?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>
diff --git a/gamedata/trains/ja/trains.xml b/gamedata/trains/ja/trains.xml
new file mode 100644 (file)
index 0000000..963c79d
--- /dev/null
@@ -0,0 +1,157 @@
+<?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>