Tuesday, January 14, 2014

Done with the paint app

There was a trip to Germany for the boyfriend to attend a conference, and Christmas, and New Year's, so not a whole lot got done between the 15th and the 2nd. But since then it's been chin-rubbing and poking and growling, and finally the app was finished. I still need code review but right now it does just about everything I want it to.

The sticky bit was adding text. I'd originally set up the button to open an alert dialog where you could enter your text, and select a font size via a slider. That was visually jarring and functionally awkward because you couldn't really tell how large to make your text in relation to the drawing.  You would only know after placing it, so you'd have to undo and try again if you found it the wrong size.

So I found a bit of code that would include an actionable graphic (an image of an X to clear the text) in an EditText. From that starting point I made the EditText so it would be placed directly on the drawing surface, and it could be resized by dragging at the corner graphic. Clicking the checkmark graphic at the top cements the text in place. It's much smoother than having a dialog box.

After that, some code to save the drawing in JSON so it can be retrieved later.

For the next trick I want to put advertising in it. I'd like have a free-with-ads version, and a paid no-ads version. Don't hurt me, please. I'm not crazy about doing it either.

So to distract you from that last paragraph, here's the code for an edittext that can be moved around and the font resized by dragging.  Both the drawing and the EditText are children of a Viewgroup.

public class MovableEditText extends EditText {

    // 2 drawables: an OK and a resize handle
    private Drawable closeImg = getResources().getDrawable(R.drawable.text_ok);
    private Drawable resizeImg = getResources().getDrawable(R.drawable.text_resize);
    private long timeFingerDown;
    public InputMethodManager imm;
    private App app;
    private boolean resizing;
    private TextCompletedListener textListener;
    private boolean finished = false;
    private float startDeltaLeft;
    private float startDeltaTop;

    float leftMargin = 100;
    float topMargin = 100;

    public MovableEditText(Context context) {
        super(context);
        app = (App) context.getApplicationContext();
        imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
        init();
    }

    @Override
    public void scrollTo(int x, int y) {
        super.scrollTo(0, 0);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                timeFingerDown = event.getEventTime();
                // Remembers where you grabbed the text
                startDeltaLeft = event.getX();
                startDeltaTop = event.getY();
                setCursorVisible(true);
                return true;

            case MotionEvent.ACTION_UP:
                resizing = false;
                // clicking on the OK checkbox
                if (event.getY() < getPaddingTop() + closeImg.getIntrinsicHeight()) {
                    MovableEditText.this.okButtonClick();
                }
                // otherwise, act like a proper edittext
                if (event.getEventTime() - timeFingerDown < 200 && !finished) {
                    super.onTouchEvent(event);
                }
                return false;

            case MotionEvent.ACTION_MOVE:
                if (event.getX() > getWidth() - getPaddingRight() - resizeImg.getIntrinsicWidth()) {
                    resizeBox(event);
                    // we touched the arrow, so we are resizing the box (i.e. increasing font size)
                    resizing = true;
                    return true;
                }
                if (event.getEventTime() - timeFingerDown > 200 && !resizing) {
                    // we are moving the box; close the keyboard
                    imm.hideSoftInputFromWindow(getWindowToken(), 0);
                    moveBox(event);
                }
                return true;

            case MotionEvent.ACTION_CANCEL:
                resizing = false;
                return false;
        }
        return true;
    }

    public void setTextCompletedListener(TextCompletedListener l) {
        textListener = l;
    }

    private void init() {
        setMinimumHeight(closeImg.getIntrinsicHeight()*2);
        setMinimumWidth(closeImg.getIntrinsicWidth()+resizeImg.getIntrinsicWidth() *2);
        // Set bounds of the Clear button so it will look ok
        closeImg.setBounds(0, 0, closeImg.getIntrinsicWidth(), closeImg.getIntrinsicHeight());
        resizeImg.setBounds(0, 0, resizeImg.getIntrinsicWidth(), resizeImg.getIntrinsicHeight());
        // There may be initial text in the field, so we may need to display the  button
        showOkButton();
        //if text changes, take care of the button
        this.addTextChangedListener(new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                MovableEditText.this.showOkButton();
            }

            @Override
            public void afterTextChanged(Editable arg0) {
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
        });
    }

    private void moveBox(MotionEvent event) {
        leftMargin += (int) (event.getX() - startDeltaLeft);
        topMargin += (int) (event.getY() - startDeltaTop);
        requestLayout();
    }

    // the following is to be used by drawview when resizing and panning
    public void externalMove(float dragX, float dragY) {
        leftMargin = (int) (leftMargin + dragX);
        topMargin = (int) (topMargin + dragY);
        requestLayout();
    }

    private void resizeBox(MotionEvent event) {
        setWidth((int) event.getX());
        setHeight((int) event.getY());
        resizeText();
    }

    // change the text size to whatever will fit in the box
    private void resizeText() {
        String[] multiline = this.getText().toString().split("\n");
        float adjustedHeight = (getMeasuredHeight()/multiline.length - getCompoundPaddingBottom() - getCompoundPaddingTop())/2;
        setTextSize(adjustedHeight);
    }

    // if there is text, add the OK button
    void showOkButton() {
        if (this.getText().toString().equals("")) {
            // remove the clear button
            this.setCompoundDrawables(this.getCompoundDrawables()[0],null, resizeImg, this.getCompoundDrawables()[3] );
        } else {
            //add clear button
            this.setCompoundDrawables(this.getCompoundDrawables()[0], closeImg, resizeImg, this.getCompoundDrawables()[3]);
        }
    }

    void okButtonClick() {
        MyViewGroup viewGroup = (MyViewGroup) getParent();
        // get the text, get the size, get the position, create a Stroke from it
        // locate this view in the viewgroup's coordinate system
        // the screen coordinates minus the location of the MET
        Float deltaL = getLeft() - getScrollX() - viewGroup.dv.getCenterX();
        Float deltaB = getTop() - getScrollY() + getBaseline() - viewGroup.dv.getCenterY() ;
        // viewcenterxy are the canvas coordinates
        Float dvViewCenterX = viewGroup.dv.getViewCenterX();
        Float dvViewCenterY = viewGroup.dv.getViewCenterY();
        Float scaleFactor = viewGroup.dv.getScaleFactor();
        // translate that delta to the canvas's coordinates
        Float translatedL = dvViewCenterX + (deltaL/scaleFactor);
        Float translatedB = dvViewCenterY + (deltaB/scaleFactor);

        if (this.getText().toString().length()>0) {
            app.setText(getText().toString());
            app.setTextSize(getTextSize()/2);  // i do not know why it keeps doubling the size
            app.strokeInProgress = new TextStrokeBuilder(app, translatedL, translatedB, scaleFactor);
            app.finishStroke();
        }
        // tell viewgroup to delete the EditText
        textListener.onTextCompleted();
        finished = true;
        imm.hideSoftInputFromWindow(getWindowToken(),0);
    }

    public void setLeftMargin(float left) {
        leftMargin = left;
    }
    public void setTopMargin (float top) {
        topMargin = top;
    }
}

Thursday, December 12, 2013

getting close to done

I finally managed to build a resizable EditText box. So you click on the Add Text button, and a little grey box pops up onscreen on top of your drawing. It's semi-transparent so you can see what's going on below. The keyboard pops up and you type your stuff. A double-headed arrow icon on the right: left/right will increase the width of the box, up/down will change the size of the font. Touch the checkmark icon at the top and the text will be placed. Press & hold on the text itself will let you drag it around to where you want it.

This is getting nice. It's reasonably smooth and intuitive.

There are several things I need to fix to get it fully working right. At the moment it doesn't place the text where I want, and the size is being measured in actual pixels, so if you're zoomed out on the page, it's freaking teeny tiny. These are scaling issues. The other bit is gracefully deleting the EditText when finished -- I need a hook or a listener or something that the EditText can use to tell the app it's done.

Sunday, December 1, 2013

More Play Store piracy


I wrote about this back in April. I do not browse the Play Store all that often -- once a month, maybe a bit more -- and I found another one a couple days ago. The game is the property of Mateusz Skutnik, an artist and Flash programmer lauded by escape gamers for his atmospheric, brain-twisting Submachine series.

I notified Mateusz and he confirmed that the developer did not have permission to port his work to Android. He'll be taking action on it.

Because the games they steal are by different developers, pirates' offerings are going to be very diverse in visual style. A developer making his or her own apps will have a certain style, probably more clearly seen in games than other apps. Widgets such as control buttons and menu styles will be re-used, and the flow from one activity to the next will likely have the same feel.

The ad load of a pirated game will be high. That's one thing that you can count on, because the game is free and this is the sole means of revenue.

It would be interesting if Google, with its advanced abilities to search images for faces and general concepts, applied this to the Play Store.

Sunday, November 17, 2013

working on a new app

A few days before Halloween Brian settled on his "costume": a bow tie.

Not just any ol' bowtie, but

><  > the Bowtie of the Future <  ><

It would be changeable at a touch. It would set the background to the shirt behind it. It would take photos. It would have a +1 button.

In about 6 hours we cobbled together a bowtie image that would cycle through the Google colors and wiggle when clicked, had a fake +1 button and a counter on the left, a subtle (Google design always runs subtle) "BETA" on the right. "Adjusting" the tie, i.e. rotating the phone back and forth, would take a photo.

There were a bunch of little quirky things to code for. Because of the front camera's placement, the entire app had to be displayed upside-down from the normal landscape mode, otherwise the camera would have been obscured by his collar. Photos taken by the camera resulted as upside-down, but flipping is easy. Since he's tallish, the camera tended to take photos of the top edge of the opposite wall, and I experimented with carving wedges from packing foam until we decided it was more trouble than it was worth. Brian was dissatisfied with the shake code I'd found and fine-tuned it to use the gyroscope and a specific sequence  of motions to listen for.

And of course we didn't actually use the back camera to divine the clothing behind it. Brian picked a shirt, I slapped it on the scanner, and turned it into the background image.

But that isn't the 'new app' of the post title.  I've started a paint program. Nothing that isn't the same as quite a few paint apps out there, and in fact it may be simpler than most. But it's partly for the experience and partly because I have an idea that will make it special.

The basics, of course, include:
- a palette of 10 colors, which can be customized
- fill color
- outline thickness
- brush
- point-to-point lines
- rectangles and ovals
- text (nothing fancy, just plain ol' text using the native Android font in the outline color)
- zooming, panning
- undo
- eraser (you won't believe how hard that is to figure out, partially due to sketchy Android documentation).

All colors include transparency levels.

You can take a photo or select one from your gallery to slide in as the background layer.

You can save the drawing+background to the gallery as a jpg, and share it to email, Drive, etc.

All customizable items (colors, line thickness) are saved in SharedPreferences, so you can always have that perfect 20% transparent purple you love.

I've just finished coding all the above. Neither Brian nor I are fully satisfied with the sequence of events used to add text.  It's not easy to make a not-too-disruptive popup with a space for your text and a slider for your font size when there's a keyboard that will slide in and take up half your screen space.

More to come...

Tuesday, August 13, 2013

Ninja Escape by Niwaka Soft - walkthrough all stages

Android app

Stage 1 - Use the shuriken to hit the targets
Stage 2 - Pull the rope 6 times
Stage 3 - Shake your device
Stage 4 - use the sword to kill the tiger
Stage 5 - shake (like a bird!)
Stage 6 - hit the 5 with shuriken
Stage 7 - use the flame to fire up the candle. The candle goes in inventory. Use it to see the Chinese numbers.  This is an equation!
Stage 8 - use shuriken on jar for key.
Stage 9 - swipe left and right to move panels away, then swipe up when you see the arrows.
Stage 10 - enter the numbers on the bottom side of the dice. Opposing sides of dice always add up to 7.
Stage 11 - shake to get the key. unlock chest for a brush. paint the eye on the daruma doll.
Stage 12 - use the shuriken to bring the ropes on the sides down. Pull ropes in order 1 2 3 4 5 4 3 2 1. (Thanks, Angela!)
Stage 13 - click each clock dial so it resembles the corresponding "screw" on the fan.
Stage 14 - flip your device upside down and tap the X (I think) 9 times.
Stage 15 - take the brush and brush away all the black marks. I had a hard time distinguishing when all the black was gone, it took me several tries to get it.
Stage 16 - Touch the screen to bring the spiders down, then use the shuriken to kill them in the order indicated.
Stage 17 - move the candles around so there is the indicated quantity in the box.
Stage 18 - turn your device upside down to show 3 0. Shake it to get an arrow. Tap the circle 3 times.
Stage 19 - take the bucket and use it on the branch.
Stage 20 - 704. I brute forced it, so I don't have an explanation for that number. (Edit: apparently the symbol on the banner is supposed to be *, as in multiply, so 88 * 8.
Stage 21 - slide the panels to the center in the order shown.
Stage 22 - take the hook from the left and, using your sword, the rope on the right. It will automatically combine into a grappling hook to use on the ring.
Stage 23 - take the bucket and put the fire out. EZPZ
Stage 24 - Is a little like Simon. Press the 2nd button and a snake (or garden hose?) will pop out from the hole above it. Press the 2nd button again and 2 snakes will come out. Pay attention to which hole and the order, and press the corresponding buttons to get 3 snakes. Once you've gotten all 4 snakes the ladder will come down to exit.
Super spoiler:
2 | 2 | 1, 4 | 3, 1, 2 | 4, 2, 1, 3
Stage 25 - Select the fire tool and shoot the first tube. This will show you which colors to select out of each group of 3 buttons.
Stage 26 - place a red eye on each of the dragons. Swipe them away, then swipe the wall again to get the stairs.
Stage 27 - arrange the sliders to match the heights of the yellow ovals.
Stage 28 - shake your phone to see the symbols on the top of the wall. Click the panels to match.
Stage 29 - use your fire to start the torches, then pull on the rope. Enter the number shown.
Stage 30 - First, the banner shows 2 arrows. Swipe simultaneously using 2 fingers from the center to the edges, i.e. your left finger starts left of the center and swipes to the left edge, and your right finger starts right of center and goes to the right edge. If you've done this correctly, the banner should change to a left arrow. After this, using the same positioning for the swipe -- from the middle outward -- swipe as follows: L L R L L R R L R R. If you mess up it will go back to the 2 arrows and you have to start over again.
Stage 31 - Select your sword and tap on the screen. Arrows will start flying out and your job is to slice them. If you manage to slice them all (they will disappear instead of flying offscreen) you get to the number safe. Code is 332 (again brute forced; no obvious clue except the arrows, which I was unable to count while slicing them).
Stage 32: shake your phone
Stage 33: place the bars on the rack in rainbow order, starting with red at the top.
Stage 34: tilt your phone so that the west arrow points north, then enter the directions.
Stage 35: shake your phone, take the purple ball and place it on the hole. Press the button.
Stage 36: shake your phone to make the warrior go away; shake it again to return with a key. Take the key & shake again to put the key in the lock
Stage 37: the sage advises peace :) set your phone down and wait.
Stage 38: click the circles from small to large.
Stage 39: Shake your device to expose the letters EVE on the banner on the right. The code is a date, the eve of a holiday celebrated nearly worldwide. It seems this has to be the first number you enter, so if you tried others already, restart the level.
Stage 40: touch the small square to the left of the screen to show the code.
Stage 41: swipe the screen in the direction indicated by the deer.
Stage 42: take the gun. Tilt your phone to the left and shoot the ninja.
Stage 43: hold your finger on the thermometer until it gets to the top.
Stage 44: use your sword and slice the painting, then tap the button.
Stage 45: drag the ball to the hole, then flick the arrow in the direction indicated to get a key.
Stage 46: keep "pulling" (flicking) down the corner that hangs down until the screen is torn down.
Stage 47: Turn your device upside down and click the button on the floor. Then turn it right side up and click the button  on the wall.
Stage 48: Shake your device to get the blue ball to drop from the banner. Drag it to the first oval "counter" on the floor. Click the 1st counter once (so there is one star/dot showing), click the 2nd counter once, and the 3rd counter 3 times (3 star/dots).
Stage 49: Turn your device upside down 3 times to light the first 3 candles, then shake for the last.
Stage 50: Tap the figure on the right. There are 3 hotspots that will light the candles, mostly on the left side of the figure.




I got frustrated with this because the game does not save my progress (Nexus 7, currently Android 4.4.2, but it wasn't saving in the earlier version either). Hopefully someone else will be able to finish since it's a pretty nice game.

Tuesday, July 30, 2013

Tesshi-e game: Escape from Mr M's Room.

This is gonna suck, cos it's a paid game, which means none of the usual ETR players are around to help.

Then again, it sucks that the people who would pay for Tesshi-e's very high quality games are very few.  She's put out 88 free games, everyone loves them and she gets 4 and 5 stars very consistently, but when it comes to paying a pittance (231 yen = appx $2.35, 1.75 euro, you can't get one cup at Starbucks for that), no one's stepping up to the plate. I've even seen people say "I never pay for a game on principle" -- what principles, pray tell, are those?

Anyway. This is not a walkthrough but just notes I took while playing, which make decent hints.

There are a ton of clues. I've found a TV remote, a slingshot with no ammo, a piece of paper, and a bottle opener.

The TV got me a slingshot.  The TV displays a green man puppet on 2 different channels, and the positions he stands in correspond to the 4 buttons where the actual puppet is sitting. Just concatenate those two sequences and you have the answer.

The 3 Mr Birdys on a 3-letter safe correspond to the pillows of the same colors. Each Birdy has a number. That corresponds to a letter in the word on each pillow.  That got me the corkscrew. I sure could use a bottle of wine right now, but I haven't found one yet.

Ah. totally missed seeing a bottle to the left of the TV. A knife now, and with it a spade key, and with that the SD.

Took me a while to assemble the equation for the 4 digit box. You have to cut the paper and place it over each of the pictures on the wall, then reorder the shapes. You get the equation 16 x 5859 / 12, which is 7812.

Used the camera to flash into the dark space. There's a picture showing which corners to click on the rotating picture, and a key. Key gives a handle (box under the sofa).

Handle used on the machine on the floor. Got a ball. Ball goes in the slingshot for a windup key.

Windup key used on the akebekos, 231133, used in turn on the other akebeko. Have a key.

Opened the panel above the bed. Using the card-suit panels I get equations for their value. Also used the cork to slingshot the last panel high up above the sofa. Now I've got more math to do.

Got the equations; they need to be arranged in the same vertical order as the pictures.

Happy coin: check the key.

Tuesday, July 16, 2013

Fiddling and tweaking

Since releasing MapTag I've made a number of minor adjustments to the UI in response to comments.

However, it doesn't work in IE8 and that will not change.

The more fun part has been adding a visual index of saved games and previously played games which players completed in less than 10 minutes and less than 125km from the target. Click the "more games" tab to view that. It would be nice to dynamically adjust the criteria, but App Engine doesn't allow an inequality query on more than one field. It's a bit of a struggle to understand why it cannot do something that's elementary to any SQL query, until one considers its intended scalability.

So instead, I had to add a boolean field for 'successful', and if I want to change the criteria I have to run a bit of python script* to run through all the records and recompute it.  One hundred twenty-five kilometers is a pretty broad definition of success, but I wanted it a bit broad for countries that don't have a Roman-based alphabet, which is hard for us Roman alphabetters to Google for. For someplace like Thailand, if you get the right country, you still have a chance of "success."

* the server side I originally wrote in Java, but since Python is faster for certain datastore operations, the boyfriend recommended we switch to that. Did I mention I didn't know any Python? or hardly anything about server side programming?