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