My favorites | Sign in
Logo
                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
/*
* Copyright (C) 2008 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.divideandconquer;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.*;
import android.graphics.drawable.GradientDrawable;
import android.os.Debug;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

import java.util.List;
import java.util.ArrayList;

/**
* Handles the visual display and touch input for the game.
*/
public class DivideAndConquerView extends View implements BallEngine.BallEventCallBack {

static final int BORDER_WIDTH = 10;

// this needs to match size of ball drawable
static final float BALL_RADIUS = 5f;

static final float BALL_SPEED = 80f;

// if true, will profile the drawing code during each animating line and export
// the result to a file named 'BallsDrawing.trace' on the sd card
// this file can be pulled off and profiled with traceview
// $ adb pull /sdcard/BallsDrawing.trace .
// traceview BallsDrawing.trace
private static final boolean PROFILE_DRAWING = false;

private boolean mDrawingProfilingStarted = false;

private final Paint mPaint;
private BallEngine mEngine;

private Mode mMode = Mode.Paused;

private BallEngineCallBack mCallback;

// interface for starting a line
private DirectionPoint mDirectionPoint = null;
private Bitmap mBallBitmap;
private float mBallBitmapRadius;
private final Bitmap mExplosion1;
private final Bitmap mExplosion2;
private final Bitmap mExplosion3;

/**
* Callback notifying of events related to the ball engine.
*/
static interface BallEngineCallBack {

/**
* The engine has its dimensions and is ready to go.
* @param ballEngine The ball engine.
*/
void onEngineReady(BallEngine ballEngine);

/**
* A ball has hit a moving line.
* @param ballEngine The engine.
* @param x The x coordinate of the ball.
* @param y The y coordinate of the ball.
*/
void onBallHitsMovingLine(BallEngine ballEngine, float x, float y);

/**
* A line made it to the edges of its region, splitting off a new region.
* @param ballEngine The engine.
*/
void onAreaChange(BallEngine ballEngine);
}

/**
* @return The ball engine associated with the game.
*/
public BallEngine getEngine() {
return mEngine;
}

/**
* Keeps track of the mode of this view.
*/
enum Mode {

/**
* The balls are bouncing around.
*/
Bouncing,

/**
* The animation has stopped and the balls won't move around. The user
* may not unpause it; this is used to temporarily stop games between
* levels, or when the game is over and the activity places a dialog up.
*/
Paused,

/**
* Same as {@link #Paused}, but paints the word 'touch to unpause' on
* the screen, so the user knows he/she can unpause the game.
*/
PausedByUser
}

public DivideAndConquerView(Context context, AttributeSet attrs) {
super(context, attrs);

mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.BLACK);

// so we can see the back key
setFocusableInTouchMode(true);

drawBackgroundGradient();

mBallBitmap = BitmapFactory.decodeResource(
context.getResources(),
R.drawable.ball);

mBallBitmapRadius = ((float) mBallBitmap.getWidth()) / 2f;

mExplosion1 = BitmapFactory.decodeResource(
context.getResources(),
R.drawable.explosion1);
mExplosion2 = BitmapFactory.decodeResource(
context.getResources(),
R.drawable.explosion2);
mExplosion3 = BitmapFactory.decodeResource(
context.getResources(),
R.drawable.explosion3);
}

final GradientDrawable mBackgroundGradient =
new GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
new int[]{Color.RED, Color.YELLOW});

void drawBackgroundGradient() {
setBackgroundDrawable(mBackgroundGradient);
}

/**
* Set the callback that will be notified of events related to the ball
* engine.
* @param callback The callback.
*/
public void setCallback(BallEngineCallBack callback) {
mCallback = callback;
}

@Override
protected void onSizeChanged(int i, int i1, int i2, int i3) {
super.onSizeChanged(i, i1, i2,
i3);

// this should only happen once when the activity is first launched.
// we could be smarter about saving / restoring across activity
// lifecycles, but for now, this is good enough to handle in game play,
// and most cases of navigating away with the home key and coming back.
mEngine = new BallEngine(
BORDER_WIDTH, getWidth() - BORDER_WIDTH,
BORDER_WIDTH, getHeight() - BORDER_WIDTH,
BALL_SPEED,
BALL_RADIUS);
mEngine.setCallBack(this);
mCallback.onEngineReady(mEngine);
}


/**
* @return the current mode of operation.
*/
public Mode getMode() {
return mMode;
}

/**
* Set the mode of operation.
* @param mode The mode.
*/
public void setMode(Mode mode) {
mMode = mode;

if (mMode == Mode.Bouncing && mEngine != null) {
// when starting up again, the engine needs to know what 'now' is.
final long now = SystemClock.elapsedRealtime();
mEngine.setNow(now);

mExplosions.clear();
invalidate();
}
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// the first time the user hits back while the balls are moving,
// we'll pause the game. but if they hit back again, we'll do the usual
// (exit the activity)
if (keyCode == KeyEvent.KEYCODE_BACK && mMode == Mode.Bouncing) {
setMode(Mode.PausedByUser);
return true;
}
return super.onKeyDown(keyCode, event);
}

@Override
public boolean onTouchEvent(MotionEvent motionEvent) {

if (mMode == Mode.PausedByUser) {
// touching unpauses when the game was paused by the user.
setMode(Mode.Bouncing);
return true;
} else if (mMode == Mode.Paused) {
return false;
}

final float x = motionEvent.getX();
final float y = motionEvent.getY();
switch(motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mEngine.canStartLineAt(x, y)) {
mDirectionPoint =
new DirectionPoint(x, y);
}
return true;
case MotionEvent.ACTION_MOVE:
if (mDirectionPoint != null) {
mDirectionPoint.updateEndPoint(x, y);
} else if (mEngine.canStartLineAt(x, y)) {
mDirectionPoint =
new DirectionPoint(x, y);
}
return true;
case MotionEvent.ACTION_UP:
if (mDirectionPoint != null) {
switch (mDirectionPoint.getDirection()) {
case Unknown:
// do nothing
break;
case Horizonal:
mEngine.startHorizontalLine(SystemClock.elapsedRealtime(),
mDirectionPoint.getX(), mDirectionPoint.getY());
if (PROFILE_DRAWING) {
if (!mDrawingProfilingStarted) {
Debug.startMethodTracing("BallsDrawing");
mDrawingProfilingStarted = true;
}
}
break;
case Vertical:
mEngine.startVerticalLine(SystemClock.elapsedRealtime(),
mDirectionPoint.getX(), mDirectionPoint.getY());
if (PROFILE_DRAWING) {
if (!mDrawingProfilingStarted) {
Debug.startMethodTracing("BallsDrawing");
mDrawingProfilingStarted = true;
}
}
break;
}
}
mDirectionPoint = null;
return true;
case MotionEvent.ACTION_CANCEL:
mDirectionPoint = null;
return true;
}
return false;
}

/** {@inheritDoc} */
public void onBallHitsBall(Ball ballA, Ball ballB) {

}

/** {@inheritDoc} */
public void onBallHitsLine(long when, Ball ball, AnimatingLine animatingLine) {
mCallback.onBallHitsMovingLine(mEngine, ball.getX(), ball.getY());

mExplosions.add(
new Explosion(
when,
ball.getX(), ball.getY(),
mExplosion1, mExplosion2, mExplosion3));

}

static class Explosion {
private long mLastUpdate;
private long mProgress = 0;
private final float mX;
private final float mY;

private final Bitmap mExplosion1;
private final Bitmap mExplosion2;
private final Bitmap mExplosion3;
private final float mRadius;

Explosion(long mLastUpdate, float mX, float mY,
Bitmap explosion1, Bitmap explosion2, Bitmap explosion3) {
this.mLastUpdate = mLastUpdate;
this.mX = mX;
this.mY = mY;
this.mExplosion1 = explosion1;
this.mExplosion2 = explosion2;
this.mExplosion3 = explosion3;
mRadius = ((float) mExplosion1.getWidth()) / 2f;

}

public void update(long now) {
mProgress += (now - mLastUpdate);
mLastUpdate = now;
}

public void setNow(long now) {
mLastUpdate = now;
}

public void draw(Canvas canvas, Paint paint) {
if (mProgress < 80L) {
canvas.drawBitmap(mExplosion1, mX - mRadius, mY - mRadius, paint);
} else if (mProgress < 160L) {
canvas.drawBitmap(mExplosion2, mX - mRadius, mY - mRadius, paint);
} else if (mProgress < 400L) {
canvas.drawBitmap(mExplosion3, mX - mRadius, mY - mRadius, paint);
}
}

public boolean done() {
return mProgress > 700L;
}
}

private ArrayList<Explosion> mExplosions = new ArrayList<Explosion>();


@Override
protected void onDraw(Canvas canvas) {
boolean newRegion = false;

if (mMode == Mode.Bouncing) {

// handle the ball engine
final long now = SystemClock.elapsedRealtime();
newRegion = mEngine.update(now);

if (newRegion) {
mCallback.onAreaChange(mEngine);

// reset back to full alpha bg color
drawBackgroundGradient();
}

if (PROFILE_DRAWING) {
if (newRegion && mDrawingProfilingStarted) {
mDrawingProfilingStarted = false;
Debug.stopMethodTracing();
}
}

// the X-plosions
for (int i = 0; i < mExplosions.size(); i++) {
final Explosion explosion = mExplosions.get(i);
explosion.update(now);
}


}

for (int i = 0; i < mEngine.getRegions().size(); i++) {
BallRegion region = mEngine.getRegions().get(i);
drawRegion(canvas, region);
}

for (int i = 0; i < mExplosions.size(); i++) {
final Explosion explosion = mExplosions.get(i);
explosion.draw(canvas, mPaint);
// TODO prune explosions that are done
}


if (mMode == Mode.PausedByUser) {
drawPausedText(canvas);
} else if (mMode == Mode.Bouncing) {
// keep em' bouncing!
invalidate();
}
}

/**
* Pain the text instructing the user how to unpause the game.
*/
private void drawPausedText(Canvas canvas) {
mPaint.setColor(Color.BLACK);
mPaint.setAntiAlias(true);
mPaint.setTextSize(
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
20,
getResources().getDisplayMetrics()));
final String unpauseInstructions = getContext().getString(R.string.unpause_instructions);
canvas.drawText(unpauseInstructions, getWidth() / 5, getHeight() / 2, mPaint);
mPaint.setAntiAlias(false);
}

private RectF mRectF = new RectF();

/**
* Draw a ball region.
*/
private void drawRegion(Canvas canvas, BallRegion region) {

// draw fill rect to offset against background
mPaint.setColor(Color.LTGRAY);

mRectF.set(region.getLeft(), region.getTop(),
region.getRight(), region.getBottom());
canvas.drawRect(mRectF, mPaint);


//draw an outline
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
canvas.drawRect(mRectF, mPaint);
mPaint.setStyle(Paint.Style.FILL); // restore style

// draw each ball
for (Ball ball : region.getBalls()) {
// canvas.drawCircle(ball.getX(), ball.getY(), BALL_RADIUS, mPaint);
canvas.drawBitmap(
mBallBitmap,
ball.getX() - mBallBitmapRadius,
ball.getY() - mBallBitmapRadius,
mPaint);
}

// draw the animating line
final AnimatingLine al = region.getAnimatingLine();
if (al != null) {
drawAnimatingLine(canvas, al);
}
}

private static int scaleToBlack(int component, float percentage) {
// return (int) ((1f - percentage*0.4f) * component);

return (int) (percentage * 0.6f * (0xFF - component) + component);
}

/**
* Draw an animating line.
*/
private void drawAnimatingLine(Canvas canvas, AnimatingLine al) {

final float perc = al.getPercentageDone();
final int color = Color.RED;
mPaint.setColor(Color.argb(
0xFF,
scaleToBlack(Color.red(color), perc),
scaleToBlack(Color.green(color), perc),
scaleToBlack(Color.blue(color), perc)
));
switch (al.getDirection()) {
case Horizontal:
canvas.drawLine(
al.getStart(), al.getPerpAxisOffset(),
al.getEnd(), al.getPerpAxisOffset(),
mPaint);
break;
case Vertical:
canvas.drawLine(
al.getPerpAxisOffset(), al.getStart(),
al.getPerpAxisOffset(), al.getEnd(),
mPaint);
break;
}
}
}
Show details Hide details

Change log

r152 by kros...@google.com on Apr 13, 2009   Diff
Whoops, never commited the "explosion"
update from the holidays.  Was going to
clean it up a bit, but never got around to
it.. better to commit it now then lose
source forever :)
Go to: 
Project members, sign in to write a code review

Older revisions

r149 by kros...@google.com on Jan 03, 2009   Diff
v 1.2: improved graphics, balls can
collide

r134 by kros...@google.com on Oct 31, 2008   Diff
Added 'shrink to fit', removed EULA.
TODO: the way the main view knows that
the shrinkng of a region is done is
kinda hacky, I will improve that at
some point.
r40 by kros...@google.com on Sep 10, 2008   Diff
initial checkin, see README.txt for
overview
All revisions of this file

File info

Size: 16341 bytes, 502 lines