PageRenderTime 47ms CodeModel.GetById 16ms app.highlight 26ms RepoModel.GetById 2ms app.codeStats 0ms

/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 */
 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}