updated examples HelloAndroid, LunarLander and Snake to Android 1.5
git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@18704 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
parent
806892c0f5
commit
58e60e1417
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.helloandroid"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<application android:label="@string/app_name">
|
||||
<activity android:name=".HelloAndroid"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="imported" default="help">
|
||||
|
||||
<property environment="env"/>
|
||||
|
||||
<!-- Android SDK -->
|
||||
<condition property="_dx" value="dx.bat" else="dx">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<condition property="_apkbuilder" value="apkbuilder.bat" else="apkbuilder">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<property name="android.jar" value="${sdk-location}/platforms/android-1.5/android.jar"/>
|
||||
<property name="maps.jar" value="${sdk-location}/add-ons/maps.jar"/>
|
||||
<property name="dx.cmd" value="${sdk-location}/platforms/android-1.5/tools/${_dx}"/>
|
||||
<property name="aapt.cmd" value="${sdk-location}/platforms/android-1.5/tools/aapt"/>
|
||||
<property name="apkbuilder.cmd" value="${sdk-location}/tools/${_apkbuilder}"/>
|
||||
<property name="adb.cmd" value="${sdk-location}/tools/adb"/>
|
||||
|
||||
<!-- Scala SDK -->
|
||||
<property name="scala-compiler.jar" value="${env.SCALA_HOME}/lib/scala-compiler.jar"/>
|
||||
<property name="scala-library.jar" value="${env.SCALA_HOME}/lib/scala-library.jar"/>
|
||||
|
||||
<!-- Android build files -->
|
||||
<property name="manifest.xml" value="${basedir}/AndroidManifest.xml"/>
|
||||
<xmlproperty file="${manifest.xml}" collapseAttributes="true"/>
|
||||
<property name="classes.dex" value="${basedir}/bin/classes.dex"/>
|
||||
<property name="empty.apk" value="${basedir}/bin/${ant.project.name}.ap_"/>
|
||||
<property name="basename.apk" value="${ant.project.name}-debug.apk"/>
|
||||
<property name="package.apk" value="${basedir}/bin/${basename.apk}"/>
|
||||
|
||||
<taskdef name="apkbuilder"
|
||||
classname="com.android.ant.ApkBuilderTask"
|
||||
classpathref="android.antlibs"
|
||||
/>
|
||||
<path id="scala.path">
|
||||
<pathelement path="${scala-compiler.jar}"/>
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
</path>
|
||||
<taskdef
|
||||
resource="scala/tools/ant/antlib.xml"
|
||||
classpathref="scala.path"
|
||||
/>
|
||||
|
||||
<uptodate property="gen.uptodate" targetfile="${basedir}/gen/.gen-complete">
|
||||
<srcfiles dir="${basedir}/gen" includes="**/*.java"/>
|
||||
</uptodate>
|
||||
<uptodate property="compile.uptodate" targetfile="${basedir}/bin/.compile-complete">
|
||||
<srcfiles dir="${basedir}/gen" includes="**/*.java"/>
|
||||
</uptodate>
|
||||
<uptodate property="dex.uptodate" targetfile="${classes.dex}">
|
||||
<srcfiles dir="${basedir}/bin/classes" includes="**/*.class"/>
|
||||
</uptodate>
|
||||
<uptodate property="pkg.uptodate" targetfile="${empty.apk}">
|
||||
<srcfiles dir="${basedir}/bin" includes="**/*.dex,**/*.xml"/>
|
||||
</uptodate>
|
||||
<uptodate property="apk.uptodate" targetfile="${package.apk}">
|
||||
<srcfiles dir="${basedir}/bin" includes="**/*.dex,**/*.xml"/>
|
||||
</uptodate>
|
||||
|
||||
<target name="resource-src" unless="gen.uptodate">
|
||||
<mkdir dir="${basedir}/gen"/>
|
||||
<echo message="Generating R.java / Manifest.java from the resources..."/>
|
||||
<exec executable="${aapt.cmd}">
|
||||
<arg line="package -m -J '${basedir}/gen' -M '${manifest.xml}' -S '${basedir}/res' -I '${android.jar}'"/>
|
||||
</exec>
|
||||
<touch file="${basedir}/gen/.gen-complete" verbose="no"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="resource-src" unless="compile.uptodate">
|
||||
<path id="boot.path">
|
||||
<pathelement location="${android.jar}"/>
|
||||
<pathelement location="${maps.jar}"/>
|
||||
</path>
|
||||
<mkdir dir="${basedir}/bin/classes"/>
|
||||
<javac
|
||||
srcdir="${basedir}/src${path.separator}${basedir}/gen" includes="**/*.java"
|
||||
destdir="${basedir}/bin/classes" bootclasspathref="boot.path"
|
||||
target="1.5" source="1.5" encoding="ascii">
|
||||
<classpath>
|
||||
<pathelement location="${android.jar}"/>
|
||||
</classpath>
|
||||
</javac>
|
||||
<scalac
|
||||
srcdir="${basedir}/src" includes="**/*.scala"
|
||||
destdir="${basedir}/bin/classes">
|
||||
<classpath>
|
||||
<pathelement location="${scala-library.jar}"/>
|
||||
<pathelement location="${android.jar}"/>
|
||||
<pathelement location="${basedir}/bin/classes"/>
|
||||
</classpath>
|
||||
</scalac>
|
||||
<touch file="${basedir}/bin/.compile-complete" verbose="no"/>
|
||||
</target>
|
||||
|
||||
<target name="dex" depends="compile" unless="dex.uptodate">
|
||||
<echo message="Converting compiled files and external libraries into bin/classes.dex..."/>
|
||||
<!-- IMPORTANT ! -->
|
||||
<!-- in shell script /opt/android/platforms/android-1.5/tools/dx -->
|
||||
<!-- add the following code on line 60: javaOpts="-Xmx768M" -->
|
||||
<exec executable="${dx.cmd}">
|
||||
<arg line="--dex --no-optimize --output='${classes.dex}' '${basedir}/bin/classes' '${scala-library.jar}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="package-resources" depends="dex" unless="pkg.uptodate">
|
||||
<echo message="Packaging resources"/>
|
||||
<exec executable="${aapt.cmd}">
|
||||
<arg line="package -f -M '${manifest.xml}' -S '${basedir}/res' -I '${android.jar}' -F '${empty.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="debug" depends="package-resources" unless="apk.uptodate">
|
||||
<apkbuilder
|
||||
basename="${ant.project.name}" outfolder="${basedir}/bin"
|
||||
signed="true" verbose="true"
|
||||
/>
|
||||
<exec executable="${apkbuilder.cmd}">
|
||||
<arg line="'${package.apk}' -v -z '${empty.apk}' -f '${classes.dex}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="release">
|
||||
<echo message="Sorry. Not yet implemented"/>
|
||||
</target>
|
||||
|
||||
<target name="install" depends="debug">
|
||||
<echo message="Installing bin/${basename.apk} onto default emulator..."/>
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="install '${package.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="reinstall" depends="debug">
|
||||
<echo message="Reinstalling bin/${basename.apk} onto default emulator..."/>
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="install -r '${package.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="uninstall">
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="uninstall '${manifest.package}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="help">
|
||||
<echo message="Android Ant Build. Available targets:"/>
|
||||
<echo message=" help: Display this help."/>
|
||||
<echo message=" debug: Builds the application and sign it with a debug key."/>
|
||||
<echo message=" install: Install the debug package onto a running emulator or device."/>
|
||||
<echo message=" reinstall: Reinstall the debug package onto a running emulator or device."/>
|
||||
<echo message=" uninstall: Uninstall the application from a running emulator or device."/>
|
||||
<echo message=" clean: Clean up build files from project."/>
|
||||
</target>
|
||||
|
||||
<macrodef name="remove">
|
||||
<attribute name="dir"/>
|
||||
<sequential>
|
||||
<delete dir="@{dir}" includeemptydirs="yes" quiet="yes" failonerror="no"/>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<target name="clean" description="clean up">
|
||||
<echo message="Cleaning project '${ant.project.name}'..."/>
|
||||
<remove dir="${basedir}/bin"/>
|
||||
<remove dir="${basedir}/gen"/>
|
||||
</target>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,15 @@
|
|||
# This file is used to override default values used by the Ant build system.
|
||||
#
|
||||
# This file must be checked in Version Control Systems, as it is
|
||||
# integral to the build system of your project.
|
||||
|
||||
# The name of your application package as defined in the manifest.
|
||||
# Used by the 'uninstall' rule.
|
||||
#application-package=com.example.myproject
|
||||
|
||||
# The name of the source folder.
|
||||
#source-folder=src
|
||||
|
||||
# The name of the output folder.
|
||||
#out-folder=bin
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="HelloAndroid" default="help">
|
||||
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contain the path to the SDK. It should *NOT* be checked in in Version
|
||||
Control Systems. -->
|
||||
<property file="local.properties"/>
|
||||
|
||||
<!-- The build.properties file can be created by you and is never touched
|
||||
by the 'android' tool. This is the place to change some of the default property values
|
||||
used by the Ant rules.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
application-package
|
||||
the name of your application package as defined in the manifest. Used by the
|
||||
'uninstall' rule.
|
||||
source-folder
|
||||
the name of the source folder. Default is 'src'.
|
||||
out-folder
|
||||
the name of the output folder. Default is 'bin'.
|
||||
|
||||
Properties related to the SDK location or the project target should be updated
|
||||
using the 'android' tool with the 'update' action.
|
||||
|
||||
This file is an integral part of the build system for your application and
|
||||
should be checked in in Version Control Systems.
|
||||
|
||||
-->
|
||||
<property file="build.properties"/>
|
||||
|
||||
<!-- The default.properties file is created and updated by the 'android' tool, as well
|
||||
as ADT.
|
||||
This file is an integral part of the build system for your application and
|
||||
should be checked in in Version Control Systems. -->
|
||||
<property file="default.properties"/>
|
||||
|
||||
<!-- Custom Android task to deal with the project target, and import the proper rules.
|
||||
This requires ant 1.6.0 or above. -->
|
||||
<path id="android.antlibs">
|
||||
<pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
|
||||
</path>
|
||||
|
||||
<taskdef name="setup"
|
||||
classname="com.android.ant.SetupTask"
|
||||
classpathref="android.antlibs"/>
|
||||
|
||||
<!-- Execute the Android Setup task that will setup some properties specific to the target,
|
||||
and import the rules files.
|
||||
To customize the rules, copy/paste them below the task, and disable import by setting
|
||||
the import attribute to false:
|
||||
<setup import="false" />
|
||||
|
||||
This will ensure that the properties are setup correctly but that your customized
|
||||
targets are used.
|
||||
-->
|
||||
<setup import="false"/>
|
||||
|
||||
<import file="build-imported.xml"/>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,11 @@
|
|||
# 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 use,
|
||||
# "build.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
|
||||
# Project target.
|
||||
target=Google Inc.:Google APIs:3
|
|
@ -0,0 +1,11 @@
|
|||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must *NOT* be checked in Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
|
||||
# location of the SDK. This is only used by Ant
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#sdk-location=C:\\Program Files\\Android
|
||||
sdk-location=/opt/android
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World, HelloAndroid"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Hello (Scala)</string>
|
||||
</resources>
|
|
@ -0,0 +1,17 @@
|
|||
package com.example.helloandroid
|
||||
|
||||
import _root_.android.app.Activity
|
||||
import _root_.android.os.Bundle
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
class HelloAndroid extends Activity {
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val tv = new TextView(this)
|
||||
tv setText "Scala on Android"
|
||||
setContentView(tv)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.helloandroid.tests"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<!-- We add an application tag here just so that we can indicate that
|
||||
this package needs to link against the android.test library,
|
||||
which is needed when building test cases. -->
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
<!--
|
||||
This declares that this application uses the instrumentation test runner targeting
|
||||
the package of com.android.helloandroid. To run the tests use the command:
|
||||
"adb shell am instrument -w com.android.helloandroid.tests/android.test.InstrumentationTestRunner"
|
||||
-->
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="com.android.helloandroid"
|
||||
android:label="Tests for HelloAndroid"/>
|
||||
</manifest>
|
|
@ -1,11 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.lunarlander">
|
||||
<application>
|
||||
<activity class=".LunarLander" android:label="LunarLander">
|
||||
package="com.example.android.lunarlander">
|
||||
<application android:icon="@drawable/app_lunar_lander" android:label="@string/app_name">
|
||||
<activity android:name="LunarLander">
|
||||
<intent-filter>
|
||||
<action android:value="android.intent.action.MAIN" />
|
||||
<category android:value="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="imported" default="help">
|
||||
|
||||
<property environment="env"/>
|
||||
|
||||
<!-- Android SDK -->
|
||||
<condition property="_dx" value="dx.bat" else="dx">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<condition property="_apkbuilder" value="apkbuilder.bat" else="apkbuilder">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<property name="android.jar" value="${sdk-location}/platforms/android-1.5/android.jar"/>
|
||||
<property name="maps.jar" value="${sdk-location}/add-ons/maps.jar"/>
|
||||
<property name="dx.cmd" value="${sdk-location}/platforms/android-1.5/tools/${_dx}"/>
|
||||
<property name="aapt.cmd" value="${sdk-location}/platforms/android-1.5/tools/aapt"/>
|
||||
<property name="apkbuilder.cmd" value="${sdk-location}/tools/${_apkbuilder}"/>
|
||||
<property name="adb.cmd" value="${sdk-location}/tools/adb"/>
|
||||
|
||||
<!-- Scala SDK -->
|
||||
<property name="scala-compiler.jar" value="${env.SCALA_HOME}/lib/scala-compiler.jar"/>
|
||||
<property name="scala-library.jar" value="${env.SCALA_HOME}/lib/scala-library.jar"/>
|
||||
|
||||
<!-- Android build files -->
|
||||
<property name="manifest.xml" value="${basedir}/AndroidManifest.xml"/>
|
||||
<xmlproperty file="${manifest.xml}" collapseAttributes="true"/>
|
||||
<property name="classes.dex" value="${basedir}/bin/classes.dex"/>
|
||||
<property name="empty.apk" value="${basedir}/bin/${ant.project.name}.ap_"/>
|
||||
<property name="basename.apk" value="${ant.project.name}-debug.apk"/>
|
||||
<property name="package.apk" value="${basedir}/bin/${basename.apk}"/>
|
||||
|
||||
<taskdef name="apkbuilder"
|
||||
classname="com.android.ant.ApkBuilderTask"
|
||||
classpathref="android.antlibs"
|
||||
/>
|
||||
<path id="scala.path">
|
||||
<pathelement path="${scala-compiler.jar}"/>
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
</path>
|
||||
<taskdef
|
||||
resource="scala/tools/ant/antlib.xml"
|
||||
classpathref="scala.path"
|
||||
/>
|
||||
|
||||
<uptodate property="gen.uptodate" targetfile="${basedir}/gen/.gen-complete">
|
||||
<srcfiles dir="${basedir}/gen" includes="**/*.java"/>
|
||||
</uptodate>
|
||||
<uptodate property="compile.uptodate" targetfile="${basedir}/bin/.compile-complete">
|
||||
<srcfiles dir="${basedir}/gen" includes="**/*.java"/>
|
||||
</uptodate>
|
||||
<uptodate property="dex.uptodate" targetfile="${classes.dex}">
|
||||
<srcfiles dir="${basedir}/bin/classes" includes="**/*.class"/>
|
||||
</uptodate>
|
||||
<uptodate property="pkg.uptodate" targetfile="${empty.apk}">
|
||||
<srcfiles dir="${basedir}/bin" includes="**/*.dex,**/*.xml"/>
|
||||
</uptodate>
|
||||
<uptodate property="apk.uptodate" targetfile="${package.apk}">
|
||||
<srcfiles dir="${basedir}/bin" includes="**/*.dex,**/*.xml"/>
|
||||
</uptodate>
|
||||
|
||||
<target name="resource-src" unless="gen.uptodate">
|
||||
<mkdir dir="${basedir}/gen"/>
|
||||
<echo message="Generating R.java / Manifest.java from the resources..."/>
|
||||
<exec executable="${aapt.cmd}">
|
||||
<arg line="package -m -J '${basedir}/gen' -M '${manifest.xml}' -S '${basedir}/res' -I '${android.jar}'"/>
|
||||
</exec>
|
||||
<touch file="${basedir}/gen/.gen-complete" verbose="no"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="resource-src" unless="compile.uptodate">
|
||||
<path id="boot.path">
|
||||
<pathelement location="${android.jar}"/>
|
||||
<pathelement location="${maps.jar}"/>
|
||||
</path>
|
||||
<mkdir dir="${basedir}/bin/classes"/>
|
||||
<javac
|
||||
srcdir="${basedir}/src${path.separator}${basedir}/gen" includes="**/*.java"
|
||||
destdir="${basedir}/bin/classes" bootclasspathref="boot.path"
|
||||
target="1.5" source="1.5" encoding="ascii">
|
||||
<classpath>
|
||||
<pathelement location="${android.jar}"/>
|
||||
</classpath>
|
||||
</javac>
|
||||
<scalac
|
||||
srcdir="${basedir}/src" includes="**/*.scala"
|
||||
destdir="${basedir}/bin/classes">
|
||||
<classpath>
|
||||
<pathelement location="${scala-library.jar}"/>
|
||||
<pathelement location="${android.jar}"/>
|
||||
<pathelement location="${basedir}/bin/classes"/>
|
||||
</classpath>
|
||||
</scalac>
|
||||
<touch file="${basedir}/bin/.compile-complete" verbose="no"/>
|
||||
</target>
|
||||
|
||||
<target name="dex" depends="compile" unless="dex.uptodate">
|
||||
<echo message="Converting compiled files and external libraries into bin/classes.dex..."/>
|
||||
<!-- IMPORTANT ! -->
|
||||
<!-- in shell script /opt/android/platforms/android-1.5/tools/dx -->
|
||||
<!-- add the following code on line 60: javaOpts="-Xmx768M" -->
|
||||
<exec executable="${dx.cmd}">
|
||||
<arg line="--dex --no-optimize --output='${classes.dex}' '${basedir}/bin/classes' '${scala-library.jar}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="package-resources" depends="dex" unless="pkg.uptodate">
|
||||
<echo message="Packaging resources"/>
|
||||
<exec executable="${aapt.cmd}">
|
||||
<arg line="package -f -M '${manifest.xml}' -S '${basedir}/res' -I '${android.jar}' -F '${empty.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="debug" depends="package-resources" unless="apk.uptodate">
|
||||
<apkbuilder
|
||||
basename="${ant.project.name}" outfolder="${basedir}/bin"
|
||||
signed="true" verbose="true"
|
||||
/>
|
||||
<exec executable="${apkbuilder.cmd}">
|
||||
<arg line="'${package.apk}' -v -z '${empty.apk}' -f '${classes.dex}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="release">
|
||||
<echo message="Sorry. Not yet implemented"/>
|
||||
</target>
|
||||
|
||||
<target name="install" depends="debug">
|
||||
<echo message="Installing bin/${basename.apk} onto default emulator..."/>
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="install '${package.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="reinstall" depends="debug">
|
||||
<echo message="Reinstalling bin/${basename.apk} onto default emulator..."/>
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="install -r '${package.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="uninstall">
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="uninstall '${manifest.package}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="help">
|
||||
<echo message="Android Ant Build. Available targets:"/>
|
||||
<echo message=" help: Display this help."/>
|
||||
<echo message=" debug: Builds the application and sign it with a debug key."/>
|
||||
<echo message=" install: Install the debug package onto a running emulator or device."/>
|
||||
<echo message=" reinstall: Reinstall the debug package onto a running emulator or device."/>
|
||||
<echo message=" uninstall: Uninstall the application from a running emulator or device."/>
|
||||
<echo message=" clean: Clean up build files from project."/>
|
||||
</target>
|
||||
|
||||
<macrodef name="remove">
|
||||
<attribute name="dir"/>
|
||||
<sequential>
|
||||
<delete dir="@{dir}" includeemptydirs="yes" quiet="yes" failonerror="no"/>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<target name="clean" description="clean up">
|
||||
<echo message="Cleaning project '${ant.project.name}'..."/>
|
||||
<remove dir="${basedir}/bin"/>
|
||||
<remove dir="${basedir}/gen"/>
|
||||
</target>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,15 @@
|
|||
# This file is used to override default values used by the Ant build system.
|
||||
#
|
||||
# This file must be checked in Version Control Systems, as it is
|
||||
# integral to the build system of your project.
|
||||
|
||||
# The name of your application package as defined in the manifest.
|
||||
# Used by the 'uninstall' rule.
|
||||
#application-package=com.example.myproject
|
||||
|
||||
# The name of the source folder.
|
||||
#source-folder=src
|
||||
|
||||
# The name of the output folder.
|
||||
#out-folder=bin
|
||||
|
|
@ -1,222 +1,64 @@
|
|||
<?xml version="1.0" ?>
|
||||
<project name="LunarLander" default="package">
|
||||
<property name="sdk-folder" value="/home/linuxsoft/apps/android-m3-rc37a" />
|
||||
<property name="android-tools" value="/home/linuxsoft/apps/android-m3-rc37a/tools" />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="LunarLander" default="help">
|
||||
|
||||
<!-- The intermediates directory -->
|
||||
<!-- Eclipse uses "bin" for its own output, so we do the same. -->
|
||||
<property name="outdir" value="bin" />
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contain the path to the SDK. It should *NOT* be checked in in Version
|
||||
Control Systems. -->
|
||||
<property file="local.properties"/>
|
||||
|
||||
<!-- No user servicable parts below. -->
|
||||
<!-- The build.properties file can be created by you and is never touched
|
||||
by the 'android' tool. This is the place to change some of the default property values
|
||||
used by the Ant rules.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
<!-- Input directories -->
|
||||
<property name="resource-dir" value="res" />
|
||||
<property name="asset-dir" value="assets" />
|
||||
<property name="srcdir" value="src" />
|
||||
application-package
|
||||
the name of your application package as defined in the manifest. Used by the
|
||||
'uninstall' rule.
|
||||
source-folder
|
||||
the name of the source folder. Default is 'src'.
|
||||
out-folder
|
||||
the name of the output folder. Default is 'bin'.
|
||||
|
||||
<!-- Output directories -->
|
||||
<property name="outdir-classes" value="${outdir}/classes" />
|
||||
Properties related to the SDK location or the project target should be updated
|
||||
using the 'android' tool with the 'update' action.
|
||||
|
||||
<!-- Create R.java in the source directory -->
|
||||
<property name="outdir-r" value="src" />
|
||||
This file is an integral part of the build system for your application and
|
||||
should be checked in in Version Control Systems.
|
||||
|
||||
<!-- Intermediate files -->
|
||||
<property name="dex-file" value="classes.dex" />
|
||||
<property name="intermediate-dex" value="${outdir}/${dex-file}" />
|
||||
-->
|
||||
<property file="build.properties"/>
|
||||
|
||||
<!-- The final package file to generate -->
|
||||
<property name="out-package" value="${outdir}/${ant.project.name}.apk" />
|
||||
<!-- The default.properties file is created and updated by the 'android' tool, as well
|
||||
as ADT.
|
||||
This file is an integral part of the build system for your application and
|
||||
should be checked in in Version Control Systems. -->
|
||||
<property file="default.properties"/>
|
||||
|
||||
<!-- Tools -->
|
||||
<property name="aapt" value="${android-tools}/aapt" />
|
||||
<property name="aidl" value="${android-tools}/aidl" />
|
||||
<property name="dx" value="${android-tools}/dx" />
|
||||
<property name="zip" value="zip" />
|
||||
<property name="android-jar" value="${sdk-folder}/android.jar" />
|
||||
|
||||
<!-- Scala -->
|
||||
<property environment="env"/>
|
||||
<property name="scala.dir" value="${env.SCALA_HOME}"/>
|
||||
<property name="scala-compiler.jar" value="${scala.dir}/lib/scala-compiler.jar"/>
|
||||
<property name="scala-library.jar" value="${scala.dir}/lib/scala-library.jar"/>
|
||||
<property name="scala-android.jar" value="${scala.dir}/lib/scala-android.jar"/>
|
||||
<fail message="Missing library scala-android.jar (use sbaz to install it)">
|
||||
<condition><not><available file="${scala-android.jar}"/></not></condition>
|
||||
</fail>
|
||||
<property name="scala-depend.jar" value="${android-tools}/lib/scala-depend.jar"/>
|
||||
<path id="scala.path">
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
<pathelement path="${scala-compiler.jar}"/>
|
||||
<!-- Custom Android task to deal with the project target, and import the proper rules.
|
||||
This requires ant 1.6.0 or above. -->
|
||||
<path id="android.antlibs">
|
||||
<pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
|
||||
</path>
|
||||
<path id="scalac.path">
|
||||
<pathelement path="${android-jar}"/>
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
</path>
|
||||
<taskdef
|
||||
resource="scala/tools/ant/antlib.xml"
|
||||
classpathref="scala.path"
|
||||
/>
|
||||
<macrodef name="smartjar">
|
||||
<attribute name="srcdir"/>
|
||||
<attribute name="basedir"/>
|
||||
<attribute name="classname"/>
|
||||
<attribute name="destfile"/>
|
||||
<sequential>
|
||||
<depend
|
||||
srcdir="@{srcdir}"
|
||||
destdir="@{basedir}" closure="true"
|
||||
cache="@{basedir}"
|
||||
classpath="${android-jar}"
|
||||
/>
|
||||
<java
|
||||
classname="ch.epfl.lamp.util.depend" output="@{basedir}/classes.dep"
|
||||
classpath="${scala-library.jar}${path.separator}${scala-depend.jar}">
|
||||
<arg line="@{basedir}${file.separator}dependencies.txt"/>
|
||||
<arg line="@{classname}"/>
|
||||
</java>
|
||||
<jar
|
||||
destfile="@{destfile}"
|
||||
basedir="@{basedir}"
|
||||
includesfile="@{basedir}/classes.dep"
|
||||
/>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<!-- Rules -->
|
||||
<taskdef name="setup"
|
||||
classname="com.android.ant.SetupTask"
|
||||
classpathref="android.antlibs"/>
|
||||
|
||||
<!-- Create the output directories if they don't exist yet. -->
|
||||
<target name="dirs">
|
||||
<mkdir dir="${outdir}" />
|
||||
<mkdir dir="${outdir-classes}" />
|
||||
</target>
|
||||
<!-- Execute the Android Setup task that will setup some properties specific to the target,
|
||||
and import the rules files.
|
||||
To customize the rules, copy/paste them below the task, and disable import by setting
|
||||
the import attribute to false:
|
||||
<setup import="false" />
|
||||
|
||||
<!-- Generate the R.java file for this project's resources. -->
|
||||
<target name="resource-src" depends="dirs">
|
||||
<echo>Generating R.java...</echo>
|
||||
<exec executable="${aapt}" failonerror="true">
|
||||
<arg value="compile" />
|
||||
<arg value="-m" />
|
||||
<arg value="-J" />
|
||||
<arg value="${outdir-r}" />
|
||||
<arg value="-M" />
|
||||
<arg value="AndroidManifest.xml" />
|
||||
<arg value="-S" />
|
||||
<arg value="${resource-dir}" />
|
||||
<arg value="-I" />
|
||||
<arg value="${android-jar}" />
|
||||
</exec>
|
||||
</target>
|
||||
This will ensure that the properties are setup correctly but that your customized
|
||||
targets are used.
|
||||
-->
|
||||
<setup import="false"/>
|
||||
|
||||
<!-- Generate java classes from .aidl files. -->
|
||||
<target name="aidl" depends="dirs">
|
||||
<apply executable="${aidl}" failonerror="true">
|
||||
<fileset dir="${srcdir}">
|
||||
<include name="**/*.aidl"/>
|
||||
</fileset>
|
||||
</apply>
|
||||
</target>
|
||||
<import file="build-imported.xml"/>
|
||||
|
||||
<!-- Compile this project's .java files into .class files. -->
|
||||
<target name="compile" depends="dirs, resource-src, aidl">
|
||||
<javac encoding="ascii" target="1.5" debug="true" extdirs=""
|
||||
srcdir="."
|
||||
destdir="${outdir-classes}"
|
||||
bootclasspath="${android-jar}" />
|
||||
<scalac encoding="ascii" target="jvm-1.5"
|
||||
srcdir="${srcdir}"
|
||||
destdir="${outdir-classes}"
|
||||
bootclasspathref="scalac.path" />
|
||||
<unjar src="${scala-android.jar}" dest="${outdir-classes}"/>
|
||||
<smartjar
|
||||
srcdir="${srcdir}"
|
||||
basedir="${outdir-classes}"
|
||||
classname="com.google.android.lunarlander.LunarLander"
|
||||
destfile="${outdir}/${ant.project.name}.jar"
|
||||
/>
|
||||
<delete includeemptydirs="true">
|
||||
<fileset dir="${outdir-classes}" includes="**/*"/>
|
||||
</delete>
|
||||
<unjar src="${outdir}/${ant.project.name}.jar" dest="${outdir-classes}"/>
|
||||
</target>
|
||||
|
||||
<!-- Convert this project's .class files into .dex files. -->
|
||||
<target name="dex" depends="compile">
|
||||
<exec executable="${dx}" failonerror="true">
|
||||
<arg value="-JXmx384M" />
|
||||
<arg value="--dex" />
|
||||
<arg value="--output=${intermediate-dex}" />
|
||||
<arg value="--locals=full" />
|
||||
<arg value="--positions=lines" />
|
||||
<arg path="${outdir-classes}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Put the project's resources into the output package file. -->
|
||||
<target name="package-res-and-assets">
|
||||
<echo>Packaging resources and assets...</echo>
|
||||
<exec executable="${aapt}" failonerror="true">
|
||||
<arg value="package" />
|
||||
<arg value="-f" />
|
||||
<arg value="-c" />
|
||||
<arg value="-M" />
|
||||
<arg value="AndroidManifest.xml" />
|
||||
<arg value="-S" />
|
||||
<arg value="${resource-dir}" />
|
||||
<arg value="-A" />
|
||||
<arg value="${asset-dir}" />
|
||||
<arg value="-I" />
|
||||
<arg value="${android-jar}" />
|
||||
<arg value="${out-package}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Same as package-res-and-assets, but without "-A ${asset-dir}" -->
|
||||
<target name="package-res-no-assets">
|
||||
<echo>Packaging resources...</echo>
|
||||
<exec executable="${aapt}" failonerror="true">
|
||||
<arg value="package" />
|
||||
<arg value="-f" />
|
||||
<arg value="-c" />
|
||||
<arg value="-M" />
|
||||
<arg value="AndroidManifest.xml" />
|
||||
<arg value="-S" />
|
||||
<arg value="${resource-dir}" />
|
||||
<!-- No assets directory -->
|
||||
<arg value="-I" />
|
||||
<arg value="${android-jar}" />
|
||||
<arg value="${out-package}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Invoke the proper target depending on whether or not
|
||||
an assets directory is present. -->
|
||||
<!-- TODO: find a nicer way to include the "-A ${asset-dir}" argument
|
||||
only when the assets dir exists. -->
|
||||
<target name="package-res">
|
||||
<available file="${asset-dir}" type="dir"
|
||||
property="res-target" value="and-assets" />
|
||||
<property name="res-target" value="no-assets" />
|
||||
<antcall target="package-res-${res-target}" />
|
||||
</target>
|
||||
|
||||
<!-- Put the project's .class files into the output package file. -->
|
||||
<target name="package-java" depends="compile, package-res">
|
||||
<echo>Packaging java...</echo>
|
||||
<jar destfile="${out-package}"
|
||||
basedir="${outdir-classes}"
|
||||
update="true" />
|
||||
</target>
|
||||
|
||||
<!-- Put the project's .dex files into the output package file. -->
|
||||
<target name="package-dex" depends="dex, package-res">
|
||||
<echo>Packaging dex...</echo>
|
||||
<exec executable="${zip}" failonerror="true">
|
||||
<arg value="-qj" />
|
||||
<arg value="${out-package}" />
|
||||
<arg value="${intermediate-dex}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Create the package file for this project from the sources. -->
|
||||
<target name="package" depends="package-dex" />
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# 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 use,
|
||||
# "build.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
|
||||
# Project target.
|
||||
target=Google Inc.:Google APIs:3
|
|
@ -0,0 +1,11 @@
|
|||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must *NOT* be checked in Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
|
||||
# location of the SDK. This is only used by Ant
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#sdk-location=C:\\Program Files\\Android
|
||||
sdk-location=/opt/android
|
|
@ -1,41 +1,39 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
<!-- Copyright (C) 2007 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<com.google.android.lunarlander.LunarView
|
||||
id="@+id/lunar"
|
||||
<com.example.android.lunarlander.LunarView
|
||||
android:id="@+id/lunar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@drawable/earthrise" />
|
||||
android:layout_height="fill_parent"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" >
|
||||
<TextView
|
||||
id="@+id/text"
|
||||
android:id="@+id/text"
|
||||
android:text="@string/lunar_layout_text_text"
|
||||
android:visibility="visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textAlign="center"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="#88ffffff"
|
||||
android:textSize="24sp"/>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -1,22 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Lunar Lander</string>
|
||||
<string name="app_name">Lunar Lander (Scala)</string>
|
||||
|
||||
<string name="menu_start">Start</string>
|
||||
<string name="menu_stop">Stop</string>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
|
@ -0,0 +1,135 @@
|
|||
package com.example.android.lunarlander
|
||||
|
||||
import _root_.android.app.Activity
|
||||
import _root_.android.os.Bundle
|
||||
import _root_.android.util.Log
|
||||
import _root_.android.view.{Menu, MenuItem, Window}
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
/** <p>
|
||||
* This is a simple LunarLander activity that houses a single LunarView.
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>animating by calling invalidate() from draw()</li>
|
||||
* <li>loading and drawing resources</li>
|
||||
* <li>handling onPause() in an animation</li>
|
||||
* </ul>
|
||||
*/
|
||||
class LunarLander extends Activity {
|
||||
import LunarThread._
|
||||
|
||||
private object MenuId extends Enumeration {
|
||||
val EASY, HARD, MEDIUM, PAUSE, RESUME, START, STOP = Value
|
||||
}
|
||||
private type MenuId = MenuId.Value
|
||||
|
||||
/** A handle to the thread that's actually running the animation. */
|
||||
private var mLunarThread: LunarThread = null
|
||||
|
||||
/** A handle to the View in which the game is running. */
|
||||
private var mLunarView: LunarView = null
|
||||
|
||||
/** Invoked during init to give the Activity a chance to set up its Menu.
|
||||
*
|
||||
* @param menu the Menu to which entries may be added
|
||||
* @return true
|
||||
*/
|
||||
override def onCreateOptionsMenu(menu: Menu): Boolean = {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
menu.add(0, MenuId.START.id, 0, R.string.menu_start)
|
||||
menu.add(0, MenuId.STOP.id, 0, R.string.menu_stop)
|
||||
menu.add(0, MenuId.PAUSE.id, 0, R.string.menu_pause)
|
||||
menu.add(0, MenuId.RESUME.id, 0, R.string.menu_resume)
|
||||
menu.add(0, MenuId.EASY.id, 0, R.string.menu_easy)
|
||||
menu.add(0, MenuId.MEDIUM.id, 0, R.string.menu_medium)
|
||||
menu.add(0, MenuId.HARD.id, 0, R.string.menu_hard)
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/** Invoked when the user selects an item from the Menu.
|
||||
*
|
||||
* @param item the Menu entry which was selected
|
||||
* @return true if the Menu item was legit (and we consumed it), false
|
||||
* otherwise
|
||||
*/
|
||||
override def onOptionsItemSelected(item: MenuItem): Boolean =
|
||||
item.getItemId match {
|
||||
case MenuId.START =>
|
||||
mLunarThread.doStart()
|
||||
true
|
||||
case MenuId.STOP =>
|
||||
mLunarThread.setState(State.LOSE, getText(R.string.message_stopped))
|
||||
true
|
||||
case MenuId.PAUSE =>
|
||||
mLunarThread.pause()
|
||||
true
|
||||
case MenuId.RESUME =>
|
||||
mLunarThread.unpause()
|
||||
true
|
||||
case MenuId.EASY =>
|
||||
mLunarThread setDifficulty Difficulty.EASY
|
||||
true
|
||||
case MenuId.MEDIUM =>
|
||||
mLunarThread setDifficulty Difficulty.MEDIUM
|
||||
true
|
||||
case MenuId.HARD =>
|
||||
mLunarThread setDifficulty Difficulty.HARD
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
/** Invoked when the Activity is created.
|
||||
*
|
||||
* @param savedInstanceState a Bundle containing state saved from a previous
|
||||
* execution, or null if this is a new execution
|
||||
*/
|
||||
override protected def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// turn off the window's title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
// tell system to use the layout defined in our XML file
|
||||
setContentView(R.layout.lunar_layout)
|
||||
|
||||
// get handles to the LunarView from XML, and its LunarThread
|
||||
mLunarView = findViewById(R.id.lunar).asInstanceOf[LunarView]
|
||||
mLunarThread = mLunarView.getThread
|
||||
|
||||
// give the LunarView a handle to the TextView used for messages
|
||||
mLunarView setTextView findViewById(R.id.text).asInstanceOf[TextView]
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// we were just launched: set up a new game
|
||||
mLunarThread setState State.READY
|
||||
Log.w(this.getClass().getName(), "SIS is null")
|
||||
} else {
|
||||
// we are being restored: resume a previous game
|
||||
mLunarThread restoreState savedInstanceState
|
||||
Log.w(this.getClass().getName(), "SIS is nonnull")
|
||||
}
|
||||
}
|
||||
|
||||
/** Invoked when the Activity loses user focus.
|
||||
*/
|
||||
override protected def onPause() {
|
||||
super.onPause()
|
||||
mLunarView.getThread.pause() // pause game when Activity pauses
|
||||
}
|
||||
|
||||
/** Notification that something is about to happen, to give the Activity a
|
||||
* chance to save state.
|
||||
*
|
||||
* @param outState a Bundle into which this Activity should save its state
|
||||
*/
|
||||
override protected def onSaveInstanceState(outState: Bundle) {
|
||||
// just have the View's thread save its state into our Bundle
|
||||
super.onSaveInstanceState(outState)
|
||||
mLunarThread saveState outState
|
||||
Log.w(this.getClass().getName(), "SIS called")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,679 @@
|
|||
package com.example.android.lunarlander
|
||||
|
||||
import _root_.android.content.Context
|
||||
import _root_.android.graphics.{Bitmap, BitmapFactory, Canvas, Paint, RectF}
|
||||
import _root_.android.graphics.drawable.Drawable
|
||||
import _root_.android.os.{Bundle, Handler, Message}
|
||||
import _root_.android.view.{KeyEvent, SurfaceHolder, View}
|
||||
|
||||
|
||||
private[lunarlander] object LunarThread {
|
||||
|
||||
/** Difficulty setting constants */
|
||||
object Difficulty extends Enumeration {
|
||||
val EASY, HARD, MEDIUM = Value
|
||||
}
|
||||
type Difficulty = Difficulty.Value
|
||||
|
||||
/** Physics constants */
|
||||
val PHYS_DOWN_ACCEL_SEC = 35
|
||||
val PHYS_FIRE_ACCEL_SEC = 80
|
||||
val PHYS_FUEL_INIT = 60
|
||||
val PHYS_FUEL_MAX = 100
|
||||
val PHYS_FUEL_SEC = 10
|
||||
val PHYS_SLEW_SEC = 120 // degrees/second rotate
|
||||
val PHYS_SPEED_HYPERSPACE = 180
|
||||
val PHYS_SPEED_INIT = 30
|
||||
val PHYS_SPEED_MAX = 120
|
||||
|
||||
/** State-tracking constants */
|
||||
object State extends Enumeration {
|
||||
val LOSE, PAUSE, READY, RUNNING, WIN = Value
|
||||
}
|
||||
type State = State.Value
|
||||
|
||||
/** Goal condition constants*/
|
||||
val TARGET_ANGLE = 18 // > this angle means crash
|
||||
val TARGET_BOTTOM_PADDING = 17 // px below gear
|
||||
val TARGET_PAD_HEIGHT = 8 // how high above ground
|
||||
val TARGET_SPEED = 28 // > this speed means crash
|
||||
val TARGET_WIDTH = 1.6 // width of target
|
||||
|
||||
/** UI constants (i.e. the speed & fuel bars) */
|
||||
val UI_BAR = 100 // width of the bar(s)
|
||||
val UI_BAR_HEIGHT = 10 // height of the bar(s)
|
||||
|
||||
private val KEY_DIFFICULTY = "mDifficulty"
|
||||
private val KEY_DX = "mDX"
|
||||
|
||||
private val KEY_DY = "mDY"
|
||||
private val KEY_FUEL = "mFuel"
|
||||
private val KEY_GOAL_ANGLE = "mGoalAngle"
|
||||
private val KEY_GOAL_SPEED = "mGoalSpeed"
|
||||
private val KEY_GOAL_WIDTH = "mGoalWidth"
|
||||
|
||||
private val KEY_GOAL_X = "mGoalX"
|
||||
private val KEY_HEADING = "mHeading"
|
||||
private val KEY_LANDER_HEIGHT = "mLanderHeight"
|
||||
private val KEY_LANDER_WIDTH = "mLanderWidth"
|
||||
private val KEY_WINS = "mWinsInARow"
|
||||
|
||||
private val KEY_X = "mX"
|
||||
private val KEY_Y = "mY"
|
||||
}
|
||||
|
||||
private[lunarlander] class LunarThread(mSurfaceHolder: SurfaceHolder,
|
||||
mContext: Context,
|
||||
mHandler: Handler) extends Thread {
|
||||
import LunarThread._
|
||||
|
||||
/** Member (state) fields
|
||||
*/
|
||||
/** The drawable to use as the background of the animation canvas */
|
||||
private var mBackgroundImage: Bitmap = null
|
||||
|
||||
/** Current height/width of the surface/canvas.
|
||||
*
|
||||
* @see #setSurfaceSize
|
||||
*/
|
||||
private var mCanvasHeight = 1
|
||||
private var mCanvasWidth = 1
|
||||
|
||||
/** What to draw for the Lander when it has crashed */
|
||||
private var mCrashedImage: Drawable = null
|
||||
|
||||
/** Current difficulty -- amount of fuel, allowed angle, etc.
|
||||
* Default is MEDIUM.
|
||||
*/
|
||||
private var mDifficulty = Difficulty.MEDIUM
|
||||
|
||||
/** Velocity dx/dy. */
|
||||
private var mDX: Double = 0.0
|
||||
private var mDY: Double = 0.0
|
||||
|
||||
/** Is the engine burning? */
|
||||
private var mEngineFiring = true
|
||||
|
||||
/** What to draw for the Lander when the engine is firing */
|
||||
private var mFiringImage: Drawable = null
|
||||
|
||||
/** Fuel remaining */
|
||||
private var mFuel: Double = PHYS_FUEL_INIT
|
||||
|
||||
/** Allowed angle. */
|
||||
private var mGoalAngle: Int = 0
|
||||
|
||||
/** Allowed speed. */
|
||||
private var mGoalSpeed = 0
|
||||
|
||||
/** Width of the landing pad. */
|
||||
private var mGoalWidth = 0
|
||||
|
||||
/** X of the landing pad. */
|
||||
private var mGoalX = 0
|
||||
|
||||
/** Lander heading in degrees, with 0 up, 90 right. Kept in the range
|
||||
* 0..360.
|
||||
*/
|
||||
private var mHeading: Double = 0.0
|
||||
|
||||
/** Pixel height of lander image. */
|
||||
private var mLanderHeight = 0
|
||||
|
||||
/** What to draw for the Lander in its normal state */
|
||||
private var mLanderImage: Drawable = null
|
||||
|
||||
/** Pixel width of lander image. */
|
||||
private var mLanderWidth = 0
|
||||
|
||||
/** Used to figure out elapsed time between frames */
|
||||
private var mLastTime = 0L
|
||||
|
||||
/** Paint to draw the lines on screen. */
|
||||
private var mLinePaint: Paint = null
|
||||
|
||||
/** "Bad" speed-too-high variant of the line color. */
|
||||
private var mLinePaintBad: Paint = null
|
||||
|
||||
/** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
|
||||
private var mMode = State.READY
|
||||
|
||||
/** Currently rotating, -1 left, 0 none, 1 right. */
|
||||
private var mRotating = 0
|
||||
|
||||
/** Indicate whether the surface has been created & is ready to draw */
|
||||
private var mRun = false;
|
||||
|
||||
/** Scratch rect object. */
|
||||
private var mScratchRect = new RectF(0, 0, 0, 0)
|
||||
|
||||
/** Number of wins in a row. */
|
||||
private var mWinsInARow = 0
|
||||
|
||||
/** X/Y of lander center. */
|
||||
private var mX = 0.0
|
||||
private var mY = 0.0
|
||||
|
||||
initLunarThread()
|
||||
|
||||
private def initLunarThread() {
|
||||
val res = mContext.getResources()
|
||||
// cache handles to our key sprites & other drawables
|
||||
mLanderImage = res getDrawable R.drawable.lander_plain
|
||||
mFiringImage = res getDrawable R.drawable.lander_firing
|
||||
mCrashedImage = res getDrawable R.drawable.lander_crashed
|
||||
|
||||
// load background image as a Bitmap instead of a Drawable b/c
|
||||
// we don't need to transform it and it's faster to draw this way
|
||||
mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.earthrise)
|
||||
|
||||
// Use the regular lander image as the model size for all sprites
|
||||
mLanderWidth = mLanderImage.getIntrinsicWidth()
|
||||
mLanderHeight = mLanderImage.getIntrinsicHeight()
|
||||
|
||||
// Initialize paints for speedometer
|
||||
mLinePaint = new Paint()
|
||||
mLinePaint setAntiAlias true
|
||||
mLinePaint.setARGB(255, 0, 255, 0)
|
||||
|
||||
mLinePaintBad = new Paint()
|
||||
mLinePaintBad setAntiAlias true
|
||||
mLinePaintBad.setARGB(255, 120, 180, 0)
|
||||
|
||||
// initial show-up of lander (not yet playing)
|
||||
mX = mLanderWidth
|
||||
mY = mLanderHeight * 2
|
||||
mFuel = PHYS_FUEL_INIT
|
||||
mDX = 0
|
||||
mDY = 0
|
||||
mHeading = 0
|
||||
}
|
||||
|
||||
/** Starts the game, setting parameters for the current difficulty.
|
||||
*/
|
||||
def doStart() {
|
||||
mSurfaceHolder synchronized {
|
||||
// First set the game for Medium difficulty
|
||||
mFuel = PHYS_FUEL_INIT
|
||||
mEngineFiring = false
|
||||
mGoalWidth = (mLanderWidth * TARGET_WIDTH).toInt
|
||||
mGoalSpeed = TARGET_SPEED
|
||||
mGoalAngle = TARGET_ANGLE
|
||||
var speedInit: Double = PHYS_SPEED_INIT
|
||||
|
||||
// Adjust difficulty params for EASY/HARD
|
||||
if (mDifficulty == Difficulty.EASY) {
|
||||
mFuel = mFuel * 3 / 2
|
||||
mGoalWidth = mGoalWidth * 4 / 3
|
||||
mGoalSpeed = mGoalSpeed * 3 / 2
|
||||
mGoalAngle = mGoalAngle * 4 / 3
|
||||
speedInit = speedInit * 3 / 4
|
||||
} else if (mDifficulty == Difficulty.HARD) {
|
||||
mFuel = mFuel * 7 / 8
|
||||
mGoalWidth = mGoalWidth * 3 / 4
|
||||
mGoalSpeed = mGoalSpeed * 7 / 8
|
||||
speedInit = speedInit * 4 / 3
|
||||
}
|
||||
|
||||
// pick a convenient initial location for the lander sprite
|
||||
mX = mCanvasWidth / 2
|
||||
mY = mCanvasHeight - mLanderHeight / 2
|
||||
|
||||
// start with a little random motion
|
||||
mDY = Math.random * -speedInit
|
||||
mDX = Math.random * 2 * speedInit - speedInit
|
||||
mHeading = 0
|
||||
|
||||
// Figure initial spot for landing, not too near center
|
||||
do {
|
||||
mGoalX = (Math.random * (mCanvasWidth - mGoalWidth)).toInt
|
||||
}
|
||||
while (Math.abs(mGoalX - (mX - mLanderWidth / 2)) <= mCanvasHeight / 6)
|
||||
|
||||
mLastTime = System.currentTimeMillis + 100
|
||||
setState(State.RUNNING)
|
||||
}
|
||||
}
|
||||
|
||||
/** Pauses the physics update & animation.
|
||||
*/
|
||||
def pause() {
|
||||
mSurfaceHolder synchronized {
|
||||
if (mMode == State.RUNNING) setState(State.PAUSE)
|
||||
}
|
||||
}
|
||||
|
||||
/** Restores game state from the indicated Bundle. Typically called when
|
||||
* the Activity is being restored after having been previously
|
||||
* destroyed.
|
||||
*
|
||||
* @param savedState Bundle containing the game state
|
||||
*/
|
||||
def restoreState(savedState: Bundle) {
|
||||
mSurfaceHolder synchronized {
|
||||
setState(State.PAUSE)
|
||||
mRotating = 0
|
||||
mEngineFiring = false
|
||||
|
||||
mDifficulty = Difficulty(savedState getInt KEY_DIFFICULTY)
|
||||
mX = savedState getDouble KEY_X
|
||||
mY = savedState getDouble KEY_Y
|
||||
mDX = savedState getDouble KEY_DX
|
||||
mDY = savedState getDouble KEY_DY
|
||||
mHeading = savedState getDouble KEY_HEADING
|
||||
|
||||
mLanderWidth = savedState getInt KEY_LANDER_WIDTH
|
||||
mLanderHeight = savedState getInt KEY_LANDER_HEIGHT
|
||||
mGoalX = savedState getInt KEY_GOAL_X
|
||||
mGoalSpeed = savedState getInt KEY_GOAL_SPEED
|
||||
mGoalAngle = savedState getInt KEY_GOAL_ANGLE
|
||||
mGoalWidth = savedState getInt KEY_GOAL_WIDTH
|
||||
mWinsInARow = savedState getInt KEY_WINS
|
||||
mFuel = savedState getDouble KEY_FUEL
|
||||
}
|
||||
}
|
||||
|
||||
override def run() {
|
||||
while (mRun) {
|
||||
var c: Canvas = null
|
||||
try {
|
||||
c = mSurfaceHolder lockCanvas null
|
||||
mSurfaceHolder synchronized {
|
||||
if (mMode == State.RUNNING) updatePhysics()
|
||||
doDraw(c)
|
||||
}
|
||||
} finally {
|
||||
// do this in a finally so that if an exception is thrown
|
||||
// during the above, we don't leave the Surface in an
|
||||
// inconsistent state
|
||||
if (c != null) {
|
||||
mSurfaceHolder unlockCanvasAndPost c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Dump game state to the provided Bundle. Typically called when the
|
||||
* Activity is being suspended.
|
||||
*
|
||||
* @return Bundle with this view's state
|
||||
*/
|
||||
def saveState(map: Bundle): Bundle = {
|
||||
mSurfaceHolder synchronized {
|
||||
if (map != null) {
|
||||
map.putInt(KEY_DIFFICULTY, mDifficulty.id)
|
||||
map.putDouble(KEY_X, mX)
|
||||
map.putDouble(KEY_Y, mY)
|
||||
map.putDouble(KEY_DX, mDX)
|
||||
map.putDouble(KEY_DY, mDY)
|
||||
map.putDouble(KEY_HEADING, mHeading)
|
||||
map.putInt(KEY_LANDER_WIDTH, mLanderWidth)
|
||||
map.putInt(KEY_LANDER_HEIGHT, mLanderHeight)
|
||||
map.putInt(KEY_GOAL_X, mGoalX)
|
||||
map.putInt(KEY_GOAL_SPEED, mGoalSpeed)
|
||||
map.putInt(KEY_GOAL_ANGLE, mGoalAngle)
|
||||
map.putInt(KEY_GOAL_WIDTH, mGoalWidth)
|
||||
map.putInt(KEY_WINS, mWinsInARow)
|
||||
map.putDouble(KEY_FUEL, mFuel)
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
/** Sets the current difficulty.
|
||||
*
|
||||
* @param difficulty
|
||||
*/
|
||||
def setDifficulty(difficulty: Difficulty) {
|
||||
mSurfaceHolder synchronized {
|
||||
mDifficulty = difficulty
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets if the engine is currently firing.
|
||||
*/
|
||||
def setFiring(firing: Boolean) {
|
||||
mSurfaceHolder synchronized {
|
||||
mEngineFiring = firing
|
||||
}
|
||||
}
|
||||
|
||||
/** Used to signal the thread whether it should be running or not.
|
||||
* Passing true allows the thread to run; passing false will shut it
|
||||
* down if it's already running. Calling start() after this was most
|
||||
* recently called with false will result in an immediate shutdown.
|
||||
*
|
||||
* @param b true to run, false to shut down
|
||||
*/
|
||||
def setRunning(b: Boolean) {
|
||||
mRun = b
|
||||
}
|
||||
|
||||
/** Sets the game mode. That is, whether we are running, paused, in the
|
||||
* failure state, in the victory state, etc.
|
||||
*
|
||||
* @see #setState(State, CharSequence)
|
||||
* @param mode one of the State.* constants
|
||||
*/
|
||||
def setState(mode: State) {
|
||||
mSurfaceHolder synchronized {
|
||||
setState(mode, null)
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the game mode. That is, whether we are running, paused, in the
|
||||
* failure state, in the victory state, etc.
|
||||
*
|
||||
* @param mode one of the State.* constants
|
||||
* @param message string to add to screen or null
|
||||
*/
|
||||
def setState(mode: State, message: CharSequence) {
|
||||
/** This method optionally can cause a text message to be displayed
|
||||
* to the user when the mode changes. Since the View that actually
|
||||
* renders that text is part of the main View hierarchy and not
|
||||
* owned by this thread, we can't touch the state of that View.
|
||||
* Instead we use a Message + Handler to relay commands to the main
|
||||
* thread, which updates the user-text View.
|
||||
*/
|
||||
mSurfaceHolder synchronized {
|
||||
mMode = mode
|
||||
|
||||
if (mMode == State.RUNNING) {
|
||||
val msg = mHandler.obtainMessage()
|
||||
val b = new Bundle()
|
||||
b.putString("text", "")
|
||||
b.putInt("viz", View.INVISIBLE)
|
||||
msg setData b
|
||||
mHandler sendMessage msg
|
||||
} else {
|
||||
mRotating = 0
|
||||
mEngineFiring = false
|
||||
val res = mContext.getResources()
|
||||
val str: CharSequence = mMode match {
|
||||
case State.READY => res getText R.string.mode_ready
|
||||
case State.PAUSE => res getText R.string.mode_pause
|
||||
case State.LOSE => res getText R.string.mode_lose
|
||||
case State.WIN => (res getString R.string.mode_win_prefix) +
|
||||
mWinsInARow + " " +
|
||||
(res getString R.string.mode_win_suffix)
|
||||
case _ => ""
|
||||
}
|
||||
val text =
|
||||
(if (message != null) message + "\n" + str
|
||||
else str).toString
|
||||
|
||||
if (mMode == State.LOSE) mWinsInARow = 0
|
||||
|
||||
val msg = mHandler.obtainMessage()
|
||||
val b = new Bundle()
|
||||
b.putString("text", text)
|
||||
b.putInt("viz", View.VISIBLE)
|
||||
msg setData b
|
||||
mHandler sendMessage msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Callback invoked when the surface dimensions change. */
|
||||
def setSurfaceSize(width: Int, height: Int) {
|
||||
// synchronized to make sure these all change atomically
|
||||
mSurfaceHolder synchronized {
|
||||
mCanvasWidth = width
|
||||
mCanvasHeight = height
|
||||
|
||||
// don't forget to resize the background image
|
||||
mBackgroundImage =
|
||||
Bitmap.createScaledBitmap(mBackgroundImage, width, height, true)
|
||||
}
|
||||
}
|
||||
|
||||
/** Resumes from a pause. */
|
||||
def unpause() {
|
||||
// Move the real time clock up to now
|
||||
mSurfaceHolder synchronized {
|
||||
mLastTime = System.currentTimeMillis + 100
|
||||
}
|
||||
setState(State.RUNNING)
|
||||
}
|
||||
|
||||
/** Handles a key-down event.
|
||||
*
|
||||
* @param keyCode the key that was pressed
|
||||
* @param msg the original event object
|
||||
* @return true
|
||||
*/
|
||||
def doKeyDown(keyCode: Int, msg: KeyEvent): Boolean = {
|
||||
mSurfaceHolder synchronized {
|
||||
var okStart = false
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true
|
||||
if (keyCode == KeyEvent.KEYCODE_S) okStart = true
|
||||
|
||||
var center = keyCode == KeyEvent.KEYCODE_DPAD_UP
|
||||
|
||||
if (okStart
|
||||
&& (mMode == State.READY || mMode == State.LOSE || mMode == State.WIN)) {
|
||||
// ready-to-start -> start
|
||||
doStart()
|
||||
return true
|
||||
} else if (mMode == State.PAUSE && okStart) {
|
||||
// paused -> running
|
||||
unpause()
|
||||
return true
|
||||
} else if (mMode == State.RUNNING) {
|
||||
// center/space -> fire
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_SPACE) {
|
||||
setFiring(true)
|
||||
return true
|
||||
// left/q -> left
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
|
||||
|| keyCode == KeyEvent.KEYCODE_Q) {
|
||||
mRotating = -1
|
||||
return true
|
||||
// right/w -> right
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
|| keyCode == KeyEvent.KEYCODE_W) {
|
||||
mRotating = 1
|
||||
return true
|
||||
// up -> pause
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
|
||||
pause()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles a key-up event.
|
||||
*
|
||||
* @param keyCode the key that was pressed
|
||||
* @param msg the original event object
|
||||
* @return true if the key was handled and consumed, or else false
|
||||
*/
|
||||
def doKeyUp(keyCode: Int, msg: KeyEvent): Boolean = {
|
||||
var handled = false
|
||||
|
||||
mSurfaceHolder synchronized {
|
||||
if (mMode == State.RUNNING) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
|
||||
|| keyCode == KeyEvent.KEYCODE_SPACE) {
|
||||
setFiring(false)
|
||||
handled = true
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
|
||||
|| keyCode == KeyEvent.KEYCODE_Q
|
||||
|| keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
|
||||
|| keyCode == KeyEvent.KEYCODE_W) {
|
||||
mRotating = 0
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
handled
|
||||
}
|
||||
|
||||
/** Draws the ship, fuel/speed bars, and background to the provided
|
||||
* Canvas.
|
||||
*/
|
||||
private def doDraw(canvas: Canvas) {
|
||||
// Draw the background image. Operations on the Canvas accumulate
|
||||
// so this is like clearing the screen.
|
||||
canvas.drawBitmap(mBackgroundImage, 0, 0, null)
|
||||
|
||||
val yTop = mCanvasHeight - (mY + mLanderHeight / 2).toInt
|
||||
val xLeft = (mX - mLanderWidth / 2).toInt
|
||||
|
||||
// Draw the fuel gauge
|
||||
val fuelWidth = (UI_BAR * mFuel / PHYS_FUEL_MAX).toInt
|
||||
mScratchRect.set(4, 4, 4 + fuelWidth, 4 + UI_BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaint)
|
||||
|
||||
// Draw the speed gauge, with a two-tone effect
|
||||
val speed = Math.sqrt(mDX * mDX + mDY * mDY)
|
||||
val speedWidth = (UI_BAR * speed / PHYS_SPEED_MAX).toInt
|
||||
|
||||
if (speed <= mGoalSpeed) {
|
||||
mScratchRect.set(4 + UI_BAR + 4, 4,
|
||||
4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaint)
|
||||
} else {
|
||||
// Draw the bad color in back, with the good color in front of
|
||||
// it
|
||||
mScratchRect.set(4 + UI_BAR + 4, 4,
|
||||
4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaintBad);
|
||||
val goalWidth = (UI_BAR * mGoalSpeed / PHYS_SPEED_MAX)
|
||||
mScratchRect.set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + goalWidth,
|
||||
4 + UI_BAR_HEIGHT);
|
||||
canvas.drawRect(mScratchRect, mLinePaint)
|
||||
}
|
||||
|
||||
// Draw the landing pad
|
||||
canvas.drawLine(mGoalX, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,
|
||||
mGoalX + mGoalWidth, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,
|
||||
mLinePaint)
|
||||
|
||||
// Draw the ship with its current rotation
|
||||
canvas.save()
|
||||
canvas.rotate(mHeading.toFloat, mX.toFloat, mCanvasHeight - mY.toFloat)
|
||||
if (mMode == State.LOSE) {
|
||||
mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
|
||||
+ mLanderHeight)
|
||||
mCrashedImage.draw(canvas);
|
||||
} else if (mEngineFiring) {
|
||||
mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
|
||||
+ mLanderHeight);
|
||||
mFiringImage.draw(canvas);
|
||||
} else {
|
||||
mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
|
||||
+ mLanderHeight);
|
||||
mLanderImage.draw(canvas);
|
||||
}
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
/** Figures the lander state (x, y, fuel, ...) based on the passage of
|
||||
* realtime. Does not invalidate(). Called at the start of draw().
|
||||
* Detects the end-of-game and sets the UI to the next state.
|
||||
*/
|
||||
private def updatePhysics() {
|
||||
val now = System.currentTimeMillis
|
||||
|
||||
// Do nothing if mLastTime is in the future.
|
||||
// This allows the game-start to delay the start of the physics
|
||||
// by 100ms or whatever.
|
||||
if (mLastTime > now) return
|
||||
|
||||
val elapsed = (now - mLastTime) / 1000.0
|
||||
|
||||
// mRotating -- update heading
|
||||
if (mRotating != 0) {
|
||||
mHeading += mRotating * (PHYS_SLEW_SEC * elapsed)
|
||||
|
||||
// Bring things back into the range 0..360
|
||||
if (mHeading < 0) mHeading += 360
|
||||
else if (mHeading >= 360) mHeading -= 360
|
||||
}
|
||||
|
||||
// Base accelerations -- 0 for x, gravity for y
|
||||
var ddx = 0.0
|
||||
var ddy = -PHYS_DOWN_ACCEL_SEC * elapsed
|
||||
|
||||
if (mEngineFiring) {
|
||||
// taking 0 as up, 90 as to the right
|
||||
// cos(deg) is ddy component, sin(deg) is ddx component
|
||||
var elapsedFiring = elapsed
|
||||
var fuelUsed = elapsedFiring * PHYS_FUEL_SEC
|
||||
|
||||
// tricky case where we run out of fuel partway through the
|
||||
// elapsed
|
||||
if (fuelUsed > mFuel) {
|
||||
elapsedFiring = mFuel / fuelUsed * elapsed
|
||||
fuelUsed = mFuel
|
||||
|
||||
// Oddball case where we adjust the "control" from here
|
||||
mEngineFiring = false
|
||||
}
|
||||
|
||||
mFuel -= fuelUsed
|
||||
|
||||
// have this much acceleration from the engine
|
||||
val accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring
|
||||
|
||||
val radians = 2 * Math.Pi * mHeading / 360
|
||||
ddx = Math.sin(radians) * accel
|
||||
ddy += Math.cos(radians) * accel
|
||||
}
|
||||
|
||||
val dxOld = mDX
|
||||
val dyOld = mDY
|
||||
|
||||
// figure speeds for the end of the period
|
||||
mDX += ddx
|
||||
mDY += ddy
|
||||
|
||||
// figure position based on average speed during the period
|
||||
mX += elapsed * (mDX + dxOld) / 2
|
||||
mY += elapsed * (mDY + dyOld) / 2
|
||||
|
||||
mLastTime = now
|
||||
|
||||
// Evaluate if we have landed ... stop the game
|
||||
val yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2
|
||||
- TARGET_BOTTOM_PADDING;
|
||||
if (mY <= yLowerBound) {
|
||||
mY = yLowerBound
|
||||
|
||||
var result = State.LOSE
|
||||
var message: CharSequence = ""
|
||||
val res = mContext.getResources();
|
||||
val speed = Math.sqrt(mDX * mDX + mDY * mDY);
|
||||
val onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX
|
||||
+ mLanderWidth / 2 <= mGoalX + mGoalWidth)
|
||||
|
||||
// "Hyperspace" win -- upside down, going fast,
|
||||
// puts you back at the top.
|
||||
if (onGoal && Math.abs(mHeading - 180) < mGoalAngle
|
||||
&& speed > PHYS_SPEED_HYPERSPACE) {
|
||||
result = State.WIN
|
||||
mWinsInARow += 1
|
||||
doStart()
|
||||
|
||||
return
|
||||
// Oddball case: this case does a return, all other cases
|
||||
// fall through to setMode() below.
|
||||
} else if (!onGoal) {
|
||||
message = res getText R.string.message_off_pad
|
||||
} else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) {
|
||||
message = res getText R.string.message_bad_angle
|
||||
} else if (speed > mGoalSpeed) {
|
||||
message = res getText R.string.message_too_fast
|
||||
} else {
|
||||
result = State.WIN
|
||||
mWinsInARow += 1
|
||||
}
|
||||
setState(result, message)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
package com.example.android.lunarlander
|
||||
|
||||
import _root_.android.content.Context
|
||||
import _root_.android.os.{Bundle, Handler, Message}
|
||||
import _root_.android.util.AttributeSet;
|
||||
import _root_.android.view.{KeyEvent, SurfaceHolder, SurfaceView, View}
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
|
||||
/** View that draws, takes keystrokes, etc. for a simple LunarLander game.
|
||||
*
|
||||
* Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
|
||||
* current ship physics. All x/y etc. are measured with (0,0) at the lower left.
|
||||
* updatePhysics() advances the physics based on realtime. draw() renders the
|
||||
* ship, and does an invalidate() to prompt another draw() as soon as possible
|
||||
* by the system.
|
||||
*/
|
||||
class LunarView(context: Context, attrs: AttributeSet)
|
||||
extends SurfaceView(context, attrs) with SurfaceHolder.Callback {
|
||||
|
||||
/** Pointer to the text view to display "Paused.." etc. */
|
||||
private var mStatusText: TextView = null
|
||||
|
||||
/** The thread that actually draws the animation */
|
||||
private var thread: LunarThread = null
|
||||
|
||||
initLunarView()
|
||||
|
||||
private def initLunarView() {
|
||||
// register our interest in hearing about changes to our surface
|
||||
val holder = getHolder
|
||||
holder addCallback this
|
||||
|
||||
// create thread only; it's started in surfaceCreated()
|
||||
thread = new LunarThread(holder, context, new Handler() {
|
||||
override def handleMessage(m: Message) {
|
||||
mStatusText setVisibility m.getData().getInt("viz")
|
||||
mStatusText setText m.getData().getString("text")
|
||||
}
|
||||
})
|
||||
setFocusable(true) // make sure we get key events
|
||||
}
|
||||
|
||||
/** Fetches the animation thread corresponding to this LunarView.
|
||||
*
|
||||
* @return the animation thread
|
||||
*/
|
||||
def getThread: LunarThread = thread
|
||||
|
||||
/** Standard override to get key-press events.
|
||||
*/
|
||||
override def onKeyDown(keyCode: Int, msg: KeyEvent): Boolean =
|
||||
thread.doKeyDown(keyCode, msg)
|
||||
|
||||
/** Standard override for key-up. We actually care about these, so we can
|
||||
* turn off the engine or stop rotating.
|
||||
*/
|
||||
override def onKeyUp(keyCode: Int, msg: KeyEvent): Boolean =
|
||||
thread.doKeyUp(keyCode, msg)
|
||||
|
||||
/** Standard window-focus override. Notice focus lost so we can pause on
|
||||
* focus lost. e.g. user switches to take a call.
|
||||
*/
|
||||
override def onWindowFocusChanged(hasWindowFocus: Boolean) {
|
||||
if (!hasWindowFocus) thread.pause()
|
||||
}
|
||||
|
||||
/** Installs a pointer to the text view used for messages.
|
||||
*/
|
||||
def setTextView(textView: TextView) {
|
||||
mStatusText = textView
|
||||
}
|
||||
|
||||
/* Callback invoked when the surface dimensions change. */
|
||||
def surfaceChanged(holder: SurfaceHolder, format: Int,
|
||||
width: Int, height: Int) {
|
||||
thread.setSurfaceSize(width, height)
|
||||
}
|
||||
|
||||
/** Callback invoked when the Surface has been created and is ready to be
|
||||
* used.
|
||||
*/
|
||||
def surfaceCreated(holder: SurfaceHolder) {
|
||||
// start the thread here so that we don't busy-wait in run()
|
||||
// waiting for the surface to be created
|
||||
thread setRunning true
|
||||
thread.start()
|
||||
}
|
||||
|
||||
/** Callback invoked when the Surface has been destroyed and must no longer
|
||||
* be touched. WARNING: after this method returns, the Surface/Canvas must
|
||||
* never be touched again!
|
||||
*/
|
||||
def surfaceDestroyed(holder: SurfaceHolder) {
|
||||
// we have to tell thread to shut down & wait for it to finish, or else
|
||||
// it might touch the Surface after we return and explode
|
||||
var retry = true
|
||||
thread setRunning false
|
||||
while (retry) {
|
||||
try {
|
||||
thread.join()
|
||||
retry = false
|
||||
} catch {
|
||||
case e: InterruptedException =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.lunarlander
|
||||
|
||||
import _root_.android.app.Activity
|
||||
import _root_.android.os.Bundle
|
||||
import _root_.android.view.{Menu, Window}
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
/**
|
||||
* This is a simple LunarLander activity
|
||||
* that houses a single LunarView. It demonstrates...
|
||||
* <ul>
|
||||
* <li>animating by calling invalidate() from draw()
|
||||
* <li>loading and drawing resources
|
||||
* <li>handling onPause() in an animation
|
||||
* </ul>
|
||||
*/
|
||||
class LunarLander extends Activity {
|
||||
private var mLunarView: LunarView = _
|
||||
|
||||
override def onCreate(icicle: Bundle) {
|
||||
super.onCreate(icicle)
|
||||
|
||||
// Turn off the title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
// Make our view
|
||||
setContentView(R.layout.lunar_layout)
|
||||
mLunarView = findViewById(R.id.lunar).asInstanceOf[LunarView]
|
||||
|
||||
// Tell the view about the text view
|
||||
mLunarView setTextView findViewById(R.id.text).asInstanceOf[TextView]
|
||||
|
||||
if (icicle == null) {
|
||||
// We were just launched -- set up a new game
|
||||
mLunarView.mode = LunarView.READY
|
||||
} else {
|
||||
// We are being restored
|
||||
val map = icicle getBundle "lunar-view"
|
||||
if (map != null) mLunarView restoreState map
|
||||
else mLunarView.mode = LunarView.READY
|
||||
}
|
||||
}
|
||||
|
||||
override def onPause() {
|
||||
super.onPause()
|
||||
// Pause the came when our activity pauses
|
||||
mLunarView.doPause()
|
||||
}
|
||||
|
||||
override def onFreeze(outState: Bundle) {
|
||||
// Remember game state
|
||||
outState.putBundle("lunar-view", mLunarView.saveState())
|
||||
}
|
||||
|
||||
override def onCreateOptionsMenu(menu: Menu): Boolean = {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
def run(b: => Unit) = new Runnable { def run() { mLunarView.doStart() } }
|
||||
|
||||
menu.add(0, 0, R.string.menu_start, run { mLunarView.doStart() })
|
||||
menu.add(0, 0, R.string.menu_stop, run {
|
||||
mLunarView.setMode(LunarView.LOSE, LunarLander.this.getText(R.string.message_stopped))
|
||||
})
|
||||
menu.add(0, 0, R.string.menu_pause, run { mLunarView.doPause() })
|
||||
menu.add(0, 0, R.string.menu_resume, run { mLunarView.doResume() })
|
||||
menu.addSeparator(0, 0)
|
||||
menu.add(0, 0, R.string.menu_easy, run { mLunarView.difficulty = LunarView.EASY })
|
||||
menu.add(0, 0, R.string.menu_medium, run { mLunarView.difficulty = LunarView.MEDIUM })
|
||||
menu.add(0, 0, R.string.menu_hard, run { mLunarView.difficulty = LunarView.HARD })
|
||||
true
|
||||
}
|
||||
}
|
|
@ -1,647 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.lunarlander
|
||||
|
||||
import _root_.android.content.{Context, Resources}
|
||||
import _root_.android.graphics.{Canvas, Paint, RectF}
|
||||
import _root_.android.graphics.drawable.Drawable
|
||||
import _root_.android.os.Bundle
|
||||
import _root_.android.util.AttributeSet
|
||||
import _root_.android.view.{KeyEvent, View}
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
import java.util.Map
|
||||
|
||||
object LunarView {
|
||||
final val READY = 0
|
||||
final val RUNNING = 1
|
||||
final val PAUSE = 2
|
||||
final val LOSE = 3
|
||||
final val WIN = 4
|
||||
|
||||
final val EASY = 0
|
||||
final val MEDIUM = 1
|
||||
final val HARD = 2
|
||||
|
||||
// Parameters for how the physics works.
|
||||
final val FIRE_ACCEL_SEC = 80
|
||||
final val DOWN_ACCEL_SEC = 35
|
||||
final val FUEL_SEC = 10
|
||||
|
||||
final val SLEW_SEC = 120 // degrees/second rotate
|
||||
|
||||
final val FUEL_INIT = 60d
|
||||
final val FUEL_MAX = 100
|
||||
|
||||
final val SPEED_INIT = 30
|
||||
final val SPEED_MAX = 120
|
||||
final val SPEED_HYPERSPACE = 180
|
||||
|
||||
// Parameters for landing successfully (MEDIUM difficulty).
|
||||
final val TARGET_SPEED = 28
|
||||
final val TARGET_WIDTH = 1.6 // how much wider than lander
|
||||
final val TARGET_ANGLE = 18
|
||||
|
||||
/**
|
||||
* Pixel height of the fuel/speed bar.
|
||||
*/
|
||||
final val BAR_HEIGHT = 10
|
||||
|
||||
/**
|
||||
* Pixel width of the fuel/speed bar.
|
||||
*/
|
||||
final val BAR = 100
|
||||
|
||||
/**
|
||||
* Height of the landing pad off the bottom.
|
||||
*/
|
||||
final val PAD_HEIGHT = 8
|
||||
|
||||
/**
|
||||
* Extra pixels below the landing gear in the images
|
||||
*/
|
||||
final val BOTTOM_PADDING = 17
|
||||
}
|
||||
|
||||
/**
|
||||
* View that draws, takes keystrokes, etc. for a simple LunarLander game.
|
||||
*
|
||||
* Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
|
||||
* current ship physics. All x/y etc. are measured with (0,0) at the lower left.
|
||||
* updatePhysics() advances the physics based on realtime. draw() renders the
|
||||
* ship, and does an invalidate() to prompt another draw() as soon as possible
|
||||
* by the system.
|
||||
*/
|
||||
class LunarView(context: Context, attrs: AttributeSet, inflateParams: Map)
|
||||
extends View(context, attrs, inflateParams) {
|
||||
import LunarView._ // companion object
|
||||
|
||||
/**
|
||||
* The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN
|
||||
*/
|
||||
private var mMode = READY
|
||||
|
||||
/**
|
||||
* Current difficulty -- amount of fuel, allowed angle, etc.
|
||||
* Default is MEDIUM.
|
||||
*/
|
||||
private var mDifficulty = MEDIUM
|
||||
|
||||
/**
|
||||
* Velocity dx.
|
||||
*/
|
||||
private var mDX = 0.0
|
||||
|
||||
/**
|
||||
* Velocity dy.
|
||||
*/
|
||||
private var mDY = 0.0
|
||||
|
||||
/**
|
||||
* Lander heading in degrees, with 0 up, 90 right.
|
||||
* Kept in the range 0..360.
|
||||
*/
|
||||
private var mHeading = 0.0
|
||||
|
||||
/**
|
||||
* Currently rotating, -1 left, 0 none, 1 right.
|
||||
*/
|
||||
private var mRotating = 0
|
||||
|
||||
/**
|
||||
* X of the landing pad.
|
||||
*/
|
||||
private var mGoalX = 0
|
||||
|
||||
/**
|
||||
* Allowed speed.
|
||||
*/
|
||||
private var mGoalSpeed = 0
|
||||
|
||||
/**
|
||||
* Allowed angle.
|
||||
*/
|
||||
private var mGoalAngle = 0
|
||||
|
||||
/**
|
||||
* Width of the landing pad.
|
||||
*/
|
||||
private var mGoalWidth = 0
|
||||
|
||||
/**
|
||||
* Number of wins in a row.
|
||||
*/
|
||||
private var mWinsInARow = 0
|
||||
|
||||
/**
|
||||
* Fuel remaining
|
||||
*/
|
||||
private var mFuel = FUEL_INIT
|
||||
|
||||
/**
|
||||
* Is the engine burning?
|
||||
*/
|
||||
private var mEngineFiring = true
|
||||
|
||||
|
||||
/**
|
||||
* Used to figure out elapsed time between frames
|
||||
*/
|
||||
private var mLastTime: Long = _
|
||||
|
||||
/**
|
||||
* Paint to draw the lines on screen.
|
||||
*/
|
||||
private val mLinePaint = new Paint
|
||||
mLinePaint setAntiAlias true
|
||||
mLinePaint.setARGB(255, 0, 255, 0)
|
||||
|
||||
/**
|
||||
* "Bad" speed-too-high variant of the line color.
|
||||
*/
|
||||
private val mLinePaintBad = new Paint
|
||||
mLinePaintBad setAntiAlias true
|
||||
mLinePaintBad.setARGB(255, 120, 180, 0)
|
||||
|
||||
/**
|
||||
* What to draw for the Lander in its normal state
|
||||
*/
|
||||
private val mLanderImage =
|
||||
context.getResources getDrawable R.drawable.lander_plain
|
||||
|
||||
/**
|
||||
* What to draw for the Lander when the engine is firing
|
||||
*/
|
||||
private val mFiringImage =
|
||||
context.getResources getDrawable R.drawable.lander_firing
|
||||
|
||||
/**
|
||||
* What to draw for the Lander when it has crashed
|
||||
*/
|
||||
private val mCrashedImage =
|
||||
context.getResources getDrawable R.drawable.lander_crashed
|
||||
|
||||
/**
|
||||
* Pixel width of lander image.
|
||||
*/
|
||||
var mLanderWidth = mLanderImage.getIntrinsicWidth
|
||||
|
||||
/**
|
||||
* Pixel height of lander image.
|
||||
*/
|
||||
var mLanderHeight = mLanderImage.getIntrinsicHeight
|
||||
|
||||
/**
|
||||
* X of lander center.
|
||||
*/
|
||||
private var mX: Double = mLanderWidth
|
||||
|
||||
/**
|
||||
* Y of lander center.
|
||||
*/
|
||||
private var mY: Double = mLanderHeight * 2
|
||||
|
||||
/**
|
||||
* Pointer to the text view to display "Paused.." etc.
|
||||
*/
|
||||
private var mStatusText: TextView = _
|
||||
|
||||
/**
|
||||
* Scratch rect object.
|
||||
*/
|
||||
private val mScratchRect= new RectF(0, 0, 0, 0)
|
||||
|
||||
|
||||
setBackground(R.drawable.earthrise)
|
||||
// Make sure we get keys
|
||||
setFocusable(true)
|
||||
|
||||
/**
|
||||
* Save game state so that the user does not lose anything
|
||||
* if the game process is killed while we are in the
|
||||
* background.
|
||||
*
|
||||
* @return Map with this view's state
|
||||
*/
|
||||
def saveState(): Bundle = {
|
||||
val map = new Bundle
|
||||
|
||||
map.putInteger("mDifficulty", mDifficulty)
|
||||
map.putDouble("mX", mX)
|
||||
map.putDouble("mY", mY)
|
||||
map.putDouble("mDX", mDX)
|
||||
map.putDouble("mDY", mDY)
|
||||
map.putDouble("mHeading", mHeading)
|
||||
map.putInteger("mLanderWidth", mLanderWidth)
|
||||
map.putInteger("mLanderHeight", mLanderHeight)
|
||||
map.putInteger("mGoalX", mGoalX)
|
||||
map.putInteger("mGoalSpeed", mGoalSpeed)
|
||||
map.putInteger("mGoalAngle", mGoalAngle)
|
||||
map.putInteger("mGoalWidth", mGoalWidth)
|
||||
map.putInteger("mWinsInARow", mWinsInARow)
|
||||
map.putDouble("mFuel", mFuel)
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore game state if our process is being relaunched
|
||||
*
|
||||
* @param icicle Map containing the game state
|
||||
*/
|
||||
def restoreState(icicle: Bundle) {
|
||||
mode = PAUSE
|
||||
mRotating = 0
|
||||
mEngineFiring = false
|
||||
|
||||
mDifficulty = icicle.getInteger("mDifficulty").intValue
|
||||
mX = icicle.getDouble("mX").doubleValue
|
||||
mY = icicle.getDouble("mY").doubleValue
|
||||
mDX = icicle.getDouble("mDX").doubleValue
|
||||
mDY = icicle.getDouble("mDY").doubleValue
|
||||
mHeading = icicle.getDouble("mHeading").doubleValue
|
||||
|
||||
mLanderWidth = icicle.getInteger("mLanderWidth").intValue
|
||||
mLanderHeight = icicle.getInteger("mLanderHeight").intValue
|
||||
mGoalX = icicle.getInteger("mGoalX").intValue
|
||||
mGoalSpeed = icicle.getInteger("mGoalSpeed").intValue
|
||||
mGoalAngle = icicle.getInteger("mGoalAngle").intValue
|
||||
mGoalWidth = icicle.getInteger("mGoalWidth").intValue
|
||||
mWinsInARow = icicle.getInteger("mWinsInARow").intValue
|
||||
mFuel = icicle.getDouble("mFuel").doubleValue
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a pointer to the text view used
|
||||
* for messages.
|
||||
*/
|
||||
def setTextView(textView: TextView) { mStatusText = textView }
|
||||
|
||||
/**
|
||||
* Standard window-focus override.
|
||||
* Notice focus lost so we can pause on focus lost.
|
||||
* e.g. user switches to take a call.
|
||||
*/
|
||||
override def windowFocusChanged(hasWindowFocus: Boolean) {
|
||||
if (!hasWindowFocus) doPause()
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard override of View.draw.
|
||||
* Draws the ship and fuel/speed bars.
|
||||
*/
|
||||
override def onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
if (mMode == RUNNING) updatePhysics()
|
||||
|
||||
val screenWidth = getWidth
|
||||
val screenHeight = getHeight
|
||||
|
||||
val yTop = screenHeight - (mY.toInt + mLanderHeight/2)
|
||||
val xLeft = mX.toInt - mLanderWidth/2
|
||||
|
||||
// Draw fuel rect
|
||||
val fuelWidth = (BAR * mFuel / FUEL_MAX).toInt
|
||||
mScratchRect.set(4, 4, 4 + fuelWidth, 4 + BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaint)
|
||||
|
||||
val speed = Math.sqrt(mDX*mDX + mDY*mDY)
|
||||
val speedWidth = (BAR * speed / SPEED_MAX).toInt
|
||||
|
||||
if (speed <= mGoalSpeed) {
|
||||
mScratchRect.set(4 + BAR + 4, 4, 4 + BAR + 4 + speedWidth, 4 + BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaint)
|
||||
} else {
|
||||
// Draw the bad color in back, with the good color in front of it
|
||||
mScratchRect.set(4 + BAR + 4, 4, 4 + BAR + 4 + speedWidth, 4 + BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaintBad)
|
||||
val goalWidth = (BAR * mGoalSpeed / SPEED_MAX)
|
||||
mScratchRect.set(4 + BAR + 4, 4, 4 + BAR + 4 + goalWidth, 4 + BAR_HEIGHT)
|
||||
canvas.drawRect(mScratchRect, mLinePaint)
|
||||
}
|
||||
|
||||
// Draw the landing pad
|
||||
canvas.drawLine(mGoalX, 1 + screenHeight - PAD_HEIGHT,
|
||||
mGoalX + mGoalWidth, 1 + screenHeight - PAD_HEIGHT, mLinePaint)
|
||||
|
||||
|
||||
// Draw the ship with its current rotation
|
||||
canvas.save()
|
||||
canvas.rotate(mHeading.toFloat, mX.toFloat, screenHeight - mY.toFloat)
|
||||
|
||||
if (mMode == LOSE) {
|
||||
mCrashedImage.setBounds(xLeft, yTop, xLeft+mLanderWidth, yTop+mLanderHeight)
|
||||
mCrashedImage.draw(canvas)
|
||||
} else if (mEngineFiring) {
|
||||
mFiringImage.setBounds(xLeft, yTop, xLeft+mLanderWidth, yTop+mLanderHeight)
|
||||
mFiringImage.draw(canvas)
|
||||
} else {
|
||||
mLanderImage.setBounds(xLeft, yTop, xLeft+mLanderWidth, yTop+mLanderHeight)
|
||||
mLanderImage.draw(canvas)
|
||||
}
|
||||
|
||||
/*
|
||||
* Our animation strategy is that each draw() does an invalidate(),
|
||||
* so we get a series of draws. This is a known animation strategy
|
||||
* within Android, and the system throttles the draws down to match
|
||||
* the refresh rate.
|
||||
*/
|
||||
|
||||
if (mMode == RUNNING) {
|
||||
// Invalidate a space around the current lander + the bars at the top.
|
||||
// Note: invalidating a relatively small part of the screen to draw
|
||||
// is a good optimization. In this case, the bars and the ship
|
||||
// may be far apart, limiting the value of the optimization.
|
||||
invalidate(xLeft-20, yTop-20, xLeft+mLanderWidth+20, yTop+mLanderHeight+20)
|
||||
invalidate(0, 0, screenWidth, 4 + BAR_HEIGHT)
|
||||
}
|
||||
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures the lander state (x, y, fuel, ...) based on the passage of
|
||||
* realtime. Does not invalidate(). Called at the start
|
||||
* of draw(). Detects the end-of-game and sets the UI to the next state.
|
||||
*/
|
||||
def updatePhysics() {
|
||||
val now = System.currentTimeMillis
|
||||
|
||||
// Do nothing if mLastTime is in the future.
|
||||
// This allows the game-start to delay the start of the physics
|
||||
// by 100ms or whatever.
|
||||
if (mLastTime > now) return
|
||||
|
||||
val elapsed = (now - mLastTime) / 1000.0
|
||||
|
||||
// mRotating -- update heading
|
||||
if (mRotating != 0) {
|
||||
mHeading += mRotating * (SLEW_SEC * elapsed)
|
||||
|
||||
// Bring things back into the range 0..360
|
||||
if (mHeading < 0) mHeading += 360
|
||||
else if (mHeading >= 360) mHeading -= 360
|
||||
}
|
||||
|
||||
// Base accelerations -- 0 for x, gravity for y
|
||||
var ddx = 0.0
|
||||
var ddy = -DOWN_ACCEL_SEC * elapsed
|
||||
|
||||
if (mEngineFiring) {
|
||||
// taking 0 as up, 90 as to the right
|
||||
// cos(deg) is ddy component, sin(deg) is ddx component
|
||||
var elapsedFiring = elapsed
|
||||
var fuelUsed = elapsedFiring * FUEL_SEC
|
||||
|
||||
// tricky case where we run out of fuel partway through the elapsed
|
||||
if (fuelUsed > mFuel) {
|
||||
elapsedFiring = mFuel / fuelUsed * elapsed
|
||||
fuelUsed = mFuel
|
||||
|
||||
// Oddball case where we adjust the "control" from here
|
||||
mEngineFiring = false
|
||||
}
|
||||
|
||||
mFuel -= fuelUsed
|
||||
|
||||
// have this much acceleration from the engine
|
||||
val accel = FIRE_ACCEL_SEC * elapsedFiring
|
||||
|
||||
val radians = 2 * Math.Pi * mHeading / 360
|
||||
ddx = Math.sin(radians) * accel
|
||||
ddy += Math.cos(radians) * accel
|
||||
}
|
||||
|
||||
val dxOld = mDX
|
||||
val dyOld = mDY
|
||||
|
||||
// figure speeds for the end of the period
|
||||
mDX += ddx
|
||||
mDY += ddy
|
||||
|
||||
// figure position based on average speed during the period
|
||||
mX += elapsed * (mDX + dxOld)/2
|
||||
mY += elapsed * (mDY + dyOld)/2
|
||||
|
||||
mLastTime = now
|
||||
|
||||
checkLanding()
|
||||
}
|
||||
|
||||
def checkLanding() {
|
||||
val yLowerBound = PAD_HEIGHT + mLanderHeight/2 - BOTTOM_PADDING
|
||||
if (mY <= yLowerBound) {
|
||||
mY = yLowerBound
|
||||
|
||||
val res = getContext.getResources
|
||||
val speed = Math.sqrt(mDX*mDX + mDY*mDY)
|
||||
val onGoal = (mGoalX <= mX - mLanderWidth/2 &&
|
||||
mX + mLanderWidth/2 <= mGoalX + mGoalWidth)
|
||||
|
||||
// "Hyperspace" win -- upside down, going fast,
|
||||
// puts you back at the top.
|
||||
if (onGoal && Math.abs(mHeading - 180) < mGoalAngle &&
|
||||
speed > SPEED_HYPERSPACE) {
|
||||
mWinsInARow += 1
|
||||
doStart()
|
||||
} else {
|
||||
val (result, message) =
|
||||
if (!onGoal)
|
||||
(LOSE, res.getText(R.string.message_off_pad))
|
||||
else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle))
|
||||
(LOSE, res.getText(R.string.message_bad_angle))
|
||||
else if (speed > mGoalSpeed)
|
||||
(LOSE, res.getText(R.string.message_too_fast))
|
||||
else {
|
||||
mWinsInARow += 1
|
||||
(WIN, "")
|
||||
}
|
||||
setMode(result, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the engine is currently firing.
|
||||
*/
|
||||
def isFiring_=(firing: Boolean) { mEngineFiring = firing }
|
||||
def isFiring = mEngineFiring
|
||||
|
||||
/**
|
||||
* Sets the game mode, RUNNING, PAUSED, etc.
|
||||
* @param mode RUNNING, PAUSED, ...
|
||||
*/
|
||||
def mode_=(mode: Int) { setMode(mode, null) }
|
||||
def mode = mMode
|
||||
|
||||
/**
|
||||
* Sets the game mode, RUNNING, PAUSED, etc.
|
||||
* @param mode RUNNING, PAUSED, ...
|
||||
* @param message string to add to screen or null
|
||||
*/
|
||||
def setMode(mode: Int, message: CharSequence) {
|
||||
mMode = mode
|
||||
invalidate()
|
||||
|
||||
if (mMode == RUNNING) {
|
||||
mStatusText.setVisibility(View.INVISIBLE)
|
||||
} else {
|
||||
mRotating = 0
|
||||
mEngineFiring = false
|
||||
val res = getContext.getResources
|
||||
var str = mMode match {
|
||||
case READY => res.getText(R.string.mode_ready)
|
||||
case PAUSE => res.getText(R.string.mode_pause)
|
||||
case LOSE => mWinsInARow = 0; res.getText(R.string.mode_lose)
|
||||
case WIN => res.getString(R.string.mode_win_prefix)
|
||||
+ mWinsInARow + " " + res.getString(R.string.mode_win_suffix)
|
||||
case _ => ""
|
||||
}
|
||||
mStatusText setText (if (message != null) message + "\n" + str else str)
|
||||
mStatusText setVisibility View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the game, setter parameters for the current
|
||||
* difficulty.
|
||||
*/
|
||||
def doStart() {
|
||||
// First set the game for Medium difficulty
|
||||
mFuel = FUEL_INIT
|
||||
mEngineFiring = false
|
||||
mGoalWidth = (mLanderWidth * TARGET_WIDTH).toInt
|
||||
mGoalSpeed = TARGET_SPEED
|
||||
mGoalAngle = TARGET_ANGLE
|
||||
var speedInit = SPEED_INIT
|
||||
|
||||
// Adjust difficulty params for EASY/HARD
|
||||
if (mDifficulty == EASY) {
|
||||
mFuel = mFuel * 3 / 2
|
||||
mGoalWidth = mGoalWidth * 4 / 3
|
||||
mGoalSpeed = mGoalSpeed * 3 / 2
|
||||
mGoalAngle = mGoalAngle * 4 / 3
|
||||
speedInit = speedInit * 3 / 4
|
||||
} else if (mDifficulty == HARD) {
|
||||
mFuel = mFuel * 7 / 8
|
||||
mGoalWidth = mGoalWidth * 3 / 4
|
||||
mGoalSpeed = mGoalSpeed * 7 / 8
|
||||
speedInit = speedInit * 4 / 3
|
||||
}
|
||||
|
||||
mX = getWidth/2
|
||||
mY = getHeight - mLanderHeight/2
|
||||
|
||||
// start with a little random motion
|
||||
mDY = Math.random * -speedInit
|
||||
mDX = Math.random * 2*speedInit - speedInit
|
||||
|
||||
mHeading = 0
|
||||
|
||||
// Figure initial spot for landing, not too near center
|
||||
do {
|
||||
mGoalX = (Math.random * (getWidth - mGoalWidth)).toInt
|
||||
} while (Math.abs(mGoalX - (mX - mLanderWidth/2)) <= getWidth/6)
|
||||
|
||||
mLastTime = System.currentTimeMillis + 100
|
||||
mode = RUNNING
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes from a pause.
|
||||
*/
|
||||
def doResume() {
|
||||
// Move the real time clock up to now
|
||||
mLastTime = System.currentTimeMillis + 100
|
||||
mode = RUNNING
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses from the running state.
|
||||
*/
|
||||
def doPause() { if (mMode == RUNNING) mode = PAUSE }
|
||||
|
||||
|
||||
/**
|
||||
* Standard override to get key events.
|
||||
*/
|
||||
override def onKeyDown(keyCode: Int, msg: KeyEvent): Boolean = {
|
||||
var handled = false
|
||||
|
||||
val okStart = keyCode == KeyEvent.KEYCODE_DPAD_UP ||
|
||||
keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
|
||||
keyCode == KeyEvent.KEYCODE_S
|
||||
|
||||
val center = keyCode == KeyEvent.KEYCODE_DPAD_UP
|
||||
|
||||
// ready-to-start -> start
|
||||
if (okStart && (mMode == READY || mMode == LOSE || mMode == WIN)) {
|
||||
doStart()
|
||||
handled = true
|
||||
}
|
||||
// paused -> running
|
||||
else if (mMode == PAUSE && okStart) {
|
||||
doResume()
|
||||
handled = true
|
||||
} else if (mMode == RUNNING) {
|
||||
// center/space -> fire
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER ||
|
||||
keyCode == KeyEvent.KEYCODE_SPACE) {
|
||||
isFiring = true
|
||||
handled = true
|
||||
// left/q -> left
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
|
||||
keyCode == KeyEvent.KEYCODE_Q) {
|
||||
mRotating = -1
|
||||
handled = true
|
||||
// right/w -> right
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
|
||||
keyCode == KeyEvent.KEYCODE_W) {
|
||||
mRotating = 1
|
||||
handled = true
|
||||
// up -> pause
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
|
||||
doPause()
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
handled
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard override for key-up. We actually care about these,
|
||||
* so we can turn off the engine or stop rotating.
|
||||
*/
|
||||
override def onKeyUp(keyCode: Int, msg: KeyEvent): Boolean =
|
||||
if (mMode == RUNNING) {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER ||
|
||||
keyCode == KeyEvent.KEYCODE_SPACE) {
|
||||
isFiring = false
|
||||
true
|
||||
} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
|
||||
keyCode == KeyEvent.KEYCODE_Q ||
|
||||
keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
|
||||
keyCode == KeyEvent.KEYCODE_W) {
|
||||
mRotating = 0
|
||||
true
|
||||
} else false
|
||||
} else false
|
||||
|
||||
def difficulty_=(d: Int) { mDifficulty = d }
|
||||
def difficulty = mDifficulty
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2008 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.lunarlander.tests">
|
||||
|
||||
<!-- We add an application tag here just so that we can indicate that
|
||||
this package needs to link against the android.test library,
|
||||
which is needed when building test cases. -->
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="com.example.android.lunarlander"
|
||||
android:label="LunarLander sample tests">
|
||||
</instrumentation>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.example.android.lunarlander;
|
||||
|
||||
import android.test.ActivityInstrumentationTestCase;
|
||||
|
||||
import com.example.android.lunarlander.LunarLander;
|
||||
|
||||
/**
|
||||
* Make sure that the main launcher activity opens up properly, which will be
|
||||
* verified by {@link ActivityTestCase#testActivityTestCaseSetUpProperly}.
|
||||
*/
|
||||
public class LunarLanderTest extends ActivityInstrumentationTestCase<LunarLander> {
|
||||
|
||||
public LunarLanderTest() {
|
||||
super("com.example.android.lunarlander", LunarLander.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.snake">
|
||||
<application>
|
||||
<activity class="Snake" android:label="Snake on a Phone">
|
||||
package="com.example.android.snake"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
<application android:label="@string/app_name">
|
||||
<activity android:name=".Snake"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="keyboardHidden|orientation">
|
||||
<intent-filter>
|
||||
<action android:value="android.intent.action.MAIN" />
|
||||
<category android:value="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="imported" default="help">
|
||||
|
||||
<property environment="env"/>
|
||||
|
||||
<!-- Android SDK -->
|
||||
<condition property="_dx" value="dx.bat" else="dx">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<condition property="_apkbuilder" value="apkbuilder.bat" else="apkbuilder">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
<property name="android.jar" value="${sdk-location}/platforms/android-1.5/android.jar"/>
|
||||
<property name="maps.jar" value="${sdk-location}/add-ons/maps.jar"/>
|
||||
<property name="dx.cmd" value="${sdk-location}/platforms/android-1.5/tools/${_dx}"/>
|
||||
<property name="aapt.cmd" value="${sdk-location}/platforms/android-1.5/tools/aapt"/>
|
||||
<property name="apkbuilder.cmd" value="${sdk-location}/tools/${_apkbuilder}"/>
|
||||
<property name="adb.cmd" value="${sdk-location}/tools/adb"/>
|
||||
|
||||
<!-- Scala SDK -->
|
||||
<property name="scala-compiler.jar" value="${env.SCALA_HOME}/lib/scala-compiler.jar"/>
|
||||
<property name="scala-library.jar" value="${env.SCALA_HOME}/lib/scala-library.jar"/>
|
||||
|
||||
<!-- Android build files -->
|
||||
<property name="manifest.xml" value="${basedir}/AndroidManifest.xml"/>
|
||||
<xmlproperty file="${manifest.xml}" collapseAttributes="true"/>
|
||||
<property name="classes.dex" value="${basedir}/bin/classes.dex"/>
|
||||
<property name="empty.apk" value="${basedir}/bin/${ant.project.name}.ap_"/>
|
||||
<property name="basename.apk" value="${ant.project.name}-debug.apk"/>
|
||||
<property name="package.apk" value="${basedir}/bin/${basename.apk}"/>
|
||||
|
||||
<taskdef name="apkbuilder"
|
||||
classname="com.android.ant.ApkBuilderTask"
|
||||
classpathref="android.antlibs"
|
||||
/>
|
||||
<path id="scala.path">
|
||||
<pathelement path="${scala-compiler.jar}"/>
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
</path>
|
||||
<taskdef
|
||||
resource="scala/tools/ant/antlib.xml"
|
||||
classpathref="scala.path"
|
||||
/>
|
||||
|
||||
<uptodate property="gen.uptodate" targetfile="${basedir}/gen/.gen-complete">
|
||||
<srcfiles dir="${basedir}/gen" includes="**/*.java"/>
|
||||
</uptodate>
|
||||
<uptodate property="compile.uptodate" targetfile="${basedir}/bin/.compile-complete">
|
||||
<srcfiles dir="${basedir}/gen" includes="**/*.java"/>
|
||||
</uptodate>
|
||||
<uptodate property="dex.uptodate" targetfile="${classes.dex}">
|
||||
<srcfiles dir="${basedir}/bin/classes" includes="**/*.class"/>
|
||||
</uptodate>
|
||||
<uptodate property="pkg.uptodate" targetfile="${empty.apk}">
|
||||
<srcfiles dir="${basedir}/bin" includes="**/*.dex,**/*.xml"/>
|
||||
</uptodate>
|
||||
<uptodate property="apk.uptodate" targetfile="${package.apk}">
|
||||
<srcfiles dir="${basedir}/bin" includes="**/*.dex,**/*.xml"/>
|
||||
</uptodate>
|
||||
|
||||
<target name="resource-src" unless="gen.uptodate">
|
||||
<mkdir dir="${basedir}/gen"/>
|
||||
<echo message="Generating R.java / Manifest.java from the resources..."/>
|
||||
<exec executable="${aapt.cmd}">
|
||||
<arg line="package -m -J '${basedir}/gen' -M '${manifest.xml}' -S '${basedir}/res' -I '${android.jar}'"/>
|
||||
</exec>
|
||||
<touch file="${basedir}/gen/.gen-complete" verbose="no"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="resource-src" unless="compile.uptodate">
|
||||
<path id="boot.path">
|
||||
<pathelement location="${android.jar}"/>
|
||||
<pathelement location="${maps.jar}"/>
|
||||
</path>
|
||||
<mkdir dir="${basedir}/bin/classes"/>
|
||||
<javac
|
||||
srcdir="${basedir}/src${path.separator}${basedir}/gen" includes="**/*.java"
|
||||
destdir="${basedir}/bin/classes" bootclasspathref="boot.path"
|
||||
target="1.5" source="1.5" encoding="ascii">
|
||||
<classpath>
|
||||
<pathelement location="${android.jar}"/>
|
||||
</classpath>
|
||||
</javac>
|
||||
<scalac
|
||||
srcdir="${basedir}/src" includes="**/*.scala"
|
||||
destdir="${basedir}/bin/classes">
|
||||
<classpath>
|
||||
<pathelement location="${scala-library.jar}"/>
|
||||
<pathelement location="${android.jar}"/>
|
||||
<pathelement location="${basedir}/bin/classes"/>
|
||||
</classpath>
|
||||
</scalac>
|
||||
<touch file="${basedir}/bin/.compile-complete" verbose="no"/>
|
||||
</target>
|
||||
|
||||
<target name="dex" depends="compile" unless="dex.uptodate">
|
||||
<echo message="Converting compiled files and external libraries into bin/classes.dex..."/>
|
||||
<!-- IMPORTANT ! -->
|
||||
<!-- in shell script /opt/android/platforms/android-1.5/tools/dx -->
|
||||
<!-- add the following code on line 60: javaOpts="-Xmx768M" -->
|
||||
<exec executable="${dx.cmd}">
|
||||
<arg line="--dex --no-optimize --output='${classes.dex}' '${basedir}/bin/classes' '${scala-library.jar}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="package-resources" depends="dex" unless="pkg.uptodate">
|
||||
<echo message="Packaging resources"/>
|
||||
<exec executable="${aapt.cmd}">
|
||||
<arg line="package -f -M '${manifest.xml}' -S '${basedir}/res' -I '${android.jar}' -F '${empty.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="debug" depends="package-resources" unless="apk.uptodate">
|
||||
<apkbuilder
|
||||
basename="${ant.project.name}" outfolder="${basedir}/bin"
|
||||
signed="true" verbose="true"
|
||||
/>
|
||||
<exec executable="${apkbuilder.cmd}">
|
||||
<arg line="'${package.apk}' -v -z '${empty.apk}' -f '${classes.dex}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="release">
|
||||
<echo message="Sorry. Not yet implemented"/>
|
||||
</target>
|
||||
|
||||
<target name="install" depends="debug">
|
||||
<echo message="Installing bin/${basename.apk} onto default emulator..."/>
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="install '${package.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="reinstall" depends="debug">
|
||||
<echo message="Reinstalling bin/${basename.apk} onto default emulator..."/>
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="install -r '${package.apk}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="uninstall">
|
||||
<exec executable="${adb.cmd}">
|
||||
<arg line="uninstall '${manifest.package}'"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="help">
|
||||
<echo message="Android Ant Build. Available targets:"/>
|
||||
<echo message=" help: Display this help."/>
|
||||
<echo message=" debug: Builds the application and sign it with a debug key."/>
|
||||
<echo message=" install: Install the debug package onto a running emulator or device."/>
|
||||
<echo message=" reinstall: Reinstall the debug package onto a running emulator or device."/>
|
||||
<echo message=" uninstall: Uninstall the application from a running emulator or device."/>
|
||||
<echo message=" clean: Clean up build files from project."/>
|
||||
</target>
|
||||
|
||||
<macrodef name="remove">
|
||||
<attribute name="dir"/>
|
||||
<sequential>
|
||||
<delete dir="@{dir}" includeemptydirs="yes" quiet="yes" failonerror="no"/>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<target name="clean" description="clean up">
|
||||
<echo message="Cleaning project '${ant.project.name}'..."/>
|
||||
<remove dir="${basedir}/bin"/>
|
||||
<remove dir="${basedir}/gen"/>
|
||||
</target>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,15 @@
|
|||
# This file is used to override default values used by the Ant build system.
|
||||
#
|
||||
# This file must be checked in Version Control Systems, as it is
|
||||
# integral to the build system of your project.
|
||||
|
||||
# The name of your application package as defined in the manifest.
|
||||
# Used by the 'uninstall' rule.
|
||||
#application-package=com.example.myproject
|
||||
|
||||
# The name of the source folder.
|
||||
#source-folder=src
|
||||
|
||||
# The name of the output folder.
|
||||
#out-folder=bin
|
||||
|
|
@ -1,222 +1,64 @@
|
|||
<?xml version="1.0" ?>
|
||||
<project name="Snake" default="package">
|
||||
<property name="sdk-folder" value="/home/linuxsoft/apps/android-m3-rc22a" />
|
||||
<property name="android-tools" value="/home/linuxsoft/apps/android-m3-rc22a/tools" />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="Snake" default="help">
|
||||
|
||||
<!-- The intermediates directory -->
|
||||
<!-- Eclipse uses "bin" for its own output, so we do the same. -->
|
||||
<property name="outdir" value="bin" />
|
||||
<!-- The local.properties file is created and updated by the 'android' tool.
|
||||
It contain the path to the SDK. It should *NOT* be checked in in Version
|
||||
Control Systems. -->
|
||||
<property file="local.properties"/>
|
||||
|
||||
<!-- No user servicable parts below. -->
|
||||
<!-- The build.properties file can be created by you and is never touched
|
||||
by the 'android' tool. This is the place to change some of the default property values
|
||||
used by the Ant rules.
|
||||
Here are some properties you may want to change/update:
|
||||
|
||||
<!-- Input directories -->
|
||||
<property name="resource-dir" value="res" />
|
||||
<property name="asset-dir" value="assets" />
|
||||
<property name="srcdir" value="src" />
|
||||
application-package
|
||||
the name of your application package as defined in the manifest. Used by the
|
||||
'uninstall' rule.
|
||||
source-folder
|
||||
the name of the source folder. Default is 'src'.
|
||||
out-folder
|
||||
the name of the output folder. Default is 'bin'.
|
||||
|
||||
<!-- Output directories -->
|
||||
<property name="outdir-classes" value="${outdir}/classes" />
|
||||
Properties related to the SDK location or the project target should be updated
|
||||
using the 'android' tool with the 'update' action.
|
||||
|
||||
<!-- Create R.java in the source directory -->
|
||||
<property name="outdir-r" value="src" />
|
||||
This file is an integral part of the build system for your application and
|
||||
should be checked in in Version Control Systems.
|
||||
|
||||
<!-- Intermediate files -->
|
||||
<property name="dex-file" value="classes.dex" />
|
||||
<property name="intermediate-dex" value="${outdir}/${dex-file}" />
|
||||
-->
|
||||
<property file="build.properties"/>
|
||||
|
||||
<!-- The final package file to generate -->
|
||||
<property name="out-package" value="${outdir}/${ant.project.name}.apk" />
|
||||
<!-- The default.properties file is created and updated by the 'android' tool, as well
|
||||
as ADT.
|
||||
This file is an integral part of the build system for your application and
|
||||
should be checked in in Version Control Systems. -->
|
||||
<property file="default.properties"/>
|
||||
|
||||
<!-- Tools -->
|
||||
<property name="aapt" value="${android-tools}/aapt" />
|
||||
<property name="aidl" value="${android-tools}/aidl" />
|
||||
<property name="dx" value="${android-tools}/dx" />
|
||||
<property name="zip" value="zip" />
|
||||
<property name="android-jar" value="${sdk-folder}/android.jar" />
|
||||
|
||||
<!-- Scala -->
|
||||
<property environment="env"/>
|
||||
<property name="scala.dir" value="${env.SCALA_HOME}"/>
|
||||
<property name="scala-compiler.jar" value="${scala.dir}/lib/scala-compiler.jar"/>
|
||||
<property name="scala-library.jar" value="${scala.dir}/lib/scala-library.jar"/>
|
||||
<property name="scala-android.jar" value="${scala.dir}/lib/scala-android.jar"/>
|
||||
<fail message="Missing library scala-android.jar (use sbaz to install it)">
|
||||
<condition><not><available file="${scala-android.jar}"/></not></condition>
|
||||
</fail>
|
||||
<property name="scala-depend.jar" value="${android-tools}/lib/scala-depend.jar"/>
|
||||
<path id="scala.path">
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
<pathelement path="${scala-compiler.jar}"/>
|
||||
<!-- Custom Android task to deal with the project target, and import the proper rules.
|
||||
This requires ant 1.6.0 or above. -->
|
||||
<path id="android.antlibs">
|
||||
<pathelement path="${sdk-location}/tools/lib/anttasks.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/sdklib.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/androidprefs.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/apkbuilder.jar" />
|
||||
<pathelement path="${sdk-location}/tools/lib/jarutils.jar" />
|
||||
</path>
|
||||
<path id="scalac.path">
|
||||
<pathelement path="${android-jar}"/>
|
||||
<pathelement path="${scala-library.jar}"/>
|
||||
</path>
|
||||
<taskdef
|
||||
resource="scala/tools/ant/antlib.xml"
|
||||
classpathref="scala.path"
|
||||
/>
|
||||
<macrodef name="smartjar">
|
||||
<attribute name="srcdir"/>
|
||||
<attribute name="basedir"/>
|
||||
<attribute name="classname"/>
|
||||
<attribute name="destfile"/>
|
||||
<sequential>
|
||||
<depend
|
||||
srcdir="@{srcdir}"
|
||||
destdir="@{basedir}" closure="true"
|
||||
cache="@{basedir}"
|
||||
classpath="${android-jar}"
|
||||
/>
|
||||
<java
|
||||
classname="ch.epfl.lamp.util.depend" output="@{basedir}/classes.dep"
|
||||
classpath="${scala-library.jar}${path.separator}${scala-depend.jar}">
|
||||
<arg line="@{basedir}${file.separator}dependencies.txt"/>
|
||||
<arg line="@{classname}"/>
|
||||
</java>
|
||||
<jar
|
||||
destfile="@{destfile}"
|
||||
basedir="@{basedir}"
|
||||
includesfile="@{basedir}/classes.dep"
|
||||
/>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<!-- Rules -->
|
||||
<taskdef name="setup"
|
||||
classname="com.android.ant.SetupTask"
|
||||
classpathref="android.antlibs"/>
|
||||
|
||||
<!-- Create the output directories if they don't exist yet. -->
|
||||
<target name="dirs">
|
||||
<mkdir dir="${outdir}" />
|
||||
<mkdir dir="${outdir-classes}" />
|
||||
</target>
|
||||
<!-- Execute the Android Setup task that will setup some properties specific to the target,
|
||||
and import the rules files.
|
||||
To customize the rules, copy/paste them below the task, and disable import by setting
|
||||
the import attribute to false:
|
||||
<setup import="false" />
|
||||
|
||||
<!-- Generate the R.java file for this project's resources. -->
|
||||
<target name="resource-src" depends="dirs">
|
||||
<echo>Generating R.java...</echo>
|
||||
<exec executable="${aapt}" failonerror="true">
|
||||
<arg value="compile" />
|
||||
<arg value="-m" />
|
||||
<arg value="-J" />
|
||||
<arg value="${outdir-r}" />
|
||||
<arg value="-M" />
|
||||
<arg value="AndroidManifest.xml" />
|
||||
<arg value="-S" />
|
||||
<arg value="${resource-dir}" />
|
||||
<arg value="-I" />
|
||||
<arg value="${android-jar}" />
|
||||
</exec>
|
||||
</target>
|
||||
This will ensure that the properties are setup correctly but that your customized
|
||||
targets are used.
|
||||
-->
|
||||
<setup import="false"/>
|
||||
|
||||
<!-- Generate java classes from .aidl files. -->
|
||||
<target name="aidl" depends="dirs">
|
||||
<apply executable="${aidl}" failonerror="true">
|
||||
<fileset dir="${srcdir}">
|
||||
<include name="**/*.aidl"/>
|
||||
</fileset>
|
||||
</apply>
|
||||
</target>
|
||||
<import file="build-imported.xml"/>
|
||||
|
||||
<!-- Compile this project's .java files into .class files. -->
|
||||
<target name="compile" depends="dirs, resource-src, aidl">
|
||||
<javac encoding="ascii" target="1.5" debug="true" extdirs=""
|
||||
srcdir="."
|
||||
destdir="${outdir-classes}"
|
||||
bootclasspath="${android-jar}" />
|
||||
<scalac encoding="ascii" target="jvm-1.5"
|
||||
srcdir="${srcdir}"
|
||||
destdir="${outdir-classes}"
|
||||
bootclasspathref="scalac.path" />
|
||||
<unjar src="${scala-android.jar}" dest="${outdir-classes}"/>
|
||||
<smartjar
|
||||
srcdir="${srcdir}"
|
||||
basedir="${outdir-classes}"
|
||||
classname="com.google.android.snake.Snake"
|
||||
destfile="${outdir}/${ant.project.name}.jar"
|
||||
/>
|
||||
<delete includeemptydirs="true">
|
||||
<fileset dir="${outdir-classes}" includes="**/*"/>
|
||||
</delete>
|
||||
<unjar src="${outdir}/${ant.project.name}.jar" dest="${outdir-classes}"/>
|
||||
</target>
|
||||
|
||||
<!-- Convert this project's .class files into .dex files. -->
|
||||
<target name="dex" depends="compile">
|
||||
<exec executable="${dx}" failonerror="true">
|
||||
<arg value="-JXmx384M" />
|
||||
<arg value="--dex" />
|
||||
<arg value="--output=${intermediate-dex}" />
|
||||
<arg value="--locals=full" />
|
||||
<arg value="--positions=lines" />
|
||||
<arg path="${outdir-classes}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Put the project's resources into the output package file. -->
|
||||
<target name="package-res-and-assets">
|
||||
<echo>Packaging resources and assets...</echo>
|
||||
<exec executable="${aapt}" failonerror="true">
|
||||
<arg value="package" />
|
||||
<arg value="-f" />
|
||||
<arg value="-c" />
|
||||
<arg value="-M" />
|
||||
<arg value="AndroidManifest.xml" />
|
||||
<arg value="-S" />
|
||||
<arg value="${resource-dir}" />
|
||||
<arg value="-A" />
|
||||
<arg value="${asset-dir}" />
|
||||
<arg value="-I" />
|
||||
<arg value="${android-jar}" />
|
||||
<arg value="${out-package}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Same as package-res-and-assets, but without "-A ${asset-dir}" -->
|
||||
<target name="package-res-no-assets">
|
||||
<echo>Packaging resources...</echo>
|
||||
<exec executable="${aapt}" failonerror="true">
|
||||
<arg value="package" />
|
||||
<arg value="-f" />
|
||||
<arg value="-c" />
|
||||
<arg value="-M" />
|
||||
<arg value="AndroidManifest.xml" />
|
||||
<arg value="-S" />
|
||||
<arg value="${resource-dir}" />
|
||||
<!-- No assets directory -->
|
||||
<arg value="-I" />
|
||||
<arg value="${android-jar}" />
|
||||
<arg value="${out-package}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Invoke the proper target depending on whether or not
|
||||
an assets directory is present. -->
|
||||
<!-- TODO: find a nicer way to include the "-A ${asset-dir}" argument
|
||||
only when the assets dir exists. -->
|
||||
<target name="package-res">
|
||||
<available file="${asset-dir}" type="dir"
|
||||
property="res-target" value="and-assets" />
|
||||
<property name="res-target" value="no-assets" />
|
||||
<antcall target="package-res-${res-target}" />
|
||||
</target>
|
||||
|
||||
<!-- Put the project's .class files into the output package file. -->
|
||||
<target name="package-java" depends="compile, package-res">
|
||||
<echo>Packaging java...</echo>
|
||||
<jar destfile="${out-package}"
|
||||
basedir="${outdir-classes}"
|
||||
update="true" />
|
||||
</target>
|
||||
|
||||
<!-- Put the project's .dex files into the output package file. -->
|
||||
<target name="package-dex" depends="dex, package-res">
|
||||
<echo>Packaging dex...</echo>
|
||||
<exec executable="${zip}" failonerror="true">
|
||||
<arg value="-qj" />
|
||||
<arg value="${out-package}" />
|
||||
<arg value="${intermediate-dex}" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<!-- Create the package file for this project from the sources. -->
|
||||
<target name="package" depends="package-dex" />
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# 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 use,
|
||||
# "build.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
|
||||
# Project target.
|
||||
target=Google Inc.:Google APIs:3
|
|
@ -0,0 +1,11 @@
|
|||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must *NOT* be checked in Version Control Systems,
|
||||
# as it contains information specific to your local configuration.
|
||||
|
||||
# location of the SDK. This is only used by Ant
|
||||
# For customization when using a Version Control System, please read the
|
||||
# header note.
|
||||
#sdk-location=C:\\Program Files\\Android
|
||||
sdk-location=/opt/android
|
|
@ -1,29 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
<!-- Copyright (C) 2007 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<com.google.android.snake.SnakeView
|
||||
id="@+id/snake"
|
||||
<com.example.android.snake.SnakeView
|
||||
android:id="@+id/snake"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
tileSize="12"
|
||||
tileSize="24"
|
||||
/>
|
||||
|
||||
<RelativeLayout
|
||||
|
@ -31,13 +30,13 @@
|
|||
android:layout_height="fill_parent" >
|
||||
|
||||
<TextView
|
||||
id="@+id/text"
|
||||
android:id="@+id/text"
|
||||
android:text="@string/snake_layout_text_text"
|
||||
android:visibility="visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textAlign="center"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="#ff8888ff"
|
||||
android:textSize="24sp"/>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
<!-- Copyright (C) 2007 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<declare-styleable name="TileView">
|
||||
|
|
|
@ -1,25 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
-->
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="mode_ready">Snake\nPress Up To Play</string>
|
||||
<string name="mode_pause">Paused\nPress Up To Resume</string>
|
||||
<string name="mode_lose_prefix">Game Over\nScore: </string>
|
||||
<string name="mode_lose_suffix">\nPress Up To Play</string>
|
||||
<string name="app_name">Snake (Scala)"</string>
|
||||
|
||||
<string name="mode_ready">Snake\nPress Up To Play</string>
|
||||
<string name="mode_pause">Paused\nPress Up To Resume</string>
|
||||
<string name="mode_lose_prefix">Game Over\nScore: </string>
|
||||
<string name="mode_lose_suffix">\nPress Up To Play</string>
|
||||
|
||||
<string name="snake_layout_text_text"></string>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package com.example.android.snake
|
||||
|
||||
import _root_.android.app.Activity
|
||||
import _root_.android.os.Bundle
|
||||
import _root_.android.view.Window
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
object Snake {
|
||||
private val ICICLE_KEY = "snake-view"
|
||||
}
|
||||
|
||||
/** Snake: a simple game that everyone can enjoy.
|
||||
*
|
||||
* This is an implementation of the classic Game "Snake", in which you control a
|
||||
* serpent roaming around the garden looking for apples. Be careful, though,
|
||||
* because when you catch one, not only will you become longer, but you'll move
|
||||
* faster. Running into yourself or the walls will end the game.
|
||||
*/
|
||||
class Snake extends Activity {
|
||||
import SnakeView.Mode
|
||||
|
||||
private var mSnakeView: SnakeView = null
|
||||
|
||||
/** Called when Activity is first created. Turns off the title bar, sets up
|
||||
* the content views, and fires up the SnakeView.
|
||||
*/
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// No Title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
setContentView(R.layout.snake_layout)
|
||||
|
||||
mSnakeView = findViewById(R.id.snake).asInstanceOf[SnakeView]
|
||||
mSnakeView setTextView findViewById(R.id.text).asInstanceOf[TextView]
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// We were just launched -- set up a new game
|
||||
mSnakeView setMode Mode.READY
|
||||
} else {
|
||||
// We are being restored
|
||||
val map = savedInstanceState getBundle Snake.ICICLE_KEY
|
||||
if (map != null) mSnakeView.restoreState(map)
|
||||
else mSnakeView setMode Mode.PAUSE
|
||||
}
|
||||
}
|
||||
|
||||
override protected def onPause() {
|
||||
super.onPause()
|
||||
// Pause the game along with the activity
|
||||
mSnakeView setMode Mode.PAUSE
|
||||
}
|
||||
|
||||
override def onSaveInstanceState(outState: Bundle) {
|
||||
//Store the game state
|
||||
outState.putBundle(Snake.ICICLE_KEY, mSnakeView.saveState())
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,438 @@
|
|||
package com.example.android.snake
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.util.Random
|
||||
|
||||
import _root_.android.content.Context
|
||||
import _root_.android.os.{Bundle, Handler, Message}
|
||||
import _root_.android.util.{AttributeSet, Log}
|
||||
import _root_.android.view.{KeyEvent, View}
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
object SnakeView {
|
||||
private val TAG = "SnakeView"
|
||||
|
||||
object Mode extends Enumeration {
|
||||
val PAUSE, READY, RUNNING, LOSE = Value
|
||||
}
|
||||
type Mode = Mode.Value
|
||||
|
||||
object Direction extends Enumeration {
|
||||
val NORTH, SOUTH, EAST, WEST = Value
|
||||
}
|
||||
type Direction = Direction.Value
|
||||
|
||||
/** Labels for the drawables that will be loaded into the TileView class
|
||||
*/
|
||||
private val RED_STAR = 1
|
||||
private val YELLOW_STAR = 2
|
||||
private val GREEN_STAR = 3
|
||||
|
||||
/** Everyone needs a little randomness in their life
|
||||
*/
|
||||
private val RNG = new Random()
|
||||
}
|
||||
|
||||
/** SnakeView: implementation of a simple game of Snake
|
||||
*/
|
||||
class SnakeView(context: Context, attrs: AttributeSet, defStyle: Int)
|
||||
extends TileView(context, attrs, defStyle) {
|
||||
import SnakeView._, Mode._, Direction._
|
||||
|
||||
/** Current mode of application: READY to run, RUNNING, or you have already
|
||||
* lost. static final ints are used instead of an enum for performance
|
||||
* reasons.
|
||||
*/
|
||||
private var mMode = READY
|
||||
|
||||
/** Current direction the snake is headed.
|
||||
*/
|
||||
private var mDirection = NORTH
|
||||
private var mNextDirection = NORTH
|
||||
|
||||
/** mScore: used to track the number of apples captured mMoveDelay: number of
|
||||
* milliseconds between snake movements. This will decrease as apples are
|
||||
* captured.
|
||||
*/
|
||||
private var mScore = 0L
|
||||
private var mMoveDelay = 600L
|
||||
|
||||
/** mLastMove: tracks the absolute time when the snake last moved, and is used
|
||||
* to determine if a move should be made based on mMoveDelay.
|
||||
*/
|
||||
private var mLastMove = 0L
|
||||
|
||||
/** mStatusText: text shows to the user in some run states
|
||||
*/
|
||||
private var mStatusText: TextView = null
|
||||
|
||||
/** mSnakeTrail: a list of Coordinates that make up the snake's body
|
||||
* mAppleList: the secret location of the juicy apples the snake craves.
|
||||
*/
|
||||
private var mSnakeTrail = new ListBuffer[Coordinate]
|
||||
private var mAppleList = new ListBuffer[Coordinate]
|
||||
|
||||
/** Create a simple handler that we can use to cause animation to happen. We
|
||||
* set ourselves as a target and we can use the sleep()
|
||||
* function to cause an update/invalidate to occur at a later date.
|
||||
*/
|
||||
private val mRedrawHandler = new RefreshHandler()
|
||||
|
||||
private class RefreshHandler extends Handler {
|
||||
override def handleMessage(msg: Message) {
|
||||
SnakeView.this.update()
|
||||
SnakeView.this.invalidate()
|
||||
}
|
||||
def sleep(delayMillis: Long) {
|
||||
removeMessages(0)
|
||||
sendMessageDelayed(obtainMessage(0), delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
/** Constructs a SnakeView based on inflation from XML
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
*/
|
||||
def this(context: Context, attrs: AttributeSet) =
|
||||
this(context, attrs, 0)
|
||||
|
||||
initSnakeView()
|
||||
|
||||
private def initSnakeView() {
|
||||
setFocusable(true)
|
||||
|
||||
val r = this.getContext().getResources()
|
||||
|
||||
resetTiles(4)
|
||||
loadTile(RED_STAR, r.getDrawable(R.drawable.redstar))
|
||||
loadTile(YELLOW_STAR, r.getDrawable(R.drawable.yellowstar))
|
||||
loadTile(GREEN_STAR, r.getDrawable(R.drawable.greenstar))
|
||||
}
|
||||
|
||||
private def initNewGame() {
|
||||
mSnakeTrail.clear()
|
||||
mAppleList.clear()
|
||||
|
||||
// For now we're just going to load up a short default eastbound snake
|
||||
// that's just turned north
|
||||
|
||||
mSnakeTrail += Coordinate(7, 7)
|
||||
mSnakeTrail += Coordinate(6, 7)
|
||||
mSnakeTrail += Coordinate(5, 7)
|
||||
mSnakeTrail += Coordinate(4, 7)
|
||||
mSnakeTrail += Coordinate(3, 7)
|
||||
mSnakeTrail += Coordinate(2, 7)
|
||||
mNextDirection = NORTH
|
||||
|
||||
// Two apples to start with
|
||||
addRandomApple()
|
||||
addRandomApple()
|
||||
|
||||
mMoveDelay = 600
|
||||
mScore = 0
|
||||
}
|
||||
|
||||
/** Given a List of coordinates, we need to flatten them into an array of
|
||||
* ints before we can stuff them into a map for flattening and storage.
|
||||
*
|
||||
* @param cvec : a ListBuffer of Coordinate objects
|
||||
* @return : a simple array containing the x/y values of the coordinates
|
||||
* as [x1,y1,x2,y2,x3,y3...]
|
||||
*/
|
||||
private def coordListToArray(cvec: ListBuffer[Coordinate]): Array[Int] = {
|
||||
val len = cvec.length
|
||||
val rawArray = new Array[Int](len * 2)
|
||||
for (index <- 0 until len) {
|
||||
val c = cvec(index)
|
||||
val i2 = 2 * index
|
||||
rawArray(i2) = c.x
|
||||
rawArray(i2 + 1) = c.y
|
||||
}
|
||||
rawArray
|
||||
}
|
||||
|
||||
/** Save game state so that the user does not lose anything
|
||||
* if the game process is killed while we are in the
|
||||
* background.
|
||||
*
|
||||
* @return a Bundle with this view's state
|
||||
*/
|
||||
def saveState(): Bundle = {
|
||||
val map = new Bundle()
|
||||
|
||||
map.putIntArray("mAppleList", coordListToArray(mAppleList))
|
||||
map.putInt("mDirection", mDirection.id)
|
||||
map.putInt("mNextDirection", mNextDirection.id)
|
||||
map.putLong("mMoveDelay", mMoveDelay)
|
||||
map.putLong("mScore", mScore)
|
||||
map.putIntArray("mSnakeTrail", coordListToArray(mSnakeTrail))
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/** Given a flattened array of ordinate pairs, we reconstitute them into a
|
||||
* ListBuffer of Coordinate objects
|
||||
*
|
||||
* @param rawArray : [x1,y1,x2,y2,...]
|
||||
* @return a ListBuffer of Coordinates
|
||||
*/
|
||||
private def coordArrayToList(rawArray: Array[Int]): ListBuffer[Coordinate] = {
|
||||
val coordList = new ListBuffer[Coordinate]
|
||||
for (index <- (0 until rawArray.length) map (_ * 2)) {
|
||||
coordList += Coordinate(rawArray(index), rawArray(index + 1))
|
||||
}
|
||||
coordList
|
||||
}
|
||||
|
||||
/** Restore game state if our process is being relaunched
|
||||
*
|
||||
* @param icicle a Bundle containing the game state
|
||||
*/
|
||||
def restoreState(icicle: Bundle) {
|
||||
setMode(PAUSE)
|
||||
|
||||
mAppleList = coordArrayToList(icicle getIntArray"mAppleList")
|
||||
mDirection = Direction(icicle getInt "mDirection")
|
||||
mNextDirection = Direction(icicle getInt "mNextDirection")
|
||||
mMoveDelay = icicle getLong "mMoveDelay"
|
||||
mScore = icicle getLong "mScore"
|
||||
mSnakeTrail = coordArrayToList(icicle getIntArray "mSnakeTrail")
|
||||
}
|
||||
|
||||
/** Handles key events in the game. Update the direction our snake is traveling
|
||||
* based on the DPAD. Ignore events that would cause the snake to immediately
|
||||
* turn back on itself.
|
||||
*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see android.view.View#onKeyDown(int, android.os.KeyEvent)
|
||||
*/
|
||||
override def onKeyDown(keyCode: Int, msg: KeyEvent): Boolean = {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
|
||||
if (mMode == READY | mMode == LOSE) {
|
||||
/*
|
||||
* At the beginning of the game, or the end of a previous one,
|
||||
* we should start a new game.
|
||||
*/
|
||||
initNewGame()
|
||||
setMode(RUNNING)
|
||||
update()
|
||||
return true
|
||||
}
|
||||
|
||||
if (mMode == PAUSE) {
|
||||
/*
|
||||
* If the game is merely paused, we should just continue where
|
||||
* we left off.
|
||||
*/
|
||||
setMode(RUNNING)
|
||||
update()
|
||||
return true
|
||||
}
|
||||
|
||||
if (mDirection != SOUTH) mNextDirection = NORTH
|
||||
return true
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
|
||||
if (mDirection != NORTH) mNextDirection = SOUTH
|
||||
return true
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
|
||||
if (mDirection != EAST) mNextDirection = WEST
|
||||
return true
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
|
||||
if (mDirection != WEST) mNextDirection = EAST
|
||||
return true
|
||||
}
|
||||
|
||||
super.onKeyDown(keyCode, msg)
|
||||
}
|
||||
|
||||
/** Sets the TextView that will be used to give information (such as "Game
|
||||
* Over" to the user.
|
||||
*
|
||||
* @param newView
|
||||
*/
|
||||
def setTextView(newView: TextView) {
|
||||
mStatusText = newView
|
||||
}
|
||||
|
||||
/** Updates the current mode of the application (RUNNING or PAUSED or the like)
|
||||
* as well as sets the visibility of textview for notification
|
||||
*
|
||||
* @param newMode
|
||||
*/
|
||||
def setMode(newMode: Mode) {
|
||||
val oldMode = mMode
|
||||
mMode = newMode
|
||||
|
||||
if (newMode == RUNNING & oldMode != RUNNING) {
|
||||
mStatusText setVisibility View.INVISIBLE
|
||||
update()
|
||||
return
|
||||
}
|
||||
|
||||
val res = getContext().getResources()
|
||||
val str: CharSequence = newMode match {
|
||||
case PAUSE => res getText R.string.mode_pause
|
||||
case READY => res getText R.string.mode_ready
|
||||
case LOSE => (res getString R.string.mode_lose_prefix) +
|
||||
mScore + (res getString R.string.mode_lose_suffix)
|
||||
case _ => ""
|
||||
}
|
||||
mStatusText setText str
|
||||
mStatusText setVisibility View.VISIBLE
|
||||
}
|
||||
|
||||
/** Selects a random location within the garden that is not currently covered
|
||||
* by the snake. Currently _could_ go into an infinite loop if the snake
|
||||
* currently fills the garden, but we'll leave discovery of this prize to a
|
||||
* truly excellent snake-player.
|
||||
*/
|
||||
private def addRandomApple() {
|
||||
var newCoord: Coordinate = null
|
||||
var found = false
|
||||
while (!found) {
|
||||
// Choose a new location for our apple
|
||||
val newX = 1 + RNG.nextInt(mXTileCount - 2)
|
||||
val newY = 1 + RNG.nextInt(mYTileCount - 2)
|
||||
newCoord = Coordinate(newX, newY)
|
||||
|
||||
// Make sure it's not already under the snake
|
||||
var collision = false
|
||||
for (index <- 0 until mSnakeTrail.length) {
|
||||
if (mSnakeTrail(index) equals newCoord) {
|
||||
collision = true
|
||||
}
|
||||
}
|
||||
// if we're here and there's been no collision, then we have
|
||||
// a good location for an apple. Otherwise, we'll circle back
|
||||
// and try again
|
||||
found = !collision
|
||||
}
|
||||
if (newCoord == null) {
|
||||
Log.e(TAG, "Somehow ended up with a null newCoord!")
|
||||
}
|
||||
mAppleList += newCoord
|
||||
}
|
||||
|
||||
/** Handles the basic update loop, checking to see if we are in the running
|
||||
* state, determining if a move should be made, updating the snake's location.
|
||||
*/
|
||||
def update() {
|
||||
if (mMode == RUNNING) {
|
||||
val now = System.currentTimeMillis
|
||||
|
||||
if (now - mLastMove > mMoveDelay) {
|
||||
clearTiles()
|
||||
updateWalls()
|
||||
updateSnake()
|
||||
updateApples()
|
||||
mLastMove = now
|
||||
}
|
||||
mRedrawHandler sleep mMoveDelay
|
||||
}
|
||||
}
|
||||
|
||||
/** Draws some walls.
|
||||
*/
|
||||
private def updateWalls() {
|
||||
for (x <- 0 until mXTileCount) {
|
||||
setTile(GREEN_STAR, x, 0)
|
||||
setTile(GREEN_STAR, x, mYTileCount - 1)
|
||||
}
|
||||
for (y <- 1 until (mYTileCount - 1)) {
|
||||
setTile(GREEN_STAR, 0, y)
|
||||
setTile(GREEN_STAR, mXTileCount - 1, y)
|
||||
}
|
||||
}
|
||||
|
||||
/** Draws some apples.
|
||||
*/
|
||||
private def updateApples() {
|
||||
for (c <- mAppleList) {
|
||||
setTile(YELLOW_STAR, c.x, c.y)
|
||||
}
|
||||
}
|
||||
|
||||
/** Figure out which way the snake is going, see if he's run into anything (the
|
||||
* walls, himself, or an apple). If he's not going to die, we then add to the
|
||||
* front and subtract from the rear in order to simulate motion. If we want to
|
||||
* grow him, we don't subtract from the rear.
|
||||
*/
|
||||
private def updateSnake() {
|
||||
var growSnake = false
|
||||
|
||||
// grab the snake by the head
|
||||
val head = mSnakeTrail(0)
|
||||
|
||||
mDirection = mNextDirection
|
||||
|
||||
val newHead = mDirection match {
|
||||
case EAST => new Coordinate(head.x + 1, head.y)
|
||||
case WEST => new Coordinate(head.x - 1, head.y)
|
||||
case NORTH => new Coordinate(head.x, head.y - 1)
|
||||
case SOUTH => new Coordinate(head.x, head.y + 1)
|
||||
}
|
||||
|
||||
// Collision detection
|
||||
// For now we have a 1-square wall around the entire arena
|
||||
if ((newHead.x < 1) || (newHead.y < 1)
|
||||
|| (newHead.x > mXTileCount - 2)
|
||||
|| (newHead.y > mYTileCount - 2)) {
|
||||
setMode(LOSE)
|
||||
return
|
||||
}
|
||||
|
||||
// Look for collisions with itself
|
||||
for (snakeindex <- 0 until mSnakeTrail.length) {
|
||||
val c = mSnakeTrail(snakeindex)
|
||||
if (c equals newHead) {
|
||||
setMode(LOSE)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Look for apples
|
||||
for (appleindex <- 0 until mAppleList.length) {
|
||||
val c = mAppleList(appleindex)
|
||||
if (c equals newHead) {
|
||||
mAppleList -= c
|
||||
addRandomApple()
|
||||
|
||||
mScore += 1
|
||||
mMoveDelay = mMoveDelay * 9 / 10
|
||||
|
||||
growSnake = true
|
||||
}
|
||||
}
|
||||
|
||||
// push a new head onto the ListBuffer and pull off the tail
|
||||
newHead +: mSnakeTrail
|
||||
// except if we want the snake to grow
|
||||
if (!growSnake) {
|
||||
mSnakeTrail remove (mSnakeTrail.length - 1)
|
||||
}
|
||||
|
||||
var index = 0
|
||||
for (c <- mSnakeTrail) {
|
||||
setTile(if (index == 0) YELLOW_STAR else RED_STAR, c.x, c.y)
|
||||
index += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Simple class containing two integer values and a comparison function.
|
||||
* There's probably something I should use instead, but this was quick and
|
||||
* easy to build.
|
||||
*/
|
||||
private case class Coordinate(x: Int, y: Int) {
|
||||
override def toString() = "Coordinate: [" + x + "," + y + "]"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
package com.example.android.snake
|
||||
|
||||
import _root_.android.content.Context
|
||||
import _root_.android.content.res.TypedArray
|
||||
import _root_.android.graphics.Bitmap
|
||||
import _root_.android.graphics.Canvas
|
||||
import _root_.android.graphics.Paint
|
||||
import _root_.android.graphics.drawable.Drawable
|
||||
import _root_.android.util.AttributeSet
|
||||
import _root_.android.view.View
|
||||
|
||||
/** TileView: a View-variant designed for handling arrays of "icons" or other
|
||||
* drawables.
|
||||
*/
|
||||
class TileView(context: Context, attrs: AttributeSet, defStyle: Int)
|
||||
extends View(context, attrs, defStyle) {
|
||||
|
||||
/** Parameters controlling the size of the tiles and their range within view.
|
||||
* Width/Height are in pixels, and Drawables will be scaled to fit to these
|
||||
* dimensions. X/Y Tile Counts are the number of tiles that will be drawn.
|
||||
*/
|
||||
protected var mTileSize = 0
|
||||
|
||||
protected var mXTileCount = 0
|
||||
protected var mYTileCount = 0
|
||||
|
||||
private var mXOffset = 0
|
||||
private var mYOffset = 0
|
||||
|
||||
|
||||
/** A hash that maps integer handles specified by the subclasser to the
|
||||
* drawable that will be used for that reference
|
||||
*/
|
||||
private var mTileArray: Array[Bitmap] = null
|
||||
|
||||
/** A two-dimensional array of integers in which the number represents the
|
||||
* index of the tile that should be drawn at that locations
|
||||
*/
|
||||
private var mTileGrid: Array[Array[Int]] = null
|
||||
|
||||
private final val mPaint = new Paint()
|
||||
|
||||
initTileView()
|
||||
|
||||
private def initTileView() {
|
||||
val a = context.obtainStyledAttributes(attrs, R.styleable.TileView)
|
||||
mTileSize = a.getInt(R.styleable.TileView_tileSize, 12)
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
def this(context: Context, attrs: AttributeSet) =
|
||||
this(context, attrs, 0)
|
||||
|
||||
/** Rests the internal array of Bitmaps used for drawing tiles, and
|
||||
* sets the maximum index of tiles to be inserted
|
||||
*
|
||||
* @param tilecount
|
||||
*/
|
||||
def resetTiles(tilecount: Int) {
|
||||
mTileArray = new Array[Bitmap](tilecount)
|
||||
}
|
||||
|
||||
override protected def onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
mXTileCount = Math.floor(w / mTileSize).toInt
|
||||
mYTileCount = Math.floor(h / mTileSize).toInt
|
||||
|
||||
mXOffset = ((w - (mTileSize * mXTileCount)) / 2)
|
||||
mYOffset = ((h - (mTileSize * mYTileCount)) / 2)
|
||||
|
||||
mTileGrid = new Array[Array[Int]](mXTileCount)
|
||||
for (x <- 0 until mXTileCount) mTileGrid(x) = new Array[Int](mYTileCount)
|
||||
clearTiles()
|
||||
}
|
||||
|
||||
/** Function to set the specified Drawable as the tile for a particular
|
||||
* integer key.
|
||||
*
|
||||
* @param key
|
||||
* @param tile
|
||||
*/
|
||||
def loadTile(key: Int, tile: Drawable) {
|
||||
val bitmap = Bitmap.createBitmap(mTileSize, mTileSize, Bitmap.Config.ARGB_8888)
|
||||
val canvas = new Canvas(bitmap)
|
||||
tile.setBounds(0, 0, mTileSize, mTileSize)
|
||||
tile draw canvas
|
||||
mTileArray(key) = bitmap
|
||||
}
|
||||
|
||||
/** Resets all tiles to 0 (empty)
|
||||
*/
|
||||
def clearTiles() {
|
||||
for (x <- 0 until mXTileCount; y <- 0 until mYTileCount) setTile(0, x, y)
|
||||
}
|
||||
|
||||
/** Used to indicate that a particular tile (set with loadTile and referenced
|
||||
* by an integer) should be drawn at the given x/y coordinates during the
|
||||
* next invalidate/draw cycle.
|
||||
*
|
||||
* @param tileindex
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
def setTile(tileindex: Int, x: Int, y: Int) {
|
||||
mTileGrid(x)(y) = tileindex
|
||||
}
|
||||
|
||||
override def onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
for (x <- 0 until mXTileCount; y <- 0 until mYTileCount) {
|
||||
val tileindex = mTileGrid(x)(y)
|
||||
if (tileindex > 0) {
|
||||
canvas.drawBitmap(mTileArray(tileindex),
|
||||
mXOffset + x * mTileSize,
|
||||
mYOffset + y * mTileSize,
|
||||
mPaint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.snake
|
||||
|
||||
import _root_.android.app.Activity
|
||||
import _root_.android.os.Bundle
|
||||
import _root_.android.view.Window
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
/**
|
||||
* Snake: a simple game that everyone can enjoy.
|
||||
*
|
||||
* This is an implementation of the classic Game "Snake", in which you control a
|
||||
* serpent roaming around the garden looking for apples. Be careful, though,
|
||||
* because when you catch one, not only will you become longer, but you'll move
|
||||
* faster. Running into yourself or the walls will end the game.
|
||||
*/
|
||||
object Snake {
|
||||
private val ICICLE_KEY = "snake-view"
|
||||
}
|
||||
|
||||
class Snake extends Activity {
|
||||
import Snake._ // companion object
|
||||
|
||||
private var mSnakeView: SnakeView = _
|
||||
|
||||
/**
|
||||
* Called when Activity is first created. Turns off the title bar, sets up
|
||||
* the content views, and fires up the SnakeView.
|
||||
*/
|
||||
override def onCreate(icicle: Bundle) {
|
||||
super.onCreate(icicle)
|
||||
|
||||
// No Title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
||||
|
||||
setContentView(R.layout.snake_layout)
|
||||
|
||||
mSnakeView = findViewById(R.id.snake).asInstanceOf[SnakeView]
|
||||
mSnakeView setTextView findViewById(R.id.text).asInstanceOf[TextView]
|
||||
|
||||
if (icicle == null) {
|
||||
// We were just launched -- set up a new game
|
||||
mSnakeView setMode SnakeView.READY
|
||||
} else {
|
||||
// We are being restored
|
||||
val map = icicle getBundle ICICLE_KEY
|
||||
if (map != null)
|
||||
mSnakeView restoreState map
|
||||
else
|
||||
mSnakeView setMode SnakeView.PAUSE
|
||||
}
|
||||
}
|
||||
|
||||
override protected def onPause() {
|
||||
super.onPause()
|
||||
// Pause the game along with the activity
|
||||
mSnakeView setMode SnakeView.PAUSE
|
||||
}
|
||||
|
||||
override def onFreeze(outState: Bundle) {
|
||||
//Store the game state
|
||||
outState.putBundle(ICICLE_KEY, mSnakeView.saveState())
|
||||
}
|
||||
|
||||
}
|
|
@ -1,464 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.snake
|
||||
|
||||
import java.util.Map
|
||||
|
||||
import scala.collection.jcl.ArrayList
|
||||
|
||||
import _root_.android.content.{Context, Resources}
|
||||
import _root_.android.os.{Bundle, Handler, Message}
|
||||
import _root_.android.util.{AttributeSet, Log}
|
||||
import _root_.android.view.{KeyEvent, View}
|
||||
import _root_.android.widget.TextView
|
||||
|
||||
/**
|
||||
* SnakeView: implementation of a simple game of Snake
|
||||
*/
|
||||
object SnakeView {
|
||||
private val TAG = "SnakeView"
|
||||
|
||||
final val PAUSE = 0
|
||||
final val READY = 1
|
||||
final val RUNNING = 2
|
||||
final val LOSE = 3
|
||||
|
||||
private final val NORTH = 1
|
||||
private final val SOUTH = 2
|
||||
private final val EAST = 3
|
||||
private final val WEST = 4
|
||||
|
||||
/**
|
||||
* Labels for the drawables that will be loaded into the TileView class
|
||||
*/
|
||||
private final val RED_STAR = 1
|
||||
private final val YELLOW_STAR = 2
|
||||
private final val GREEN_STAR = 3
|
||||
|
||||
/**
|
||||
* Everyone needs a little randomness in their life
|
||||
*/
|
||||
private final val RND = new Random()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a SnakeView based on inflation from XML
|
||||
*
|
||||
* @param context
|
||||
* @param attrs
|
||||
* @param inflateParams
|
||||
*/
|
||||
class SnakeView(context: Context, attrs: AttributeSet, inflateParams: Map,
|
||||
defStyle: Int) extends TileView(context, attrs, inflateParams, defStyle) {
|
||||
import SnakeView._ // companion object
|
||||
|
||||
def this(context: Context, attrs: AttributeSet, inflateParams: Map) =
|
||||
this(context, attrs, inflateParams, 0)
|
||||
|
||||
/**
|
||||
* Current mode of application: READY to run, RUNNING, or you have already
|
||||
* lost. static final ints are used instead of an enum for performance
|
||||
* reasons.
|
||||
*/
|
||||
private var mMode = READY
|
||||
|
||||
/**
|
||||
* Current direction the snake is headed.
|
||||
*/
|
||||
private var mDirection = NORTH
|
||||
private var mNextDirection = NORTH
|
||||
|
||||
/**
|
||||
* mScore: used to track the number of apples captured mMoveDelay: number of
|
||||
* milliseconds between snake movements. This will decrease as apples are
|
||||
* captured.
|
||||
*/
|
||||
private var mScore = 0L
|
||||
private var mMoveDelay = 600L
|
||||
|
||||
/**
|
||||
* mLastMove: tracks the absolute time when the snake last moved, and is used
|
||||
* to determine if a move should be made based on mMoveDelay.
|
||||
*/
|
||||
private var mLastMove: Long = _
|
||||
|
||||
/**
|
||||
* mStatusText: text shows to the user in some run states
|
||||
*/
|
||||
private var mStatusText: TextView = _
|
||||
|
||||
/**
|
||||
* mSnakeTrail: a list of Coordinates that make up the snake's body
|
||||
* mAppleList: the secret location of the juicy apples the snake craves.
|
||||
*/
|
||||
private val mSnakeTrail = new ArrayList[Coordinate]()
|
||||
private val mAppleList = new ArrayList[Coordinate]()
|
||||
|
||||
/**
|
||||
* Create a simple handler that we can use to cause animation to happen. We
|
||||
* set ourselves as a target and we can use the sleep()
|
||||
* function to cause an update/invalidate to occur at a later date.
|
||||
*/
|
||||
private val mRedrawHandler = new RefreshHandler()
|
||||
|
||||
class RefreshHandler extends Handler {
|
||||
override def handleMessage(msg: Message) {
|
||||
SnakeView.this.update()
|
||||
SnakeView.this.invalidate()
|
||||
}
|
||||
def sleep(delayMillis: Long) {
|
||||
this.removeMessages(0)
|
||||
sendMessageDelayed(obtainMessage(0), delayMillis)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
setFocusable(true)
|
||||
|
||||
val r = this.getContext().getResources()
|
||||
|
||||
resetTiles(4)
|
||||
loadTile(RED_STAR, r getDrawable R.drawable.redstar)
|
||||
loadTile(YELLOW_STAR, r getDrawable R.drawable.yellowstar)
|
||||
loadTile(GREEN_STAR, r getDrawable R.drawable.greenstar)
|
||||
}
|
||||
|
||||
private def initNewGame() {
|
||||
mSnakeTrail.clear()
|
||||
mAppleList.clear()
|
||||
|
||||
// For now we're just going to load up a short default eastbound snake
|
||||
// that's just turned north
|
||||
mSnakeTrail add new Coordinate(7, 7)
|
||||
mSnakeTrail add new Coordinate(6, 7)
|
||||
mSnakeTrail add new Coordinate(5, 7)
|
||||
mSnakeTrail add new Coordinate(4, 7)
|
||||
mSnakeTrail add new Coordinate(3, 7)
|
||||
mSnakeTrail add new Coordinate(2, 7)
|
||||
mNextDirection = NORTH
|
||||
|
||||
// Two apples to start with
|
||||
addRandomApple()
|
||||
addRandomApple()
|
||||
|
||||
mMoveDelay = 600
|
||||
mScore = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a ArrayList of coordinates, we need to flatten them into an array of
|
||||
* ints before we can stuff them into a map for flattening and storage.
|
||||
*
|
||||
* @param cvec : a ArrayList of Coordinate objects
|
||||
* @return : a simple array containing the x/y values of the coordinates
|
||||
* as [x1,y1,x2,y2,x3,y3...]
|
||||
*/
|
||||
private def coordArrayListToArray(cvec: ArrayList[Coordinate]): Array[Int] = {
|
||||
val count = cvec.size
|
||||
val rawArray = new Array[Int](count * 2)
|
||||
for (index <- 0 until count) {
|
||||
val c = cvec(index)
|
||||
rawArray(2 * index) = c.x
|
||||
rawArray(2 * index + 1) = c.y
|
||||
}
|
||||
rawArray
|
||||
}
|
||||
|
||||
/**
|
||||
* Save game state so that the user does not lose anything
|
||||
* if the game process is killed while we are in the
|
||||
* background.
|
||||
*
|
||||
* @return a Bundle with this view's state
|
||||
*/
|
||||
def saveState(): Bundle = {
|
||||
val map = new Bundle()
|
||||
|
||||
map.putIntArray("mAppleList", coordArrayListToArray(mAppleList))
|
||||
map.putInteger("mDirection", mDirection)
|
||||
map.putInteger("mNextDirection", mNextDirection)
|
||||
map.putLong("mMoveDelay", mMoveDelay)
|
||||
map.putLong("mScore", mScore)
|
||||
map.putIntArray("mSnakeTrail", coordArrayListToArray(mSnakeTrail))
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a flattened array of ordinate pairs, we reconstitute them into a
|
||||
* ArrayList of Coordinate objects
|
||||
*
|
||||
* @param rawArray : [x1,y1,x2,y2,...]
|
||||
* @return a ArrayList of Coordinates
|
||||
*/
|
||||
private def coordArrayToArrayList(dest: ArrayList[Coordinate], rawArray: Array[Int]) {
|
||||
dest.clear()
|
||||
var index = 0; while (index < rawArray.length) {
|
||||
dest add Coordinate(rawArray(index), rawArray(index + 1))
|
||||
index += 2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore game state if our process is being relaunched
|
||||
*
|
||||
* @param icicle a Bundle containing the game state
|
||||
*/
|
||||
def restoreState(icicle: Bundle) {
|
||||
setMode(PAUSE)
|
||||
|
||||
coordArrayToArrayList(mAppleList, icicle getIntArray "mAppleList")
|
||||
mDirection = (icicle getInteger "mDirection").intValue
|
||||
mNextDirection = (icicle getInteger "mNextDirection").intValue
|
||||
mMoveDelay = (icicle getLong "mMoveDelay").longValue
|
||||
mScore = (icicle getLong "mScore").longValue
|
||||
coordArrayToArrayList(mSnakeTrail, icicle getIntArray "mSnakeTrail")
|
||||
}
|
||||
|
||||
/*
|
||||
* handles key events in the game. Update the direction our snake is traveling
|
||||
* based on the DPAD. Ignore events that would cause the snake to immediately
|
||||
* turn back on itself.
|
||||
*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see android.view.View#onKeyDown(int, android.os.KeyEvent)
|
||||
*/
|
||||
override def onKeyDown(keyCode: Int, msg: KeyEvent): Boolean = keyCode match {
|
||||
case KeyEvent.KEYCODE_DPAD_UP =>
|
||||
if (mMode == READY | mMode == LOSE) {
|
||||
/*
|
||||
* At the beginning of the game, or the end of a previous one,
|
||||
* we should start a new game.
|
||||
*/
|
||||
initNewGame()
|
||||
setMode(RUNNING)
|
||||
update()
|
||||
}
|
||||
else if (mMode == PAUSE) {
|
||||
/*
|
||||
* If the game is merely paused, we should just continue where
|
||||
* we left off.
|
||||
*/
|
||||
setMode(RUNNING)
|
||||
update()
|
||||
}
|
||||
|
||||
if (mDirection != SOUTH) mNextDirection = NORTH
|
||||
true
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN =>
|
||||
if (mDirection != NORTH) mNextDirection = SOUTH
|
||||
true
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT =>
|
||||
if (mDirection != EAST) mNextDirection = WEST
|
||||
true
|
||||
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT =>
|
||||
if (mDirection != WEST) mNextDirection = EAST
|
||||
true
|
||||
|
||||
case _ =>
|
||||
super.onKeyDown(keyCode, msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TextView that will be used to give information (such as "Game
|
||||
* Over" to the user.
|
||||
*
|
||||
* @param newView
|
||||
*/
|
||||
def setTextView(newView: TextView) {
|
||||
mStatusText = newView
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current mode of the application (RUNNING or PAUSED or the like)
|
||||
* as well as sets the visibility of textview for notification
|
||||
*
|
||||
* @param newMode
|
||||
*/
|
||||
def setMode(newMode: Int) {
|
||||
val oldMode = mMode
|
||||
mMode = newMode
|
||||
|
||||
if (newMode == RUNNING & oldMode != RUNNING) {
|
||||
mStatusText setVisibility View.INVISIBLE
|
||||
update()
|
||||
return
|
||||
}
|
||||
|
||||
val res = getContext().getResources()
|
||||
val str =
|
||||
if (newMode == PAUSE)
|
||||
res getText R.string.mode_pause
|
||||
else if (newMode == READY)
|
||||
res getText R.string.mode_ready
|
||||
else if (newMode == LOSE)
|
||||
(res getString R.string.mode_lose_prefix) + mScore +
|
||||
(res getString R.string.mode_lose_suffix)
|
||||
else
|
||||
""
|
||||
|
||||
mStatusText setText str
|
||||
mStatusText setVisibility View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a random location within the garden that is not currently covered
|
||||
* by the snake. Currently _could_ go into an infinite loop if the snake
|
||||
* currently fills the garden, but we'll leave discovery of this prize to a
|
||||
* truly excellent snake-player.
|
||||
*
|
||||
*/
|
||||
private def addRandomApple() {
|
||||
var newCoord: Coordinate = null
|
||||
var found = false
|
||||
while (!found) {
|
||||
// Choose a new location for our apple
|
||||
val newX = 1 + RND.nextInt(mXTileCount - 2)
|
||||
val newY = 1 + RND.nextInt(mYTileCount - 2)
|
||||
newCoord = Coordinate(newX, newY)
|
||||
|
||||
// Make sure it's not already under the snake
|
||||
var collision = false
|
||||
for (index <- 0 until mSnakeTrail.size) {
|
||||
if (mSnakeTrail(index) equals newCoord) collision = true
|
||||
}
|
||||
// if we're here and there's been no collision, then we have
|
||||
// a good location for an apple. Otherwise, we'll circle back
|
||||
// and try again
|
||||
found = !collision
|
||||
}
|
||||
if (newCoord == null)
|
||||
Log.e(TAG, "Somehow ended up with a null newCoord!")
|
||||
mAppleList add newCoord
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the basic update loop, checking to see if we are in the running
|
||||
* state, determining if a move should be made, updating the snake's location.
|
||||
*/
|
||||
def update() {
|
||||
if (mMode == RUNNING) {
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
if (now - mLastMove > mMoveDelay) {
|
||||
clearTiles()
|
||||
updateWalls()
|
||||
updateSnake()
|
||||
updateApples()
|
||||
mLastMove = now
|
||||
}
|
||||
mRedrawHandler sleep mMoveDelay
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws some walls.
|
||||
*
|
||||
*/
|
||||
private def updateWalls() {
|
||||
for (x <- 0 until mXTileCount) {
|
||||
setTile(GREEN_STAR, x, 0)
|
||||
setTile(GREEN_STAR, x, mYTileCount - 1)
|
||||
}
|
||||
for (y <- 1 until (mYTileCount-1)) {
|
||||
setTile(GREEN_STAR, 0, y)
|
||||
setTile(GREEN_STAR, mXTileCount - 1, y)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws some apples.
|
||||
*
|
||||
*/
|
||||
private def updateApples() {
|
||||
for (c <- mAppleList) setTile(YELLOW_STAR, c.x, c.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out which way the snake is going, see if he's run into anything (the
|
||||
* walls, himself, or an apple). If he's not going to die, we then add to the
|
||||
* front and subtract from the rear in order to simulate motion. If we want to
|
||||
* grow him, we don't subtract from the rear.
|
||||
*/
|
||||
private def updateSnake() {
|
||||
var growSnake = false
|
||||
|
||||
// grab the snake by the head
|
||||
val head = mSnakeTrail(0)
|
||||
|
||||
mDirection = mNextDirection
|
||||
|
||||
val newHead = mDirection match {
|
||||
case EAST => Coordinate(head.x + 1, head.y)
|
||||
case WEST => Coordinate(head.x - 1, head.y)
|
||||
case NORTH => Coordinate(head.x, head.y - 1)
|
||||
case SOUTH => Coordinate(head.x, head.y + 1)
|
||||
}
|
||||
|
||||
// Collision detection
|
||||
// For now we have a 1-square wall around the entire arena
|
||||
if ((newHead.x < 1) || (newHead.y < 1) ||
|
||||
(newHead.x > mXTileCount - 2) || (newHead.y > mYTileCount - 2)) {
|
||||
setMode(LOSE)
|
||||
return
|
||||
}
|
||||
|
||||
// Look for collisions with itself
|
||||
for (c <- mSnakeTrail if c equals newHead) {
|
||||
setMode(LOSE)
|
||||
return
|
||||
}
|
||||
|
||||
// Look for apples
|
||||
for (c <- mAppleList if c equals newHead) {
|
||||
mAppleList remove c
|
||||
addRandomApple()
|
||||
|
||||
mScore += 1
|
||||
mMoveDelay = 9 * mMoveDelay / 10
|
||||
|
||||
growSnake = true
|
||||
}
|
||||
|
||||
// push a new head onto the ArrayList and pull off the tail
|
||||
mSnakeTrail.add(0, newHead)
|
||||
// except if we want the snake to grow
|
||||
if (!growSnake)
|
||||
mSnakeTrail remove (mSnakeTrail.size - 1)
|
||||
|
||||
var index = 0
|
||||
for (c <- mSnakeTrail) {
|
||||
setTile(if (index == 0) YELLOW_STAR else RED_STAR, c.x, c.y)
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple class containing two integer values and a comparison function.
|
||||
* There's probably something I should use instead, but this was quick and
|
||||
* easy to build.
|
||||
*/
|
||||
private case class Coordinate(x: Int, y: Int) {
|
||||
override def toString() = "Coordinate: [" + x + "," + y + "]"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2007 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.android.snake
|
||||
|
||||
import _root_.android.content.{Context, Resources}
|
||||
import _root_.android.graphics.{Bitmap, Canvas, Paint}
|
||||
import _root_.android.graphics.drawable.Drawable
|
||||
import _root_.android.util.AttributeSet
|
||||
import _root_.android.view.View
|
||||
|
||||
import java.util.Map
|
||||
|
||||
/**
|
||||
* TileView: a View-variant designed for handling arrays of "icons" or other
|
||||
* drawables.
|
||||
*/
|
||||
class TileView(context: Context, attrs: AttributeSet, inflateParams: Map,
|
||||
defStyle: Int) extends View(context, attrs, inflateParams, defStyle) {
|
||||
|
||||
/**
|
||||
* Parameters controlling the size of the tiles and their range within view.
|
||||
* Width/Height are in pixels, and Drawables will be scaled to fit to these
|
||||
* dimensions. X/Y Tile Counts are the number of tiles that will be drawn.
|
||||
*/
|
||||
protected var mTileSize: Int = _
|
||||
|
||||
protected var mXTileCount: Int = _
|
||||
protected var mYTileCount: Int = _
|
||||
|
||||
private var mXOffset: Int = _
|
||||
private var mYOffset: Int = _
|
||||
|
||||
/**
|
||||
* A hash that maps integer handles specified by the subclasser to the
|
||||
* drawable that will be used for that reference
|
||||
*/
|
||||
private var mTileArray: Array[Bitmap] = _
|
||||
|
||||
/**
|
||||
* A two-dimensional array of integers in which the number represents the
|
||||
* index of the tile that should be drawn at that locations
|
||||
*/
|
||||
private var mTileGrid: Array[Array[Int]] = _
|
||||
|
||||
private val mPaint = new Paint()
|
||||
|
||||
mTileSize = {
|
||||
val a = context.obtainStyledAttributes(attrs, R.styleable.TileView)
|
||||
a.getInt(R.styleable.TileView_tileSize, 12)
|
||||
}
|
||||
|
||||
def this(context: Context, attrs: AttributeSet, inflateParams: Map) =
|
||||
this(context, attrs, inflateParams, 0)
|
||||
|
||||
/**
|
||||
* Rests the internal array of Bitmaps used for drawing tiles, and
|
||||
* sets the maximum index of tiles to be inserted
|
||||
*
|
||||
* @param tilecount
|
||||
*/
|
||||
def resetTiles(tilecount: Int) {
|
||||
mTileArray = new Array[Bitmap](tilecount)
|
||||
}
|
||||
|
||||
override protected def onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
mXTileCount = java.lang.Math.floor(w / mTileSize).toInt
|
||||
mYTileCount = java.lang.Math.floor(h / mTileSize).toInt
|
||||
|
||||
mXOffset = (w - (mTileSize * mXTileCount)) / 2
|
||||
mYOffset = (h - (mTileSize * mYTileCount)) / 2
|
||||
|
||||
mTileGrid = new Array[Array[Int]](mXTileCount, mYTileCount)
|
||||
clearTiles()
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set the specified Drawable as the tile for a particular
|
||||
* integer key.
|
||||
*
|
||||
* @param key
|
||||
* @param tile
|
||||
*/
|
||||
def loadTile(key: Int, tile: Drawable) {
|
||||
val bitmap = Bitmap.createBitmap(mTileSize, mTileSize, true)
|
||||
val canvas = new Canvas(bitmap)
|
||||
tile.setBounds(0, 0, mTileSize, mTileSize)
|
||||
tile draw canvas
|
||||
|
||||
mTileArray(key) = bitmap
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all tiles to 0 (empty)
|
||||
*
|
||||
*/
|
||||
def clearTiles() {
|
||||
for (x <- 0 until mXTileCount; y <- 0 until mYTileCount)
|
||||
setTile(0, x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to indicate that a particular tile (set with loadTile and referenced
|
||||
* by an integer) should be drawn at the given x/y coordinates during the
|
||||
* next invalidate/draw cycle.
|
||||
*
|
||||
* @param tileindex
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
def setTile(tileindex: Int, x: Int, y: Int) {
|
||||
mTileGrid(x)(y) = tileindex
|
||||
}
|
||||
|
||||
override def onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
for (x <- 0 until mXTileCount; y <- 0 until mYTileCount if mTileGrid(x)(y) > 0)
|
||||
canvas.drawBitmap(mTileArray(mTileGrid(x)(y)),
|
||||
mXOffset + x * mTileSize,
|
||||
mYOffset + y * mTileSize,
|
||||
mPaint)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.android.snake.tests">
|
||||
|
||||
<!-- We add an application tag here just so that we can indicate that
|
||||
this package needs to link against the android.test library,
|
||||
which is needed when building test cases. -->
|
||||
<application>
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="com.example.android.snake"
|
||||
android:label="Snake sample tests">
|
||||
</instrumentation>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,17 @@
|
|||
package com.example.android.snake;
|
||||
|
||||
import android.test.ActivityInstrumentationTestCase;
|
||||
|
||||
import com.example.android.snake.Snake;
|
||||
|
||||
/**
|
||||
* Make sure that the main launcher activity opens up properly, which will be
|
||||
* verified by {@link ActivityTestCase#testActivityTestCaseSetUpProperly}.
|
||||
*/
|
||||
public class SnakeTest extends ActivityInstrumentationTestCase<Snake> {
|
||||
|
||||
public SnakeTest() {
|
||||
super("com.example.android.snake", Snake.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project name="android" default="help">
|
||||
|
||||
<target name="uninstall" description="uninstall applications">
|
||||
<ant dir="HelloAndroid" target="uninstall"/>
|
||||
<ant dir="Snake" target="uninstall"/>
|
||||
<ant dir="LunarLander" target="uninstall"/>
|
||||
</target>
|
||||
|
||||
<target name="clean" description="clean up">
|
||||
<ant dir="HelloAndroid" target="clean"/>
|
||||
<ant dir="Snake" target="clean"/>
|
||||
<ant dir="LunarLander" target="clean"/>
|
||||
</target>
|
||||
|
||||
<target name="help">
|
||||
<echo message="Android Ant Build. Available targets:"/>
|
||||
<echo message=" help: Display this help."/>
|
||||
<echo message=" uninstall: Uninstall applications from a running emulator or device."/>
|
||||
<echo message=" clean: Clean up build files from projects."/>
|
||||
</target>
|
||||
|
||||
</project>
|
Loading…
Reference in New Issue