com.google.wolff.androidhunt.Hunt.java Source code

Java tutorial

Introduction

Here is the source code for com.google.wolff.androidhunt.Hunt.java

Source

/*
 * Copyright 2013 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.wolff.androidhunt;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
import android.widget.ImageView;

import org.json.JSONArray;
import org.json.JSONObject;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Random;

public class Hunt {

    static Hunt theHunt;
    static HuntResourceManager hrm;

    public Boolean isShuffled = false;
    public long finishTime;

    public SoundManager soundManager;

    HashMap<String, Boolean> tagsFound;

    HashMap<String, AHTag> tags;
    HashMap<String, Clue> clues;
    ArrayList<Clue> clueList;
    ArrayList<AHTag> tagList;

    private boolean hasSeenIntro = false;

    static final String HAS_SEEN_INTRO_KEY = "HAS_SEEN_INTRO_KEY";

    static final String WRONG_CLUE = "WRONG CLUE";
    static final String ACK = "ACK";
    static final String CLUE_COMPLETE = "CLUE COMPLETE";
    static final String ALREADY_FOUND = "ALREADY FOUND";
    static final String DECOY = "DECOY";
    // The actual text for the DECOY clue.
    static final String DECOY_ID = "decoy";

    /** Returns the singleton hunt object, and initializes it if it's not ready. */
    public static Hunt getHunt(Resources res, Context context) {

        if (theHunt == null) {

            hrm = new HuntResourceManager();
            hrm.unzipFile(res);

            theHunt = new Hunt(hrm.huntJSON, res, context);
            String android_id = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);

            if (android_id == null) {
                // Fall back on devices where ANDROID_ID is not reliable.
                theHunt.shuffle(Integer.parseInt(Settings.Secure.ANDROID_ID, 0));
            } else {
                BigInteger bi = new BigInteger(android_id, 16);
                System.out.println(bi);

                theHunt.shuffle(bi.shortValue());
            }
        }

        return theHunt;
    }

    /** Shuffles the clues.  Note that each clue is marked with
     *  a difficulty group, so that, say, a hard clue can't proceed
     *  an easier clue.
     * @param seed The random number seed.
     */
    public void shuffle(int seed) {
        if (isShuffled) {
            return;
        }
        // Divide into shuffle groups
        ArrayList<ArrayList<Clue>> groups = new ArrayList<ArrayList<Clue>>(10);

        for (int i = 0; i < 10; i++) {
            groups.add(null);
        }

        for (int i = 0; i < clueList.size(); i++) {
            Clue c = clueList.get(i);

            if (groups.get(c.shufflegroup) == null) {
                groups.set(c.shufflegroup, new ArrayList<Clue>());
            }

            groups.get(c.shufflegroup).add(c);
        }

        clueList = new ArrayList<Clue>();

        Random r = new Random(seed);

        for (int i = 0; i < 10; i++) {
            ArrayList<Clue> cl = groups.get(i);

            if (cl == null)
                continue;

            Collections.shuffle(cl, r);

            clueList.addAll(cl);
        }

        isShuffled = true;
    }

    /**
     * Saves the player's progress.
     *
     * @param res
     * @param context
     */
    public void save(Resources res, Context context) {
        SharedPreferences sharedPref = context.getSharedPreferences(res.getString(R.string.preference_file_key),
                Context.MODE_PRIVATE);

        Editor editor = sharedPref.edit();

        for (String key : tagsFound.keySet()) {
            editor.putBoolean(key, tagsFound.get(key));
        }

        editor.putBoolean(HAS_SEEN_INTRO_KEY, hasSeenIntro);

        editor.commit();

    }

    /** Loads player progress. */
    public void restore(Resources res, Context context) {
        SharedPreferences sharedPref = context.getSharedPreferences(res.getString(R.string.preference_file_key),
                Context.MODE_PRIVATE);

        for (String tag : tags.keySet()) {
            Boolean val = sharedPref.getBoolean(tag, false);
            tagsFound.put(tag, val);
        }

        hasSeenIntro = sharedPref.getBoolean(HAS_SEEN_INTRO_KEY, false);
    }

    /** Generates the entire hunt structure from JSON */
    Hunt(String jsonString, Resources res, Context context) {

        soundManager = new SoundManager(context);

        try {
            clues = new HashMap<String, Clue>();
            clueList = new ArrayList<Clue>();
            tags = new HashMap<String, AHTag>();

            JSONObject huntObject = new JSONObject(jsonString);
            JSONArray clueObjList = huntObject.getJSONArray("clues");
            tagList = new ArrayList<AHTag>();

            int length = clueObjList.length();
            for (int i = 0; i < length; i++) {
                JSONObject clueObj = clueObjList.getJSONObject(i);

                Clue clue = new Clue(clueObj.getString("id"), clueObj.getString("displayName"),
                        clueObj.getString("displayText"), clueObj.getString("displayImage"));

                clue.shufflegroup = clueObj.getInt("shufflegroup");

                JSONArray tagObjList = clueObj.getJSONArray("tags");

                int tagLength = tagObjList.length();

                clueList.add(clue);

                for (int j = 0; j < tagLength; j++) {
                    JSONObject tagObj = tagObjList.getJSONObject(j);
                    AHTag tag = new AHTag(tagObj.getString("id"));
                    tag.clueId = clue.id;
                    clue.addTag(tag);
                    tags.put(tag.id, tag);
                    tagList.add(tag);
                }

                clues.put(clue.id, clue);
            }
        } catch (Exception e) {
            if (e != null)
                Log.e("JSON Parser", "Error parsing Hunt data " + e.toString());
        }

        reset();
        restore(res, context);
    }

    /** Deletes all player progress.*/
    public void reset() {
        tagsFound = new HashMap<String, Boolean>();
        for (AHTag tag : tagList) {
            tagsFound.put(tag.id, false);
        }

        hasSeenIntro = false;
    }

    /** Gets active clue.
     *
     * One objection to this is that we are computing progress rather than remembering.
     *
     * This is deliberate; if we cache progress, we are likely
     * to get be wrong, especially during state transitions.  The number of
     * operations here is very small and it is called infrequently, so this saves us the
     * trouble of maintaining and persisting progress.
     *
     * If this were a performance issue (called in an inner loop, for example), of course
     * we would cache this.
     * @return The current clue, or else null if you are finished.
     */
    Clue getCurrentClue() {
        int length = clueList.size();
        for (int i = 0; i < length; i++) {
            Clue clue = clueList.get(i);

            if (!isClueFinished(clue)) {
                return clue;
            }
        }

        // The hunt is complete!
        return null;
    }

    /** What clue have I *just* completed? */
    public Clue getLastCompletedClue() {
        int length = clueList.size();
        Clue lastBestClue = null;
        for (int i = 0; i < length; i++) {
            Clue clue = clueList.get(i);

            if (!isClueFinished(clue)) {
                return lastBestClue;
            }

            lastBestClue = clue;
        }

        // The hunt is complete.
        return lastBestClue;
    }

    public Boolean isTagFound(String id) {
        if (!tagsFound.containsKey(id)) {
            return false;
        }

        return tagsFound.get(id);
    }

    /**
     * Called when a tag is scanned.  Checks the hunt
     *
     * @param tagId the short string that represents the tag found.
     * @return
     */
    String findTag(String tagId) {

        if (tagId.equals(DECOY_ID)) {
            return DECOY;
        }
        // See if this tag is part of this clue
        Clue clue = getCurrentClue();

        AHTag tag = tags.get(tagId);

        if (tag == null) {
            return WRONG_CLUE;
        }

        if (clue.id.equals(tag.clueId)) {
            if (isTagFound(tagId)) {
                return ALREADY_FOUND;
            }

            tagsFound.put(tag.id, true);

            if (isClueFinished(clue)) {
                return CLUE_COMPLETE;
            }

            return ACK;
        }

        return WRONG_CLUE;
    }

    /** Have we found all the clues?*/
    Boolean isClueFinished(Clue clue) {
        for (AHTag tag : clue.tags) {
            if (!isTagFound(tag.id)) {
                return false;
            }
        }

        return true;
    }

    public int getTotalClues() {
        return clueList.size();
    }

    /** Count from 1. */
    public int getClueDisplayNumber(Clue clue) {
        return clueList.indexOf(clue) + 1;
    }

    /** Count from 0. */
    public int getClueIndex(Clue clue) {
        return clueList.indexOf(clue);
    }

    /** Return value: Whether or not it needs to load from web. */
    public void setClueImage(Resources res, ImageView imgView) {
        final Clue clue = getCurrentClue();

        if (hrm.drawables.get(clue.displayImage) == null) {
            imgView.setImageDrawable(res.getDrawable(R.drawable.ab_icon));
        } else {
            imgView.setImageDrawable(hrm.drawables.get(clue.displayImage));
        }
    }

    public boolean hasSeenIntro() {
        return hasSeenIntro;
    }

    public void setIntroSeen(Boolean val) {
        hasSeenIntro = val;
    }

    public boolean isComplete() {
        return (getCurrentClue() == null);
    }
}