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:
michelou 2009-09-15 12:54:21 +00:00
parent 806892c0f5
commit 58e60e1417
44 changed files with 2626 additions and 1984 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Hello (Scala)</string>
</resources>

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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")
}
}

View File

@ -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)
}
}
}

View File

@ -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 =>
}
}
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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())
}
}

View File

@ -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 + "]"
}
}

View File

@ -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)
}
}
}
}

View File

@ -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())
}
}

View File

@ -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 + "]"
}
}

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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);
}
}

23
docs/android-examples/build.xml Executable file
View File

@ -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>