/androidsays/src/com/google/marvin/androidsays/GameView.java

http://eyes-free.googlecode.com/ · Java · 437 lines · 351 code · 38 blank · 48 comment · 81 complexity · b04b530da02f27fe76f3bde79ed4bdc4 MD5 · raw file

  1. /*
  2. * Copyright (C) 2008 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.marvin.androidsays;
  17. import android.content.Context;
  18. import android.content.res.Resources;
  19. import android.graphics.Bitmap;
  20. import android.graphics.BitmapFactory;
  21. import android.graphics.Canvas;
  22. import android.graphics.Color;
  23. import android.graphics.Paint;
  24. import android.graphics.Rect;
  25. import android.graphics.Typeface;
  26. import android.os.Vibrator;
  27. import android.view.MotionEvent;
  28. import android.widget.TextView;
  29. import java.util.ArrayList;
  30. /**
  31. * Handles the game play for mem.
  32. *
  33. * @author clchen@google.com (Charles L. Chen)
  34. */
  35. public class GameView extends TextView {
  36. private Bitmap bgImg;
  37. private Bitmap greenImg;
  38. private Bitmap redImg;
  39. private Bitmap yellowImg;
  40. private Bitmap blueImg;
  41. private Bitmap touchedGreenImg;
  42. private Bitmap touchedRedImg;
  43. private Bitmap touchedYellowImg;
  44. private Bitmap touchedBlueImg;
  45. private int score;
  46. Rect fullScreen;
  47. Rect upperleft;
  48. Rect upperright;
  49. Rect lowerleft;
  50. Rect lowerright;
  51. private Vibrator vibe;
  52. public AndroidSays parent;
  53. private ArrayList<Integer> sequence;
  54. private int currentIndex;
  55. private Rect flash;
  56. public boolean waitingToStart;
  57. // These are timings used to control pauses between actions
  58. // All times are specified in ms
  59. private int initialWaitTime = 500;
  60. private int waitTimeBetweenTones = 310;
  61. private int flashDuration = 270;
  62. private Rect clipRect;
  63. // Used for locking the screen
  64. private boolean screenActive;
  65. private int inputCount;
  66. public GameView(Context context) {
  67. super(context);
  68. parent = (AndroidSays) context;
  69. // android.os.Debug.waitForDebugger();
  70. vibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
  71. sequence = new ArrayList<Integer>();
  72. currentIndex = 0;
  73. screenActive = false;
  74. flash = null;
  75. waitingToStart = true;
  76. inputCount = 0;
  77. Theme customTheme = new Theme();
  78. // Apply a custom theme is there is one and it can be loaded successfully
  79. if ((parent.themeFilename != null) && customTheme.loadTheme(parent.themeFilename)) {
  80. bgImg = BitmapFactory.decodeFile(customTheme.backgroundImg);
  81. greenImg = BitmapFactory.decodeFile(customTheme.greenImg);
  82. redImg = BitmapFactory.decodeFile(customTheme.redImg);
  83. yellowImg = BitmapFactory.decodeFile(customTheme.yellowImg);
  84. blueImg = BitmapFactory.decodeFile(customTheme.blueImg);
  85. touchedGreenImg = BitmapFactory.decodeFile(customTheme.touchedGreenImg);
  86. touchedRedImg = BitmapFactory.decodeFile(customTheme.touchedRedImg);
  87. touchedYellowImg = BitmapFactory.decodeFile(customTheme.touchedYellowImg);
  88. touchedBlueImg = BitmapFactory.decodeFile(customTheme.touchedBlueImg);
  89. parent.sfx.loadSoundFile("[red]", customTheme.redSnd);
  90. parent.sfx.loadSoundFile("[green]", customTheme.greenSnd);
  91. parent.sfx.loadSoundFile("[yellow]", customTheme.yellowSnd);
  92. parent.sfx.loadSoundFile("[blue]", customTheme.blueSnd);
  93. } else {
  94. // Default theme
  95. Resources res = parent.getResources();
  96. bgImg = BitmapFactory.decodeResource(res, R.drawable.bg);
  97. greenImg = BitmapFactory.decodeResource(res, R.drawable.green);
  98. redImg = BitmapFactory.decodeResource(res, R.drawable.red);
  99. yellowImg = BitmapFactory.decodeResource(res, R.drawable.yellow);
  100. blueImg = BitmapFactory.decodeResource(res, R.drawable.blue);
  101. touchedGreenImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  102. touchedRedImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  103. touchedYellowImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  104. touchedBlueImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  105. parent.sfx.loadSoundResource("[red]", R.raw.red_snd);
  106. parent.sfx.loadSoundResource("[green]", R.raw.green_snd);
  107. parent.sfx.loadSoundResource("[yellow]", R.raw.yellow_snd);
  108. parent.sfx.loadSoundResource("[blue]", R.raw.blue_snd);
  109. }
  110. // Fall back to the default theme if anything went wrong
  111. if ( (bgImg == null) ||
  112. (greenImg == null) ||
  113. (redImg == null) ||
  114. (yellowImg == null) ||
  115. (blueImg == null) ||
  116. (touchedGreenImg == null) ||
  117. (touchedRedImg == null) ||
  118. (touchedYellowImg == null) ||
  119. (touchedBlueImg== null) ){
  120. // Default theme
  121. Resources res = parent.getResources();
  122. bgImg = BitmapFactory.decodeResource(res, R.drawable.bg);
  123. greenImg = BitmapFactory.decodeResource(res, R.drawable.green);
  124. redImg = BitmapFactory.decodeResource(res, R.drawable.red);
  125. yellowImg = BitmapFactory.decodeResource(res, R.drawable.yellow);
  126. blueImg = BitmapFactory.decodeResource(res, R.drawable.blue);
  127. touchedGreenImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  128. touchedRedImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  129. touchedYellowImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  130. touchedBlueImg = BitmapFactory.decodeResource(res, R.drawable.flash);
  131. parent.sfx.loadSoundResource("[red]", R.raw.red_snd);
  132. parent.sfx.loadSoundResource("[green]", R.raw.green_snd);
  133. parent.sfx.loadSoundResource("[yellow]", R.raw.yellow_snd);
  134. parent.sfx.loadSoundResource("[blue]", R.raw.blue_snd);
  135. }
  136. gameStart();
  137. }
  138. @Override
  139. public boolean onTouchEvent(final MotionEvent event) {
  140. if (!screenActive) {
  141. return false;
  142. }
  143. if (event.getAction() == MotionEvent.ACTION_DOWN) {
  144. if (waitingToStart) {
  145. gameStart();
  146. return true;
  147. }
  148. inputCount++;
  149. if (inputCount >= sequence.size()) {
  150. screenActive = false;
  151. }
  152. // Screen can always be touched in noise maker mode
  153. if (parent.gameMode == 0) {
  154. screenActive = true;
  155. }
  156. float halfWidth = getWidth() / 2;
  157. float halfHeight = getHeight() / 2;
  158. float x = event.getX();
  159. float y = event.getY();
  160. long[] pattern = {0, 1, 40, 41};
  161. vibe.vibrate(pattern, -1);
  162. int input;
  163. if ((x < halfWidth) && (y < halfHeight)) {
  164. // Upper left
  165. flash = upperleft;
  166. postInvalidate(upperleft);
  167. playSoundEffect("[green]");
  168. input = 0;
  169. } else if ((x >= halfWidth) && (y < halfHeight)) {
  170. // Upper right
  171. flash = upperright;
  172. postInvalidate(upperright);
  173. playSoundEffect("[red]");
  174. input = 1;
  175. } else if ((x < halfWidth) && (y >= halfHeight)) {
  176. // Lower left
  177. flash = lowerleft;
  178. postInvalidate(lowerleft);
  179. playSoundEffect("[yellow]");
  180. input = 2;
  181. } else {
  182. // Lower right
  183. flash = lowerright;
  184. postInvalidate(lowerright);
  185. playSoundEffect("[blue]");
  186. input = 3;
  187. }
  188. // Don't bother evaluating input if the game is in noise maker mode
  189. if (parent.gameMode != 0) {
  190. evalInput(input);
  191. }
  192. return true;
  193. }
  194. return false;
  195. }
  196. private void postInvalidate(Rect r) {
  197. postInvalidate(r.left, r.top, r.right, r.bottom);
  198. }
  199. private void playSoundEffect(String sfx) {
  200. parent.sfx.play(sfx, 0);
  201. }
  202. private void evalInput(final int input) {
  203. Thread t = new Thread() {
  204. @Override
  205. public void run() {
  206. try {
  207. Thread.sleep(1000);
  208. } catch (InterruptedException e) {
  209. // This should not get interrupted
  210. e.printStackTrace();
  211. }
  212. if (currentIndex >= sequence.size()) {
  213. return;
  214. }
  215. if (input == sequence.get(currentIndex)) {
  216. currentIndex++;
  217. if (currentIndex >= sequence.size()) {
  218. currentIndex = 0;
  219. playSoundEffect("[right]");
  220. score++;
  221. playSequence();
  222. }
  223. } else {
  224. screenActive = true;
  225. playSoundEffect("[wrong]");
  226. gameOver();
  227. }
  228. }
  229. };
  230. t.start();
  231. }
  232. public void gameStart() {
  233. waitingToStart = false;
  234. currentIndex = 0;
  235. score = 0;
  236. sequence = new ArrayList<Integer>();
  237. parent.sfx.play("Android says:", 0);
  238. // There are no sequences in noise maker mode
  239. if (parent.gameMode == 0) {
  240. screenActive = true;
  241. return;
  242. }
  243. // Load up an initial sequence for classic mode
  244. if (parent.gameMode == 1) {
  245. for (int i = 0; i < parent.sequenceLength - 1; i++) {
  246. int random = ((int) (Math.random() * 100)) % 4;
  247. sequence.add(random);
  248. }
  249. }
  250. playSequence();
  251. }
  252. private boolean gameover = false;
  253. private void gameOver() {
  254. parent.sfx.play("Game over. Your score is:", 1);
  255. parent.sfx.play(Integer.toString(score), 1);
  256. gameover = true;
  257. waitingToStart = true;
  258. postInvalidate();
  259. }
  260. @Override
  261. protected void onDraw(Canvas canvas) {
  262. if (gameover) {
  263. gameover = false;
  264. parent.recordScore(score);
  265. }
  266. if (fullScreen == null) {
  267. fullScreen = new Rect(0, 0, getWidth() - 1, getHeight() - 1);
  268. upperleft = new Rect(0, 0, getWidth() / 2, getHeight() / 2);
  269. upperright = new Rect(getWidth() / 2, 0, getWidth() - 1, getHeight() / 2);
  270. lowerleft = new Rect(0, getHeight() / 2, getWidth() / 2, getHeight() - 1);
  271. lowerright = new Rect(getWidth() / 2, getHeight() / 2, getWidth() - 1, getHeight() - 1);
  272. clipRect = new Rect();
  273. }
  274. boolean useClipRect = canvas.getClipBounds(clipRect);
  275. canvas.drawBitmap(bgImg, null, fullScreen, null);
  276. if (!useClipRect || Rect.intersects(upperleft, clipRect)) {
  277. if (flash == upperleft) {
  278. canvas.drawBitmap(touchedGreenImg, null, upperleft, null);
  279. unflashLater(upperleft);
  280. } else {
  281. canvas.drawBitmap(greenImg, null, upperleft, null);
  282. }
  283. }
  284. if (!useClipRect || Rect.intersects(upperright, clipRect)) {
  285. if (flash == upperright) {
  286. canvas.drawBitmap(touchedRedImg, null, upperright, null);
  287. unflashLater(upperright);
  288. } else {
  289. canvas.drawBitmap(redImg, null, upperright, null);
  290. }
  291. }
  292. if (!useClipRect || Rect.intersects(lowerleft, clipRect)) {
  293. if (flash == lowerleft) {
  294. canvas.drawBitmap(touchedYellowImg, null, lowerleft, null);
  295. unflashLater(lowerleft);
  296. } else {
  297. canvas.drawBitmap(yellowImg, null, lowerleft, null);
  298. }
  299. }
  300. if (!useClipRect || Rect.intersects(lowerright, clipRect)) {
  301. if (flash == lowerright) {
  302. canvas.drawBitmap(touchedBlueImg, null, lowerright, null);
  303. unflashLater(lowerright);
  304. } else {
  305. canvas.drawBitmap(blueImg, null, lowerright, null);
  306. }
  307. }
  308. String mytext = Integer.toString(score);
  309. int x = getWidth() / 2;
  310. int y = getHeight() / 2;
  311. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  312. paint.setColor(Color.BLACK);
  313. paint.setTextAlign(Paint.Align.CENTER);
  314. paint.setTextSize(36);
  315. paint.setTypeface(Typeface.DEFAULT_BOLD);
  316. y -= paint.ascent() / 2;
  317. canvas.drawText(mytext, x, y, paint);
  318. }
  319. private void unflashLater(Rect r) {
  320. postInvalidateDelayed(flashDuration, r.left, r.top, r.right, r.bottom);
  321. flash = null;
  322. }
  323. private void playSequence() {
  324. (new Thread(new SequencePlayer())).start();
  325. }
  326. // Generates sequence either by adding one random number to the end (classic)
  327. // or by generating a brand new sequence of a set length (challenge).
  328. private void generateSequence() {
  329. currentIndex = 0;
  330. if (parent.gameMode == 1) {
  331. int random = ((int) (Math.random() * 100)) % 4;
  332. sequence.add(random);
  333. } else {
  334. sequence = new ArrayList<Integer>();
  335. for (int i = 0; i < parent.sequenceLength; i++) {
  336. int random = ((int) (Math.random() * 100)) % 4;
  337. sequence.add(random);
  338. }
  339. }
  340. }
  341. /**
  342. * Plays back the sequence This needs to be done in a different thread because
  343. * it uses sleep to keep the visual flashing in sync with the sounds.
  344. */
  345. public class SequencePlayer implements Runnable {
  346. public void run() {
  347. screenActive = false;
  348. generateSequence();
  349. try {
  350. Thread.sleep(initialWaitTime);
  351. } catch (InterruptedException e) {
  352. // Nothing needs to be done if the sleep is interrupted.
  353. e.printStackTrace();
  354. }
  355. for (int i = 0; i < sequence.size(); i++) {
  356. // TODO(clchen): - find a more graceful way of stopping the sound
  357. if (parent.halt) {
  358. inputCount = 0;
  359. screenActive = true;
  360. return;
  361. }
  362. int delay = parent.speedPrefDelay;
  363. // Negative speed_pref_delay means scaling
  364. if (delay < 0) {
  365. delay = 300 - (sequence.size() * 10);
  366. }
  367. // Scaled delay must be 0 or positive
  368. if (delay < 0) {
  369. delay = 0;
  370. }
  371. try {
  372. Thread.sleep(waitTimeBetweenTones + delay);
  373. } catch (InterruptedException e) {
  374. // Nothing needs to be done if the sleep is interrupted.
  375. e.printStackTrace();
  376. }
  377. if (sequence.get(i) == 0) {
  378. flash = upperleft;
  379. postInvalidate();
  380. playSoundEffect("[green]");
  381. } else if (sequence.get(i) == 1) {
  382. flash = upperright;
  383. postInvalidate();
  384. playSoundEffect("[red]");
  385. } else if (sequence.get(i) == 2) {
  386. flash = lowerleft;
  387. postInvalidate();
  388. playSoundEffect("[yellow]");
  389. } else {
  390. flash = lowerright;
  391. postInvalidate();
  392. playSoundEffect("[blue]");
  393. }
  394. }
  395. inputCount = 0;
  396. screenActive = true;
  397. }
  398. }
  399. }