I have used the tutorial from Here
and successfully made a similar puzzle game (with custom layout and imaged tiles)
My problem now is to recognize when the user finishes the puzzle..
I am and android developer, but this cocos2d is very new to me.
I have downloaded the source code to check what the writer of the tutorial have done to find when the user has finished the puzzle, but his code for this part is not working, and I need to find a way to check after every move if the user has finished or not..
if it were android I would not have any problem with it..
here is the relevant code:
public void generateTiles(){
//We create a Node element to hold all our tiles
CCNode tilesNode = CCNode.node();
tilesNode.setTag(TILE_NODE_TAG);
addChild(tilesNode);
float scalefactor ; // a value we compute to help scale our tiles
int useableheight ;
int tileIndex = 0 ;
//We attempt to calculate the right size for the tiles given the screen size and
//space left after adding the status label at the top
int nextval ;
int[] tileNumbers = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};//{12,1,10,2,7,11,4,14,5,0,9,15,8,13,6,3}; //random but solvable sequence of numbers
//TILE_SQUARE_SIZE = (int) ((screenSize.height *generalscalefactor)/NUM_ROWS) ;
int useablewidth = (int) (screenSize.width - statusLabel.getContentSize().width*generalscalefactor ) ;
useableheight = (int) (screenSize.height - 40*generalscalefactor - statusLabel.getContentSize().height * 1.3f*generalscalefactor) ;
TILE_SQUARE_SIZE = (int) Math.min((useableheight/NUM_ROWS) , (useablewidth/NUM_COLUMNS)) ;
toppoint = (int) (useableheight - (TILE_SQUARE_SIZE / 2) + 30*generalscalefactor) ;
scalefactor = TILE_SQUARE_SIZE / 125.0f ;
topleft = (int) ((TILE_SQUARE_SIZE / 2) + 15*generalscalefactor) ;
for (int j = toppoint ; j > toppoint - (TILE_SQUARE_SIZE * NUM_ROWS); j-= TILE_SQUARE_SIZE){
for (int i = topleft ; i < (topleft - 5*generalscalefactor) + (TILE_SQUARE_SIZE * NUM_COLUMNS); i+= TILE_SQUARE_SIZE){
if (tileIndex >= (NUM_ROWS * NUM_COLUMNS)) {
break ;
}
nextval = tileNumbers[tileIndex ];
//Generate tile by it's number and it's subfolder
Tile tile = new Tile();
tile.setIndex(nextval);
tile.setSprite(CCSprite.sprite(subFolder+"/"+subFolder+"_tile_"+String.valueOf(nextval+1)+".jpg"));
tiles.add(tile);
//CCSprite tile = CCSprite.sprite(subFolder+"/"+subFolder+"_tile_"+String.valueOf(nextval+1)+".jpg");
CCNodeExt eachNode = new CCNodeExt();
eachNode.setContentSize(tile.getSprite().getContentSize());
//
//Layout Node based on calculated postion
eachNode.setPosition(i, j);
eachNode.setNodeText(nextval + "");
//Add Tile number
if(showNumbers == true)
{
CCBitmapFontAtlas tileNumber = CCBitmapFontAtlas.bitmapFontAtlas ("00", "bionic.fnt");
tileNumber.setScale(1.4f);
eachNode.setScale(scalefactor);
eachNode.addChild(tile.getSprite(),1,1);
tileNumber.setString(nextval + "");
eachNode.addChild(tileNumber,2 );
}
else
{
eachNode.setScale(scalefactor);
eachNode.addChild(tile.getSprite(),1,1);
}
if( nextval != 0){
tilesNode.addChild(eachNode,1,nextval);
}else {
emptyPosition = CGPoint.ccp(i, j);
}
//Add each Node to a HashMap to note its location
tileIndex++;
}
}
}
#Override
public boolean ccTouchesBegan(MotionEvent event)
{
//Get touch location cordinates
CGPoint location = CCDirector.sharedDirector().convertToGL(CGPoint.ccp(event.getX(), event.getY()));
CGRect spritePos ;
CCNode tilesNode = (CCNode) getChildByTag(TILE_NODE_TAG) ;
//ccMacros.CCLOG("Began", "Began : " + location.x + " : " );
//We loop through each of the tiles and get its cordinates
for (int i = 1 ; i < (NUM_ROWS * NUM_COLUMNS); i++){
CCNodeExt eachNode = (CCNodeExt) tilesNode.getChildByTag(i) ;
//Log.d(String.valueOf(eachNode), String.valueOf(tiles.get(i).getIndex()));
//we construct a rectangle covering the current tiles cordinates
spritePos = CGRect.make(
eachNode.getPosition().x - (eachNode.getContentSize().width*generalscalefactor/2.0f),
eachNode.getPosition().y - (eachNode.getContentSize().height*generalscalefactor/2.0f),
eachNode.getContentSize().width*generalscalefactor ,
eachNode.getContentSize().height*generalscalefactor );
//Check if the user's touch falls inside the current tiles cordinates
if(spritePos.contains(location.x, location.y)){
//ccMacros.CCLOG("Began Touched Node", "Began touched : " + eachNode.getNodeText());
slideCallback(eachNode); // if yes, we pass the tile for sliding.
}
}
return true ;
}
public void slideCallback(CCNodeExt thenode) {
CGPoint nodePosition = thenode.getPosition();
//Determine the position to slide the tile to .. ofcourse only if theres an empty space beside it
if((nodePosition.x - TILE_SQUARE_SIZE)== emptyPosition.x && nodePosition.y == emptyPosition.y){
slideTile("Left", thenode,true);
}else if((nodePosition.x + TILE_SQUARE_SIZE) == emptyPosition.x && nodePosition.y == emptyPosition.y){
slideTile("Right", thenode,true);
}else if((nodePosition.x)== emptyPosition.x && nodePosition.y == (emptyPosition.y + TILE_SQUARE_SIZE )){
slideTile("Down", thenode,true);
}else if((nodePosition.x )== emptyPosition.x && nodePosition.y == (emptyPosition.y - TILE_SQUARE_SIZE)){
slideTile("Up", thenode,true);
}else{
slideTile("Unmovable", thenode,false);
}
}
public void slideTile(String direction, CCNodeExt thenode, boolean move){
CCBitmapFontAtlas moveslabel = (CCBitmapFontAtlas) getChildByTag(MOVES_LABEL_TAG);
if(move && !gameover){
// Increment the moves label and animate the tile
moves ++ ;
moveslabel.setString("Moves : " + CCFormatter.format("%03d", moves ));
//Update statuslabel
statusLabel.setString("Tile : " + thenode.getNodeText() + " -> " + direction);
//Animate the tile to slide it
CGPoint nodePosition = thenode.getPosition();
//Log.d("current pos",String.valueOf(nodePosition));
CGPoint tempPosition = emptyPosition ;
//Log.d("go to pos",String.valueOf(tempPosition));
CCMoveTo movetile = CCMoveTo.action(0.4f, tempPosition);
CCSequence movetileSeq = CCSequence.actions(movetile, CCCallFuncN.action(this, "handleWin"));
thenode.runAction(movetileSeq);
emptyPosition = nodePosition ;
//Play a sound
appcontext = CCDirector.sharedDirector().getActivity();
SoundEngine.sharedEngine().playEffect(appcontext, R.raw.tileclick);
thenode.runAction(movetileSeq);
}else{
}
}
public void handleWin(Object sender){
if(checkCorrect()){
gameover = true ;
Log.d("Game "+String.valueOf(gameover), "VICTORY!");
SoundEngine.sharedEngine().playEffect(appcontext, R.raw.tileclick);
//WinCallback(sender);
}
}
//This method checks if the puzzle has been correctly solved.
public boolean checkCorrect(){
CCNode tileNode = (CCNode) getChildByTag(TILE_NODE_TAG);
int nodeindex = 1 ;
boolean result = false;
int rowindex = 0 ;
for (int j = toppoint ; j > toppoint - (TILE_SQUARE_SIZE * NUM_ROWS); j-= TILE_SQUARE_SIZE){
for (int i = topleft ; i < (topleft - 5*generalscalefactor) + (TILE_SQUARE_SIZE * NUM_COLUMNS); i+= TILE_SQUARE_SIZE){
Log.d(String.valueOf(tileNode.getChildByTag(nodeindex).getPosition().x), String.valueOf(tileNode.getChildByTag(nodeindex).getPosition().y));
Log.d(String.valueOf(i), String.valueOf(j));
if(tileNode.getChildByTag(nodeindex).getPosition().x == i && tileNode.getChildByTag(nodeindex).getPosition().y == j ){
//Log.d(String.valueOf(tileNode.getChildByTag(nodeindex).getPosition().x), String.valueOf(tileNode.getChildByTag(nodeindex).getPosition().y));
//Log.d(String.valueOf(i), String.valueOf(j));
result = true ;
Log.d("Game "+String.valueOf(result), "result");
}else{
//Log.d("Game "+String.valueOf(false), "result");
return false ;
}
nodeindex++ ;
//Log.d("nodeindex "+String.valueOf(nodeindex), "index total");
if(nodeindex == (NUM_ROWS * NUM_COLUMNS)){
Log.d("Game "+String.valueOf(result), "result");
return result ;
}
}
}
rowindex = 0 ;
Log.d("Game "+String.valueOf(result), "result");
return result ;
}
}
basically I just need the last method to be fixed for me, all of the other code is just for reference and understanding of the last needed part..
OK, since I have solved it, and I know it is kind of hard to get good examples and answers on this subject, I will share the solution here:
Add this line as global declaration:
//Array to hold the correct order of tiles so we can compare..
private ArrayList<Tile> tilesCompleted = new ArrayList<Tile>();
and change this method:
public void generateTiles(){
//We create a Node element to hold all our tiles
tilesNode.setTag(TILE_NODE_TAG);
addChild(tilesNode);
float scalefactor ; // a value we compute to help scale our tiles
int useableheight ;
int tileIndex = 0 ;
//We attempt to calculate the right size for the tiles given the screen size and
//space left after adding the status label at the top
int nextval ;
//{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};//
int[] tileNumbers = {12,1,10,2,7,11,4,14,5,0,9,15,8,13,6,3}; //random but solvable sequence of numbers
//TILE_SQUARE_SIZE = (int) ((screenSize.height *generalscalefactor)/NUM_ROWS) ;
int useablewidth = (int) (screenSize.width - statusLabel.getContentSize().width*generalscalefactor ) ;
useableheight = (int) (screenSize.height - 40*generalscalefactor - statusLabel.getContentSize().height * 1.3f*generalscalefactor) ;
TILE_SQUARE_SIZE = (int) Math.min((useableheight/NUM_ROWS) , (useablewidth/NUM_COLUMNS)) ;
toppoint = (int) (useableheight - (TILE_SQUARE_SIZE / 2) + 30*generalscalefactor) ;
scalefactor = TILE_SQUARE_SIZE / 125.0f ;
topleft = (int) ((TILE_SQUARE_SIZE / 2) + 15*generalscalefactor) ;
for (int j = toppoint ; j > toppoint - (TILE_SQUARE_SIZE * NUM_ROWS); j-= TILE_SQUARE_SIZE){
for (int i = topleft ; i < (topleft - 5*generalscalefactor) + (TILE_SQUARE_SIZE * NUM_COLUMNS); i+= TILE_SQUARE_SIZE){
if (tileIndex >= (NUM_ROWS * NUM_COLUMNS)) {
break ;
}
nextval = tileNumbers[tileIndex ];
//Generate tile by it's number and it's subfolder
Tile tile = new Tile();
tile.setIndex(nextval);
tile.setSprite(CCSprite.sprite(subFolder+"/"+subFolder+"_tile_"+String.valueOf(nextval+1)+".jpg"));
tiles.add(tile);
//This is the solution
if(tileIndex > 0)
{
tile.setPositionX(i);
tile.setPositionY(j);
tile.setIndex(tileIndex);
tilesCompleted.add(tile);
}
else
tilesCompleted.add(tile);
//CCSprite tile = CCSprite.sprite(subFolder+"/"+subFolder+"_tile_"+String.valueOf(nextval+1)+".jpg");
CCNodeExt eachNode = new CCNodeExt();
eachNode.setContentSize(tile.getSprite().getContentSize());
//
//Layout Node based on calculated postion
eachNode.setPosition(i, j);
eachNode.setNodeText(nextval + "");
//Add Tile number
if(showNumbers == true)
{
CCBitmapFontAtlas tileNumber = CCBitmapFontAtlas.bitmapFontAtlas ("00", "bionic.fnt");
tileNumber.setScale(1.4f);
eachNode.setScale(scalefactor);
eachNode.addChild(tile.getSprite(),1,1);
tileNumber.setString(nextval + "");
eachNode.addChild(tileNumber,2 );
}
else
{
eachNode.setScale(scalefactor);
eachNode.addChild(tile.getSprite(),1,1);
}
if( nextval != 0){
tilesNode.addChild(eachNode,1,nextval);
}else {
emptyPosition = CGPoint.ccp(i, j);
}
//Add each Node to a HashMap to note its location
tileIndex++;
}
}
}
And now change this:
//This method checks if the puzzle has been correctly solved.
public boolean checkCorrect(){
//CCNode tileNodes = (CCNode) getChildByTag(TILE_NODE_TAG);
int nodeindex = 0 ;
boolean result = false;
int rowindex = 0 ;
for (int j = toppoint ; j > toppoint - (TILE_SQUARE_SIZE * NUM_ROWS); j-= TILE_SQUARE_SIZE){
if(nodeindex > 0)
{
//Log.d("y", String.valueOf(tilesNode.getChildByTag(nodeindex).getPosition().y));
//Log.d("J", String.valueOf(tilesCompleted.get(nodeindex).getPositionY()));
}
for (int i = topleft ; i < (topleft - 5*generalscalefactor) + (TILE_SQUARE_SIZE * NUM_COLUMNS); i+= TILE_SQUARE_SIZE){
if(nodeindex > 0)
{
//Log.d("x", String.valueOf(tilesNode.getChildByTag(nodeindex).getPosition().x));
//Log.d("i", String.valueOf(tilesCompleted.get(nodeindex).getPositionX()));
}
if(nodeindex>0)
{
if(tilesNode.getChildByTag(nodeindex).getPosition().x == tilesCompleted.get(nodeindex).getPositionX()
&& tilesNode.getChildByTag(nodeindex).getPosition().y == tilesCompleted.get(nodeindex).getPositionY()){
//Log.d(String.valueOf(tileNode.getChildByTag(nodeindex).getPosition().x), String.valueOf(tileNode.getChildByTag(nodeindex).getPosition().y));
//Log.d(String.valueOf(i), String.valueOf(j));
result = true ;
Log.d("Game "+String.valueOf(result), "result");
}else{
//Log.d("Game "+String.valueOf(false), "result");
return false ;
}
}
nodeindex++ ;
//Log.d("nodeindex "+String.valueOf(nodeindex), "index total");
if(nodeindex == (NUM_ROWS * NUM_COLUMNS)){
Log.d("Game "+String.valueOf(result), "result");
return result ;
}
}
}
rowindex = 0 ;
Log.d("Game "+String.valueOf(result), "result");
return result ;
}
}
GOOD LUCK :)
Related
in order to interpolate 2 values, I can use
lerp(int a, int b) {
return (a + b) / 2;
}
Now imagine I've an array(1, 30, 100, 300) and I want to interpolate it to array in size N (N=10 for example).
If N == 7, then:
1,15,30,65,100,200,300
I've no idea how to interpolate 4 values to be 10. I need a method that looks like:
interpolate(fina int[] input, final int newSize) {
int[] res = new int[newSize];
...
return res;
}
that works even on my example above with newSize of 7, 10 or whatever.
Any idea how to implement it?
SOLVED.
public static double[] interpolate(double[] x, int newLength) {
double[] y = null;
if (newLength > 0) {
int N = x.length;
if (N == 1) {
y = new double[1];
y[0] = x[0];
return y;
} else if (newLength == 1) {
y = new double[1];
int ind = (int) Math.floor(N * 0.5 + 0.5);
ind = Math.max(1, ind);
ind = Math.min(ind, N);
y[0] = x[ind - 1];
return y;
} else {
y = new double[newLength];
double Beta = ((double) newLength) / N;
double newBeta = 1.0;
if (newLength > 2)
newBeta = (N - 2.0) / (newLength - 2.0);
y[0] = x[0];
y[1] = x[1];
y[newLength - 1] = x[N - 1];
double tmp, alpha;
int i, j;
for (i = 2; i <= newLength - 2; i++) {
tmp = 1.0 + (i - 1) * newBeta;
j = (int) Math.floor(tmp);
alpha = tmp - j;
y[i] = (1.0 - alpha) * x[Math.max(0, j)] + alpha * x[Math.min(N - 1, j + 1)];
}
}
}
return y;
}
/**
* Find the maximum of all elements in the array, ignoring elements that are NaN.
* #param data
* #return
*/
public static double max(double[] data) {
double max = Double.NaN;
for (int i = 0; i < data.length; i++) {
if (Double.isNaN(data[i]))
continue;
if (Double.isNaN(max) || data[i] > max)
max = data[i];
}
return max;
}
public static int max(int[] data) {
int max = data[0];
for (int i = 1; i < data.length; i++) {
if (data[i] > max)
max = data[i];
}
return max;
}
My Requirement : I need to animate a 7*16 LED Display by passing frames through the Android App over BluetoothLE. I have created the design of the display on the app and added empty views with gradient drawable background to them. The colour of these views need to change when my touch moves into them. Adding a touch listener to each view isn't going to help in my case.
What I have achieved : I have a large number of views (100+) added programmatically with a tag set to each of them. I have set an OnTouch Event Handler for the parent view in which these views have been added.
By tracking the absolute coordinates of the touch event (x and y) and comparing with the absolute bounds of a few individual views that I am looping in the touch event handler, I am able to detect hover like touch move (out of bounds to in bounds) over 3-4 views properly.
I have referred the solution from https://stackoverflow.com/a/21903640
Where I am stuck : However, when I try to increase the loop size to cover all the added views, the app response slows down and hover detection fails on most of the views. I know this is happening because of heavy computation in the OnTouch Event Handler which I am not supposed to do.
What I need : I need an improvement on this solution in terms of performance or an alternative way to go about reaching my goal.
Code Snippet
void DrawScreen()
{
for (int column = 0; column < 8; column++)
{
for (int row = 0; row < 17; row++)
{
relativeLayout.AddView(DrawRect(row, column));
}
}
}
View DrawRect(int row, int column)
{
View customView = new View(Context);
GradientDrawable shape = new GradientDrawable();
shape.SetShape(ShapeType.Rectangle);
shape.SetCornerRadii(new float[] { 10, 10, 10, 10, 10, 10, 10, 10 });
shape.SetColor(Color.ParseColor("#3F0000"));
RelativeLayout.LayoutParams param = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
param.LeftMargin = ((column-1) * (width + h_spacing)) + h_spacing;
param.Width = width;
param.Height = height;
param.TopMargin = ((row-1) * (height + v_spacing)) + v_spacing;
customView.Background = shape;
customView.LayoutParameters = param;
customView.Tag = (8 - column).ToString() + "," + (17 - row).ToString();
return customView;
}
private void RelativeLayout_Touch(object sender, View.TouchEventArgs e)
{
if(e.Event.Action == MotionEventActions.Up)
{
out_of_bounds = true;
view_in_bound = null;
}
else
{
for (int row = 1; row < 8; row++)
{
for (int column = 1; column < 17; column++)
{
View view = relativeLayout.FindViewWithTag(row.ToString() + "," + column.ToString());
if (CheckInterSection(view, e.Event.RawX, e.Event.RawY))
{
if (out_of_bounds == true)
{
view_in_bound = view;
out_of_bounds = false;
Log.Debug("Touch", "Inside");
//ToggleViewState(view);
}
else
{
Log.Debug("Touch", "Still Inside");
}
}
else
{
if (view == view_in_bound)
{
out_of_bounds = true;
view_in_bound = null;
Log.Debug("Touch", "Outside");
}
}
}
}
}
}
bool CheckInterSection(View view, float rawX, float rawY)
{
int[] location = new int[2];
view.GetLocationOnScreen(location);
int x = location[0] - h_spacing/2;
int y = location[1] - v_spacing/2;
int width = (view.Width + h_spacing/2);
int height = (view.Height + v_spacing/2);
return (!(rawX < x || rawX > (x + width) || rawY < y || rawY > (y + height)));
}
I tried to use trajectory angles to further reduce the loop size but the performance was never up to my expectations and touch events over views were getting missed regularly.
Then I realized that I was going in the wrong direction and found a much simpler solution. Since my views were being added programmatically and were of same size, I knew the coordinates and bounds of each view in the layout. So I divided the layout into a grid and depending upon the touch coordinates, I was able to identify over which section the touch was on. Below is my solution which has been working perfectly. However, I will wait a while till I mark this as a solution since someone could have a better implementation of my technique or an alternative solution.
void DrawScreen()
{
for (int column = 1; column < 17; column++)
{
for (int row = 1; row < 8; row++)
{
relativeLayout.AddView(DrawRect(row, column));
}
}
}
View DrawRect(int row, int column)
{
View customView = new View(Context);
if (!CheckBit(row - 1, column - 1))
{
customView.SetBackgroundResource(Resource.Drawable.off_rounded_btn);
}
else
{
customView.SetBackgroundResource(Resource.Drawable.rounded_btn);
}
RelativeLayout.LayoutParams param = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
param.LeftMargin = ((column-1) * (width + h_spacing)) + h_spacing;
param.Width = width;
param.Height = height;
param.TopMargin = ((row-1) * (height + v_spacing)) + v_spacing;
customView.LayoutParameters = param;
customView.Tag = row.ToString() + "," + column.ToString();
return customView;
}
void RelativeLayout_Touch(object sender, View.TouchEventArgs e)
{
if (e.Event.Action == MotionEventActions.Up)
{
view_in_bound = null;
}
else
{
int row = CheckTouchArea(e.Event.RawX, e.Event.RawY)[0];
if (row != 0)
{
int column = CheckTouchArea(e.Event.RawX, e.Event.RawY)[1];
check_view = GetView(row, column);
if (check_view != view_in_bound)
{
ChangeViewState(check_view, Touch_CheckBit(row - 1, column - 1), row - 1, column - 1);
view_in_bound = check_view;
}
}
}
}
int[] CheckTouchArea(float rawX, float rawY)
{
int[] tag = new int[2];
int[] location = new int[2];
relativeLayout.GetLocationOnScreen(location);
float x = location[0] + h_padding / 2;
int y = location[1] + v_padding / 2;
float width = relativeLayout.Width - h_padding;
int height = relativeLayout.Height - v_padding;
if ((!(rawX < x || rawX > (x + width) || rawY < y || rawY > (y + height))))
{
int column = (int)Math.Ceiling((rawX - x) * 16 / width);
int row = (int)(Math.Ceiling((rawY - y) * 7 / height));
tag[0] = row;
tag[1] = column;
}
return tag;
}
I'm developing a game using SurfaceView which listens to touch events. The onTouchEvent method in SurfaceView works fine for many of the devices, but in some devices, sometimes it doesn't get called (Moto X Style is the one) and my app also stops responding.
I guess that this might be due to the overloading of main thread due to which onTouchEvent is starving.
Could some Android experts over here give me some tips to reduce the load on main thread if it's getting overloaded, or there might be some other reason which may cause this
The code is quite complex but still I'm posting some if you want to go through it
GameLoopThread
public class GameLoopThread extends Thread{
private GameView view;
// desired fps
private final static int MAX_FPS = 120;
// maximum number of frames to be skipped
private final static int MAX_FRAME_SKIPS = 5;
// the frame period
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
private boolean running = false;
public GameLoopThread(GameView view){
this.view = view;
}
public void setRunning(boolean running){
this.running = running;
}
public boolean isRunning() {
return running;
}
#Override
public void run() {
Canvas canvas;
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
beginTime = System.nanoTime();
framesSkipped = 0; // resetting the frames skipped
// update game state
// render state to the screen
// draws the canvas on the panel
this.view.draw(canvas);
// calculate how long did the cycle take
timeDiff = System.nanoTime() - beginTime;
// calculate sleep time
sleepTime = (int)(FRAME_PERIOD - timeDiff/1000000);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period
// very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// update without rendering
// add frame period to check if in next frame
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
}
}
finally {
// in case of an exception the surface is not left in
// an inconsistent state
view.getHolder().unlockCanvasAndPost(canvas);
} // end finally
}
}
}
GameView
public class GameView extends SurfaceView {
ArrayList<Bitmap> circles = new ArrayList<>();
int color;
public static boolean isGameOver;
public GameLoopThread gameLoopThread;
Circle circle; // Code for Circle class is provided below
public static int score = 0;
public static int stars = 0;
final Handler handler = new Handler();
int remainingTime;
boolean oneTimeFlag;
Bitmap replay;
Bitmap home;
Bitmap star;
int highScore;
boolean isLeaving;
public GameView(Context context, ArrayList<Bitmap> circles, int color) {
super(context);
this.circles = circles;
this.color = color;
oneTimeFlag = true;
gameLoopThread = new GameLoopThread(GameView.this);
getHolder().addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (!gameLoopThread.isRunning()) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
gameLoopThread.setRunning(false);
gameLoopThread = new GameLoopThread(GameView.this);
}
});
initializeCircles();
if(!gameLoopThread.isRunning()) {
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
}
public void initializeCircles() {
ArrayList<String> numbers = new ArrayList<>();
for(int i=0;i<10;i++)
numbers.add(i+"");
Random random = new Random();
int position = random.nextInt(4);
numbers.remove(color + "");
int p1 = position;
int r1 = Integer.valueOf(numbers.get(random.nextInt(9)));
numbers.remove(r1+"");
int r2 = Integer.valueOf(numbers.get(random.nextInt(8)));
numbers.remove(r2 + "");
int r3 = Integer.valueOf(numbers.get(random.nextInt(7)));
ArrayList<Bitmap> bitmaps = new ArrayList<>();
if(position == 0) {
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 1) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 2) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r3));
}
else {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
bitmaps.add(circles.get(color));
}
numbers = new ArrayList<>();
for(int i=0;i<10;i++)
numbers.add(i+"");
position = random.nextInt(4);
numbers.remove(color + "");
r1 = Integer.valueOf(numbers.get(random.nextInt(9)));
numbers.remove(r1 + "");
r2 = Integer.valueOf(numbers.get(random.nextInt(8)));
numbers.remove(r2 + "");
r3 = Integer.valueOf(numbers.get(random.nextInt(7)));
if(position == 0) {
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 1) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
}
else if(position == 2) {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(color));
bitmaps.add(circles.get(r3));
}
else {
bitmaps.add(circles.get(r1));
bitmaps.add(circles.get(r2));
bitmaps.add(circles.get(r3));
bitmaps.add(circles.get(color));
}
circle = new Circle(this, bitmaps, circles, p1, position, color, getContext());
}
#Override
public void draw(Canvas canvas) {
if(canvas != null) {
super.draw(canvas);
canvas.drawColor(Color.WHITE);
if(!isGameOver && timer != null)
stopTimerTask();
try {
circle.draw(canvas);
} catch (GameOverException e) {
isGameOver = true;
if(isLeaving)
gameOver(canvas);
else if(GameActivity.counter > 0) {
gameOver(canvas);
GameActivity.counter++;
} else {
if (oneTimeFlag) {
int size1 = 200 * GameActivity.SCREEN_HEIGHT / 1280;
int size2 = 125 * GameActivity.SCREEN_HEIGHT / 1280;
float ratio = (float) GameActivity.SCREEN_HEIGHT / 1280;
replay = GameActivity.decodeSampledBitmapFromResource(getResources(), R.drawable.replay, size1, size1);
home = GameActivity.decodeSampledBitmapFromResource(getResources(), R.drawable.home, size2, size2);
continueButton = GameActivity.decodeSampledBitmapFromResource(getContext().getResources(), R.drawable.button, (int) (540 * ratio), (int) (100 * ratio));
star = GameActivity.decodeSampledBitmapFromResource(getContext().getResources(), R.drawable.star1, (int) (220 * ratio), (int) (220 * ratio));
int w = (int) ((float) GameActivity.SCREEN_WIDTH * 0.9);
oneTimeFlag = false;
}
if (askPurchaseScreen == 2) {
gameOver(canvas);
} else {
canvas.drawColor(Circle.endColor);
}
}
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
circle.onTouch(x, y);
return true;
}
}
Circle
public class Circle {
int x;
int y1;
int y2;
public static float speedY1 = 12.5f*(float)GameActivity.SCREEN_HEIGHT/1280;
public static float speedY2 = 12.5f*(float)GameActivity.SCREEN_HEIGHT/1280;
ArrayList<Bitmap> bitmaps;
GameView gameView;
int p1; // Position of required circle in slot 1
int p2; // Position of required circle in slot 2
int color;
int tempColor;
int width;
Context context;
// Centers of required circle
float centerX1;
float centerX2;
float centerY1;
float centerY2;
ArrayList<Bitmap> circles = new ArrayList<>();
boolean touchedFirst;
boolean touchedSecond;
int count1 = 1; // Slot 1 circle radius animation
int count2 = 1; // Slot 2 circle radius animation
float tempSpeedY1;
float tempSpeedY2;
boolean stopY1;
boolean stopY2;
int barCounter = 1;
int loopCount = 0;
int endGameCount = 0; // Count to move circle upwards
double limit;
float endRadiusSpeed;
int endSlot; // Where you died
int endRadiusCount = 0; // Count to increase circle radius
int barEndCounter = 1;
final Handler handler = new Handler();
boolean exception;
public static int endColor;
public Circle(GameView gameView, ArrayList<Bitmap> bitmaps, ArrayList<Bitmap> circles, int p1, int p2, int color, Context context) {
this.gameView = gameView;
this.bitmaps = bitmaps;
this.circles = circles;
this.p1 = p1;
this.p2 = p2;
this.color = color;
this.context = context;
width = GameActivity.SCREEN_WIDTH / 4 - 10;
x = 10;
y1 = 0;
y2 = -(GameActivity.SCREEN_HEIGHT + width) / 2;
centerX1 = x + p1 * (10 + width) + width / 2;
centerY1 = y1 + width / 2;
centerX2 = x + p2 * (10 + width) + width / 2;
centerY2 = y2 + width / 2;
}
public void update() throws GameOverException {
y1+= speedY1;
y2+= speedY2;
centerY1+= speedY1;
centerY2+= speedY2;
float ratio = (float)GameActivity.SCREEN_HEIGHT/1280;
limit = width/(20*ratio);
if(y1 >= gameView.getHeight()) {
loopCount++;
if(touchedFirst)
touchedFirst = false;
else {
speedY1 = speedY2 = -(12.5f * ratio);
endColor = bitmaps.get(p1).getPixel(width/2, width/2);
endGameCount += 1;
endSlot = 1;
}
if(endGameCount == 0) {
if (stopY1) {
tempSpeedY1 = speedY1;
speedY1 = 0;
ArrayList<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i != color)
numbers.add(i);
}
tempColor = numbers.get(new Random().nextInt(9));
}
y1 = -(gameView.getWidth() / 4 - 10);
count1 = 1;
setBitmaps(1);
}
}
else if(y2 >= gameView.getHeight()) {
loopCount++;
if(touchedSecond)
touchedSecond = false;
else {
speedY1 = speedY2 = -(12.5f * ratio);
endColor = bitmaps.get(p2 + 4
).getPixel(width/2, width/2);
endGameCount += 1;
endSlot = 2;
}
if(endGameCount == 0) {
if (stopY2) {
tempSpeedY2 = speedY2;
speedY2 = 0;
}
y2 = -(gameView.getWidth() / 4 - 10);
count2 = 1;
setBitmaps(2);
}
}
}
public void setBitmaps(int slot) {
ArrayList<String> numbers = new ArrayList<>();
for(int i=0;i<10;i++)
numbers.add(i+"");
Random random = new Random();
int position = random.nextInt(4);
numbers.remove(color + "");
int r1 = Integer.valueOf(numbers.get(random.nextInt(9)));
numbers.remove(r1+"");
int r2 = Integer.valueOf(numbers.get(random.nextInt(8)));
numbers.remove(r2 + "");
int r3 = Integer.valueOf(numbers.get(random.nextInt(7)));
if(position == 0) {
bitmaps.set((slot - 1)*4, circles.get(color));
bitmaps.set((slot - 1)*4 + 1, circles.get(r1));
bitmaps.set((slot - 1)*4 + 2, circles.get(r2));
bitmaps.set((slot - 1)*4 + 3, circles.get(r3));
}
else if(position == 1) {
bitmaps.set((slot - 1)*4, circles.get(r1));
bitmaps.set((slot - 1)*4 + 1, circles.get(color));
bitmaps.set((slot - 1)*4 + 2, circles.get(r2));
bitmaps.set((slot - 1)*4 + 3, circles.get(r3));
}
else if(position == 2) {
bitmaps.set((slot - 1)*4, circles.get(r1));
bitmaps.set((slot - 1)*4 + 1, circles.get(r2));
bitmaps.set((slot - 1)*4 + 2, circles.get(color));
bitmaps.set((slot - 1)*4 + 3, circles.get(r3));
} else {
bitmaps.set((slot - 1)*4,circles.get(r1));
bitmaps.set((slot - 1)*4 + 1,circles.get(r2));
bitmaps.set((slot - 1)*4 + 2,circles.get(r3));
bitmaps.set((slot - 1)*4 + 3,circles.get(color));
}
if(slot == 1) {
p1 = position;
centerX1 = x+position*(10 + width) + width/2;
centerY1 = y1 + width/2;
}
else if(slot == 2) {
p2 = position;
centerX2 = x+position*(10 + width) + width/2;
centerY2 = y2 + width/2;
}
}
public void onTouch(float X, float Y) {
int radius = (gameView.getWidth() / 4 - 10) / 2;
if(endGameCount == 0) {
if ((X >= centerX1 - radius) && (X <= centerX1 + radius) && (Y >= centerY1 - radius) && (Y <= centerY1 + radius)) {
GameView.score++;
touchedFirst = true;
centerX1 = centerY1 = -1;
if(p1 == (timerCount - 1) && timer != null && starSlot == 1) {
GameView.stars++;
starCollected = true;
timerCount = 0;
stopTimerTask(0);
}
} else if ((X >= centerX2 - radius) && (X <= centerX2 + radius) && (Y >= centerY2 - radius) && (Y <= centerY2 + radius)) {
GameView.score++;
touchedSecond = true;
centerX2 = centerY2 = -1;
if(p2 == (timerCount - 1) && timer != null && starSlot == 2) {
GameView.stars++;
starCollected = true;
timerCount = 0;
stopTimerTask(0);
}
} else {
endSlot = 0;
if ((Y >= centerY1 - radius) && (Y <= centerY1 + radius)) {
endSlot = 1;
if (X >= 10 && X <= 10 + 2 * radius) {
p1 = 0;
centerX1 = 10 + radius;
} else if (X >= 20 + 2 * radius && X <= 20 + 4 * radius) {
p1 = 1;
centerX1 = 20 + 3 * radius;
} else if (X >= 30 + 4 * radius && X <= 30 + 6 * radius) {
p1 = 2;
centerX1 = 30 + 5 * radius;
} else if (X >= 40 + 6 * radius && X <= 40 + 8 * radius) {
p1 = 3;
centerX1 = 40 + 2 * radius;
} else
endSlot = 0;
} else if ((Y >= centerY2 - radius) && (Y <= centerY2 + radius)) {
endSlot = 2;
if (X >= 10 && X <= 10 + 2 * radius) {
p2 = 0;
centerX2 = 10 + radius;
} else if (X >= 20 + 2 * radius && X <= 20 + 4 * radius) {
p2 = 1;
centerX2 = 20 + 3 * radius;
} else if (X >= 30 + 4 * radius && X <= 30 + 6 * radius) {
p2 = 2;
centerX2 = 30 + 5 * radius;
} else if (X >= 40 + 6 * radius && X <= 40 + 8 * radius) {
p2 = 3;
centerX2 = 40 + 2 * radius;
} else
endSlot = 0;
}
if (endSlot != 0) {
speedY1 = speedY2 = 0;
limit = endGameCount = 6;
if (endSlot == 1) {
endColor= bitmaps.get(p1).getPixel(width/2, width/2);
} else {
endColor = bitmaps.get(p2 + 4).getPixel(width/2, width/2);
}
}
}
if (GameView.score % 5 == 0 && GameView.score <= 110 && barCounter == 1) {
float ratio = (float)GameActivity.SCREEN_HEIGHT/1280;
speedY1 += ratio*0.5;
speedY2 += ratio*0.5;
}
if (GameView.score > 0 && GameView.score % 15 == 14) {
if(isOddScore)
stopY1 = true;
else
stopY2 = true;
}
if (GameView.score > 0 && GameView.score % 15 == 0 && barCounter == 1) {
if(isOddScore)
stopY2 = true;
else
stopY1 = true;
}
if (GameView.score % 15 == 1)
barCounter = 1;
}
}
public void draw(Canvas canvas) throws GameOverException {
GameView.isGameOver = false;
if(exception)
throw new GameOverException(color);
update();
for(int i=0;i<bitmaps.size();i++) {
if(i<4) {
Rect rect = new Rect(x+i*(10 + width),y1,(x+width)*(i+1),y1+width);
if(endGameCount == Math.ceil(limit) && endSlot == 1) {
if(i == p1) {
endRadiusCount += 1;
if (endRadiusCount > 23) {
star.recycle();
loopCount = loopCount%starInterval;
Cryptography.saveFile((loopCount + "").getBytes(), context, "interval");
endGameCount = 0;
exception = true;
throw new GameOverException(color);
}
rect = new Rect(x + i * (10 + width) - endRadiusCount*(int)Math.ceil(endRadiusSpeed), y1 - endRadiusCount*(int)Math.ceil(endRadiusSpeed), (x + width) * (i + 1) + endRadiusCount*(int)Math.ceil(endRadiusSpeed), y1 + width + endRadiusCount*(int)Math.ceil(endRadiusSpeed));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
}
}
// TOUCH ANIMATION : DIMINISH CIRCLE
else if(i==p1 && touchedFirst) {
rect = new Rect(x + i * (10 + width) + 3*count1 + ((int)speedY1-15), y1 + 3*count1 + ((int)speedY1-15), (x + width) * (i + 1) - 3*count1 - ((int)speedY1-15), y1 + width - 3*count1 - ((int)speedY1-15));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
count1++;
}
else if(endSlot != 2) {
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
if(timerCount > 0 && starSlot == 1) {
int size = width * 30 / 50;
int difference = (width - size) / 2;
Rect starRect = new Rect(x + (timerCount - 1) * (10 + width) + difference, y1 + difference, (x + width) * (timerCount) - difference, y1 + width - difference);
canvas.drawBitmap(star, null, starRect, null);
}
}
}
if(i >= 4) {
Rect rect = new Rect(x + (i % 4) * (10 + width), y2, (x + width) * ((i % 4) + 1), y2 + width);
if(endGameCount == Math.ceil(limit) && endSlot == 2) {
if((i%4)==p2) {
endRadiusCount += 1;
if (endRadiusCount > 23) {
star.recycle();
loopCount = loopCount%starInterval;
Cryptography.saveFile((loopCount + "").getBytes(), context, "interval");
endGameCount = 0;
exception = true;
throw new GameOverException(color);
}
rect = new Rect(x + (i % 4) * (10 + width) - endRadiusCount*(int)Math.ceil(endRadiusSpeed), y2 - endRadiusCount*(int)Math.ceil(endRadiusSpeed), (x + width) * ((i % 4) + 1) + endRadiusCount*(int)Math.ceil(endRadiusSpeed), y2 + width + endRadiusCount*(int)Math.ceil(endRadiusSpeed));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
}
}
else if((i%4)==p2 && touchedSecond) {
rect = new Rect(x + (i % 4) * (10 + width) + 3*count2 + ((int)speedY1-15), y2 + 3*count2 + ((int)speedY1-15), (x + width) * ((i % 4) + 1) - 3*count2 - ((int)speedY1-15), y2 + width - 3*count2 - ((int)speedY1-15));
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
count2++;
}
else if(endSlot != 1) {
canvas.drawBitmap(bitmaps.get(i), null, rect, null);
if(timerCount > 0 && starSlot == 2) {
int size = width * 30 / 50;
int difference = (width - size) / 2;
Rect starRect = new Rect(x + (timerCount - 1) * (10 + width) + difference, y2 + difference, (x + width) * (timerCount) - difference, y2 + width - difference);
canvas.drawBitmap(star, null, starRect, null);
}
}
}
}
Rect src = new Rect(circles.get(color).getWidth()/2 - 10,circles.get(color).getHeight()/2 - 10,circles.get(color).getWidth()/2 + 10,circles.get(color).getHeight()/2 + 10);
Rect dst;
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextAlign(Paint.Align.RIGHT);
paint.setTypeface(Typeface.SANS_SERIF);
paint.setTextSize(72 * ratio);
canvas.drawText(GameView.score + " ", GameActivity.SCREEN_WIDTH, width / 2, paint);
dst = new Rect(5,5, (int) (120 * ratio - 5), (int) (120 * ratio - 5));
canvas.drawBitmap(star,null,dst,null);
paint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("" + GameView.stars, 120 * ratio, width/2, paint);
}
}
Don't override draw(). That's used to render the View, not the Surface, and you generally shouldn't override that method even if you're creating a custom View:
When implementing a view, implement onDraw(android.graphics.Canvas) instead of overriding this method.
SurfaceViews have two parts, the Surface and the View. The View part is handled like any other View, but is generally just a transparent "hole" in the layout. The Surface is a separate layer that, by default, sits behind the View layer. Whatever you draw on the Surface "shows through" the transparent hole.
By overriding draw() you're drawing on the View whenever the View UI is invalidated. You're also calling draw() from the render thread, so you're drawing on the Surface, but with default Z-ordering you can't see that because the View contents are fully opaque. You will reduce your impact on the UI thread by not drawing everything in two different layers.
Unless you're deliberately drawing on the View, it's best to avoid subclassing SurfaceView entirely, and just use it as a member.
Because your draw code is synchronized, the two draw passes will not execute concurrently. That means your View layer draw call will block waiting for the Surface layer rendering to complete. Canvas rendering on a Surface is not hardware-accelerated, so if you're touching a lot of pixels it can get slow, and the UI thread will have to wait for it to run. That wouldn't be so bad, but you're holding on to the mutex while you're sleeping, which means the only opportunity for the main UI thread to run comes during the brief instant when the loop wraps around. The thread scheduler does not guarantee fairness, so it's entirely possible to starve the main UI thread this way.
If you change #override draw() to myDraw() things should get better. You should probably move your sleep call out of the synchronized block just on general principles, or work to eliminate it entirely. You might also want to consider using a custom View instead of SurfaceView.
On an unrelated note, you should probably avoid doing this every update:
Random random = new Random();
for the reasons noted here.
Successfully solved the issue. Can't imagine that the solution would be this much simple as compared to the problem that I was considering that complex. Just reduced the frame rate from 120 to 90 and guess what, it worked like charm!
Due to a high frame rate, the SurfaceView was busy doing all the drawing and onTouchEvent() method had to starve
I want to detect motion with Android sensors. For example I hold only bottom of phone and move top of phone to up. I think I need sampling algorithms. I can write a simple application to record data of sensors . For comparing real time data and recorded data ,Is there any libary ? I have suspicious about performace problems if I would make it. Is there a different path for detetion movements ?
These links will help you to start..
http://developer.android.com/guide/topics/sensors/sensors_motion.html
http://code.google.com/p/android-motion-detection/
http://www.helloandroid.com/tutorials/android-image-processing-detecting-motions
http://code.google.com/p/android-motion-detection/ is a good example.
I modified the isDifferent method in RgbMotionDetection class to detect the motion in the center part (25%) of the camera view.
protected static boolean isDifferent(int[] first, int width, int height) {
if (first==null) throw new NullPointerException();
if (mPrevious==null) return false;
if (first.length != mPrevious.length) return true;
if (mPreviousWidth != width || mPreviousHeight != height) return true;
int totDifferentPixels = 0;
int size = height * width;
int startHeight = height / 4;
int endHeight = 3 * (height / 4);
int startWidth = width / 4;
int endWidth = 3 * (width / 4);
int offSet = width / 4;
Log.d("params", "start height " + startHeight + "end height " + endHeight + "start width " + startWidth + "end width " + endWidth);
Boolean offSetApplied;
for (int i = startHeight, ij=0; i < endHeight; i++) {
{
offSetApplied = false;
for (int j = startWidth; j < endWidth; j++, ij++) {
if (!offSetApplied){
offSetApplied = true;
ij = startHeight * width + offSet;
}
int pix = (0xff & ((int)first[ij]));
int otherPix = (0xff & ((int)mPrevious[ij]));
//Catch any pixels that are out of range
if (pix < 0) pix = 0;
if (pix > 255) pix = 255;
if (otherPix < 0) otherPix = 0;
if (otherPix > 255) otherPix = 255;
if (Math.abs(pix - otherPix) >= mPixelThreshold) {
totDifferentPixels++;
//Paint different pixel red
//first[ij] = Color.RED;
}
}
}
}
if (totDifferentPixels <= 0) totDifferentPixels = 1;
//boolean different = totDifferentPixels > mThreshold;
int percent = 100/(size/totDifferentPixels);
//float percent = (float) totDifferentPixels / (float) size;
boolean different = percent > SENSITIVITY;
String output = "Number of different pixels: " + totDifferentPixels + "> " + percent + "%";
if (different) {
Log.e(TAG, output);
} else {
Log.d(TAG, output);
}
return different;
}
I created the HTML5 version for the Pattern login like Android, but unfortunately I know not much of HTML5 as to give the required functionality.
How I can do that with the mouse can given action?
My project is this:
http://jsfiddle.net/atiruz/mvkv9/3/
var size = 3; // Dimensions -> 3x3.
var ancho = size * 82 + 10;
var alto = ancho;
var drawingCanvas = createCanvas(document.getElementById('canvas'), 600, 600);
var context = drawingCanvas.context;
context.beginPath();
CrearContenedor(context,0,0,ancho,alto,15);
for (var i = 1; i <= size; i++) {
for (var j = 1; j <= size; j++) {
CrearBoton(context, i * 80 - 32, j * 80 - 32, ancho, alto);
}
}
And the result is:
Here's a very basic (webkit only) canvas implementation: http://jsfiddle.net/ChfGh/1/
I'm using jQuery for event handling, but that isn't strictly necessary.
var can = $("#canvas")[0],
ctx = can.getContext('2d'),
wid = can.width,
hei = can.height,
pad = 20,
circles = [],
selCircs = [],
correctPath = [0,3,7,5,2],
startPoint = {
x: 0,
y: 0
},
dragging = false;
(function init() {
var rad = (wid - pad * 4) / 3;
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
circles.push(new Circle({
x: j * (pad + rad) + pad + rad / 2,
y: i * (pad + rad) + pad + rad / 2
}, rad / 2));
}
}
})();
(function draw() {
ctx.clearRect(0, 0, wid, hei);
for (var i = 0; i < circles.length; i++) {
circles[i].draw(ctx);
}
if (dragging && selCircs.length > 1) {
var pos = selCircs[0].circ.position();
ctx.beginPath();
ctx.lineWidth = 10;
ctx.strokeStyle = "#000";
ctx.moveTo(pos.x, pos.y);
for (var j = 1; j < selCircs.length; j++){
pos = selCircs[j].circ.position();
ctx.lineTo(pos.x,pos.y);
}
ctx.stroke();
}
webkitRequestAnimationFrame(draw);
})();
function Circle(center, radius, fill, stroke, hover, active) {
var center = {
x: center.x,
y: center.y
},
radius = radius,
fill = fill || '#ccc',
hover = hover || '#ddd',
active = active || '#0f0',
stroke = stroke || '',
path;
this.position = function(){
return {x:center.x, y:center.y};
}
this.draw = function(ctx) {
ctx.fillStyle = this.selected ? active : this.hovering ? hover : fill;
if (stroke) ctx.strokeStyle = stroke;
ctx.beginPath();
ctx.arc(center.x, center.y, radius, 0, Math.PI * 2, false);
ctx.fill();
if (stroke) ctx.stroke();
};
this.isPointInPath = function(x, y) {
return Math.sqrt(Math.pow(center.x - x, 2) + Math.pow(center.y - y, 2)) <= radius;
};
this.hovering = false;
this.selected = false;
}
$("#canvas").mousemove(function(e) {
for (var i = 0; i < circles.length; i++) {
var cir = circles[i];
var pip = cir.isPointInPath(e.offsetX, e.offsetY);
cir.hovering = pip;
if (dragging && pip && !cir.selected) {
selCircs.push({circ:cir, index:i});
cir.selected = true;
}
}
});
$("#canvas").mousedown(function(e) {
dragging = true;
});
$("#canvas").mouseup(function(e) {
dragging = false;
// validate path
if (selCircs.length == correctPath.length){
var valid = true;
for (var i = 0; valid && i < correctPath.length; i++){
var index = correctPath[i];
if (selCircs[i].index !== index) valid = false;
}
if (valid) alert('correct!');
}
// reset selection
for (var i = 0; i < selCircs.length; i++)
selCircs[i].circ.selected = false;
selCircs = [];
});
Here's the style I ended up implementing for fun: http://jsfiddle.net/ChfGh/6/
You can use map to extract the indexes from the selected circles:
var path = selCircs.map(function(ele){
return ele.index;
}).join(" "); // " " if you want a space between, otherwise ""
It will give you a string representation of the input.
Checkout "Pattern Lock" jquery plugin at http://pagoenka.github.io/