Permalink
Browse files

Lots of good stuff - have the sounds working, frigged with the layout…

… more.

It's still but ugly, but now shifted to the right.  Added a button for sounds.
Make the game pause when the activity is paused (taken off screen), it doesn't unpause currently.

Added a few more game controller tests - pausing, playing sounds.

Reworked the delayed runner so I can cancel pending events (for pausing).
Added a public domain icon, and took some sounds from brain workshop.
  • Loading branch information...
dleblanc committed Jan 21, 2010
1 parent b2ca733 commit 41ba2ed350fa2fa6f366c8d27d5c1c74c22fda5c
View
4 README
@@ -27,4 +27,8 @@ To build:
- Install it on the emulator:
sbt reinstall-emulator
+TODOS:
+ - unify the duplicated code between sounds and locations. Right now the sound stuff isn't as well tested.
+Image of the brain taken from http://openclipart.org/SOMbanners/files/kattekrab/8060, which is in the public domain.
+Sounds taken from the excellent 'Brain Workshop' open source desktop program.
@@ -2,7 +2,7 @@
android:versionName="0.1" android:versionCode="1" package="com.example.android" xmlns:android="http://schemas.android.com/apk/res/android">
<uses-sdk android:minSdkVersion="4"></uses-sdk>
<application android:icon="@drawable/app_icon" android:label="@string/app_name">
- <activity android:label="@string/app_name" android:name=".MainActivity">
+ <activity android:label="@string/app_name" android:name="PositronicActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"></action>
<category android:name="android.intent.category.LAUNCHER"></category>
@@ -15,19 +15,29 @@
public static final int solid_blue=0x7f020001;
}
public static final class id {
- public static final int buttonRow0=0x7f050001;
- public static final int buttonRow1=0x7f050002;
- public static final int buttonRow2=0x7f050003;
- public static final int cellTable=0x7f050000;
- public static final int positionMatchButton=0x7f050004;
- public static final int positionSucessTextField=0x7f050005;
- public static final int soundMatchButton=0x7f050006;
- public static final int soundSucessTextField=0x7f050007;
+ public static final int buttonRow0=0x7f060001;
+ public static final int buttonRow1=0x7f060002;
+ public static final int buttonRow2=0x7f060003;
+ public static final int cellTable=0x7f060000;
+ public static final int positionMatchButton=0x7f060004;
+ public static final int positionSucessTextField=0x7f060005;
+ public static final int soundMatchButton=0x7f060006;
+ public static final int soundSucessTextField=0x7f060007;
}
public static final class layout {
public static final int positronic=0x7f030000;
}
+ public static final class raw {
+ public static final int c=0x7f040000;
+ public static final int h=0x7f040001;
+ public static final int k=0x7f040002;
+ public static final int l=0x7f040003;
+ public static final int q=0x7f040004;
+ public static final int r=0x7f040005;
+ public static final int s=0x7f040006;
+ public static final int t=0x7f040007;
+ }
public static final class string {
- public static final int app_name=0x7f040000;
+ public static final int app_name=0x7f050000;
}
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -1,36 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
- android:gravity="center_horizontal">
+ android:gravity="center_horizontal" android:layout_centerHorizontal="true">
<TableLayout android:id="@+id/cellTable"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top" android:orientation="vertical"
- android:layout_alignParentTop="true" android:layout_alignParentLeft="false">
+ android:gravity="center_horizontal"
+ android:layout_alignParentTop="true" android:layout_centerHorizontal="true">
- <!--
- <include android:id="@+id/row" layout="@layout/button_row" />
- <include android:id="@+id/row" layout="@layout/button_row" />
- <include android:id="@+id/row" layout="@layout/button_row" />
- -->
-
- <TableRow android:layout_width="wrap_content"
+ <TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal"
android:id="@+id/buttonRow0">
</TableRow>
- <TableRow android:layout_width="wrap_content"
+ <TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal"
- android:id="@+id/buttonRow1">
+ android:id="@+id/buttonRow1" android:layout_below="@+id/buttonRow0">
</TableRow>
- <TableRow android:layout_width="wrap_content"
+ <TableRow android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal"
- android:id="@+id/buttonRow2">
+ android:id="@+id/buttonRow2" android:layout_below="@+id/buttonRow1" android:layout_centerHorizontal="true">
</TableRow>
</TableLayout>
<Button android:id="@+id/positionMatchButton"
android:layout_gravity="center_horizontal|bottom" android:text="Position"
- android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_width="80dp" android:layout_height="80dp"
android:layout_below="@+id/cellTable">
</Button>
@@ -41,7 +36,7 @@
<Button android:id="@+id/soundMatchButton"
android:layout_gravity="center_horizontal|bottom" android:text="Sound"
- android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:layout_width="80dp" android:layout_height="80dp"
android:layout_toRightOf="@+id/positionMatchButton"
android:layout_alignBottom="@+id/positionMatchButton">
</Button>
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
View
Binary file not shown.
@@ -1,3 +1,3 @@
<resources>
- <string name="app_name">ExampleAndroid</string>
+ <string name="app_name">Positronic</string>
</resources>
@@ -1,5 +1,5 @@
package com.example.android
-
+
import _root_.android.app.Activity
import _root_.android.os.Bundle
import _root_.android.widget._
@@ -10,13 +10,15 @@ import _root_.android.graphics.drawable.shapes._
import _root_.android.graphics.RectF
import _root_.android.util._
import _root_.android.view.View._
+import _root_.android.media._
import scala.util._
import game._
import game.util._
-class MainActivity extends Activity with GameView {
+// TODO: instantiate and use a view separately (passing this as a context), because init must happen in onCreate (and we like vals)
+class PositronicActivity extends Activity with GameView {
val delayedRunner = new AndroidDelayedRunner()
val randomGenerator = new Random()
@@ -32,6 +34,8 @@ class MainActivity extends Activity with GameView {
var positionSuccessTextField: TextView = null // Arrgh, how to make this a val
var soundSuccessTextField: TextView = null
+ var controller: GameController = null
+
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
@@ -65,7 +69,7 @@ class MainActivity extends Activity with GameView {
// TODO: use the DI-like stuff for injecting a default delayed runner here
- val controller = new GameController(this, 3, 3, new AndroidDelayedRunner())
+ controller = new GameController(this, 3, 3, new AndroidDelayedRunner())
controller.startGame()
@@ -82,6 +86,11 @@ class MainActivity extends Activity with GameView {
})
}
+ override def onPause() = {
+ controller.pauseGame()
+ super.onPause()
+ }
+
override def startGame(): Unit = {}
@@ -98,11 +107,30 @@ class MainActivity extends Activity with GameView {
square.startAnimation(animation)
}
- override def successfulPositionMatch() = showMomentaryText(positionSuccessTextField, "match")
- override def unsuccessfulPositionMatch() = showMomentaryText(positionSuccessTextField, "no match")
+ def playSound(sound: Sound.Value) = {
+ val soundsToResources = Map(
+ Sound.C -> R.raw.c,
+ Sound.H -> R.raw.h,
+ Sound.K -> R.raw.k,
+ Sound.L -> R.raw.l,
+ Sound.Q -> R.raw.q,
+ Sound.R -> R.raw.r,
+ Sound.S -> R.raw.s,
+ Sound.T -> R.raw.t)
+
+ val mediaPlayer = MediaPlayer.create(this, soundsToResources(sound)) // TODO: don't do this every time
+ mediaPlayer.start();
+ delayedRunner.runDelayedOnce(500, () => {
+ mediaPlayer.stop()
+ mediaPlayer.release()
+ })
+ }
+
+ override def successfulPositionMatch() = showMomentaryText(positionSuccessTextField, "position match")
+ override def unsuccessfulPositionMatch() = showMomentaryText(positionSuccessTextField, "no position match")
- override def successfulSoundMatch() = showMomentaryText(soundSuccessTextField, "match")
- override def unsuccessfulSoundMatch() = showMomentaryText(soundSuccessTextField, "no match")
+ override def successfulSoundMatch() = showMomentaryText(soundSuccessTextField, "sound match")
+ override def unsuccessfulSoundMatch() = showMomentaryText(soundSuccessTextField, "no sound match")
def showMomentaryText(textField: TextView, value: String) = {
Log.d("Positronic", value)
@@ -7,19 +7,29 @@ import java.util.Random
class GameController(val view: GameView, val width: Int, val height: Int, val delayedRunner: DelayedRunner) {
protected val randomGenerator = new Random()
protected var moveHistory = new MoveHistory(Nil)
+
private[this] val numberBack = 1
+ private[this] val sounds = Sound.elements.toList
def startGame() = {
- delayedRunner.runDelayedRepeating(1000, highlightCell)
+ delayedRunner.runDelayedRepeating(1000, makeRandomPlay)
moveHistory = new MoveHistory(Nil)
}
+
+ def pauseGame() = {
+ delayedRunner.clearPendingEvents()
+ }
- def highlightCell(): Unit = {
+ def makeRandomPlay(): Unit = {
val x = randomGenerator.nextInt(width)
val y = randomGenerator.nextInt(height)
- moveHistory = moveHistory.addMove(new Move(new Location(x, y), Sound.Q))
+ val sound = sounds(randomGenerator.nextInt(sounds.size))
+
+ moveHistory = moveHistory.addMove(new Move(new Location(x, y), sound))
+
view.highlightCell(x, y)
+ view.playSound(sound)
}
// TODO: factor out duplication in these two methods
@@ -5,9 +5,11 @@ trait GameView {
def highlightCell(x: Int, y:Int)
+ def playSound(sound: Sound.Value)
+
def successfulPositionMatch()
def unsuccessfulPositionMatch()
def successfulSoundMatch()
- def unsuccessfulSoundMatch()
+ def unsuccessfulSoundMatch()
}
@@ -2,7 +2,7 @@ package game
// Should probably put these accessory classes somewhere more general
object Sound extends Enumeration {
- val L, T, C, K, S, R, H, Q = Value
+ val C, H, K, L, Q, R, S, T = Value
}
case class Location(x: Int, y: Int)
@@ -20,7 +20,9 @@ class MoveHistory(val moves: List[Move]) {
def matchesLocationNMovesAgo(numMovesAgo: Int) = matchesNMovesAgo(numMovesAgo, (current, old) => current.location == old.location)
- def matchesSoundNMovesAgo(numMovesAgo: Int) = matchesNMovesAgo(numMovesAgo, (current, old) => current.sound == old.sound)
+ def matchesSoundNMovesAgo(numMovesAgo: Int) = matchesNMovesAgo(numMovesAgo, (current, old) => {
+ current.sound == old.sound
+ })
private def matchesNMovesAgo(numMovesAgo: Int, matchFunc: (Move, Move) => Boolean): Boolean = {
if (numMovesAgo >= moves.size) {
@@ -1,27 +1,34 @@
package game.util
-import _root_.android.os.Handler
+import _root_.android.os._
class AndroidDelayedRunner extends DelayedRunner {
- val handler = new Handler()
+ val MSG_TOKEN = new Object()
- def runDelayedOnce(delay: Int, func: () => Unit) = {
+ val handler = new Handler()
+
+
+ def runDelayedOnce(delay: Int, func: () => Unit) = {
- val timertask = new Runnable() {
- def run(): Unit = {
- func()
- }
- };
- handler.removeCallbacks(timertask)
- handler.postDelayed(timertask, delay)
- }
+ val timertask = new Runnable() {
+ def run(): Unit = {
+ func()
+ }
+ };
+ handler.postAtTime(timertask, MSG_TOKEN, SystemClock.uptimeMillis() + delay)
+ }
- def runDelayedRepeating(delay: Int, func: () => Unit) = {
+ def runDelayedRepeating(delay: Int, func: () => Unit) = {
- lazy val repeatFunc:() => Unit = () => {
- func()
+ lazy val repeatFunc:() => Unit = () => {
+ func()
+ runDelayedOnce(delay, repeatFunc)
+ }
+
runDelayedOnce(delay, repeatFunc)
}
- runDelayedOnce(delay, repeatFunc)
- }
+
+ def clearPendingEvents() = {
+ handler.removeCallbacksAndMessages(MSG_TOKEN)
+ }
}
@@ -1,6 +1,7 @@
package game.util
trait DelayedRunner {
- def runDelayedOnce(delay: Int, func: () => Unit)
- def runDelayedRepeating(delay: Int, func: () => Unit)
+ def runDelayedOnce(delay: Int, func: () => Unit)
+ def runDelayedRepeating(delay: Int, func: () => Unit)
+ def clearPendingEvents()
}
@@ -19,20 +19,31 @@ class RunTaskAnswer extends Answer[Object] {
class GameControllerTest extends FunSuite with ShouldMatchers {
- test("delays an initial period, then highlights any square") {
- val mockView = mock(classOf[GameView])
- val mockRunner = mock(classOf[DelayedRunner])
+ test("delays an initial period, then highlights any square") {
+ val mockView = mock(classOf[GameView])
+ val mockRunner = mock(classOf[DelayedRunner])
- // Run the scheduled task immediately when scheduled
- doAnswer(new RunTaskAnswer()).when(mockRunner).runDelayedRepeating(anyObject(), anyObject())
+ // Run the scheduled task immediately when scheduled
+ doAnswer(new RunTaskAnswer()).when(mockRunner).runDelayedRepeating(anyObject(), anyObject())
- //val controller = new GameController(mockView, 1, 1, new RunNowRunner(1))
- val controller = new GameController(mockView, 1, 1, mockRunner)
- controller.startGame()
-
- verify(mockRunner).runDelayedRepeating(any(), any())
- verify(mockView).highlightCell(anyInt(), anyInt())
- }
+ val controller = new GameController(mockView, 1, 1, mockRunner)
+ controller.startGame()
+
+ verify(mockView).highlightCell(anyInt(), anyInt())
+ }
+
+ test("delays an initial period, then plays a sound") {
+ val mockView = mock(classOf[GameView])
+ val mockRunner = mock(classOf[DelayedRunner])
+
+ // Run the scheduled task immediately when scheduled
+ doAnswer(new RunTaskAnswer()).when(mockRunner).runDelayedRepeating(anyObject(), anyObject())
+
+ val controller = new GameController(mockView, 1, 1, mockRunner)
+ controller.startGame()
+
+ verify(mockView).playSound(anyObject())
+ }
test("selecting a position match notifies the user of success when it matches") {
val mockView = mock(classOf[GameView])
@@ -57,5 +68,17 @@ class GameControllerTest extends FunSuite with ShouldMatchers {
controller.positionMatchFromView()
verify(mockView).unsuccessfulPositionMatch()
}
+
+ test("pausing game clears events from runner") {
+ // Too techie - what is the behaviour expressed here?
+ val mockView = mock(classOf[GameView])
+ val mockRunner = mock(classOf[DelayedRunner])
+
+ val controller = new GameController(mockView, 1, 1, mockRunner)
+ controller.startGame()
+ controller.pauseGame()
+
+ verify(mockRunner).clearPendingEvents()
+ }
}

0 comments on commit 41ba2ed

Please sign in to comment.