package com.icystar.findnumber;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.util.FloatMath;
import android.view.View;
import android.widget.TextView;
public class Game {
public static Game[] games = new Game[] { new Game(), new Game() };
public static class Number {
public int number;
public String text;
public int digits;
public int sizeIndex;
public float size;
public float scaleX;
public int fontIndex;
Typeface font;
public Rect rect = new Rect();
public Point position = new Point();
public void save(FileOutputStream fos) throws IOException {
Serializer.writeInt(fos, number);
Serializer.writeFloat(fos, size);
Serializer.writeFloat(fos, scaleX);
Serializer.writeInt(fos, fontIndex);
Serializer.writeInt(fos, rect.left);
Serializer.writeInt(fos, rect.right);
Serializer.writeInt(fos, rect.top);
Serializer.writeInt(fos, rect.bottom);
Serializer.writeInt(fos, position.x);
Serializer.writeInt(fos, position.y);
}
public void load(FileInputStream fis) throws IOException {
number = Serializer.readInt(fis);
size = Serializer.readFloat(fis);
scaleX = Serializer.readFloat(fis);
fontIndex = Serializer.readInt(fis);
rect.left = Serializer.readInt(fis);
rect.right = Serializer.readInt(fis);
rect.top = Serializer.readInt(fis);
rect.bottom = Serializer.readInt(fis);
position.x = Serializer.readInt(fis);
position.y = Serializer.readInt(fis);
text = Integer.toString(number);
digits = text.length();
sizeIndex = 0;
font = fonts[fontIndex];
}
}
public interface Listener {
public void onLock(Game game);
public void onWin(Game game, float time);
public void onTouchNumber(Game game, int touchedNumber, int maxNumber, boolean win);
}
private static final String filename = "game.sav";
private static final int sizesCount = 100;
private static final int generationTrials = 100;
private static final int fontTrials = 100;
private static final int positionTrials = 100;
public static final Typeface[] fonts = new Typeface[] {
Typeface.create(Typeface.SERIF, Typeface.NORMAL),
Typeface.create(Typeface.SERIF, Typeface.BOLD),
Typeface.create(Typeface.SERIF, Typeface.ITALIC),
Typeface.create(Typeface.SERIF, Typeface.BOLD_ITALIC),
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL),
Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD),
Typeface.create(Typeface.SANS_SERIF, Typeface.ITALIC),
Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC)
};
private static Object staticHandle = new Object();
private static boolean stateLoaded = false;
private static String[] playerNames;
private int width = 0;
private int height = 0;
private float minSize;
private float maxSize;
private int initialMaxSizeIndex;
private int maxDigits;
private Rect[][][] sizes;
private TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private TextView numberTextView;
private TextView timeTextView;
private TextView playerNameTextView;
private NumbersView numbersView;
private View lockView;
private Listener listener;
private Timer timer;
private Object handle = new Object();
private int waitToGenerate = 0;
private List<Point> invalidTouches = new ArrayList<Point>();
// serialization data
private Number[] numbers = new Number[0];
private int currentNumberIndex = -1;
private int currentNumber = 0;
private float time = 0;
private float lockTime = 0;
private boolean paused = true;
private boolean playing = false;
private boolean isWin = false;
private boolean isLose = false;
public void save(FileOutputStream fos) throws IOException {
synchronized (handle) {
Serializer.writeInt(fos, currentNumberIndex);
Serializer.writeInt(fos, Math.round(time));
Serializer.writeInt(fos, Math.round(lockTime));
Serializer.writeInt(fos, paused ? 1 : 0);
Serializer.writeInt(fos, playing ? 1 : 0);
Serializer.writeInt(fos, numbers.length);
for(Number number : numbers)
number.save(fos);
}
}
public void load(FileInputStream fis) throws IOException {
synchronized (handle) {
currentNumberIndex = Serializer.readInt(fis);
time = Serializer.readInt(fis);
lockTime = Serializer.readInt(fis);
paused = Serializer.readInt(fis) != 0;
playing = Serializer.readInt(fis) != 0;
numbers = new Number[Serializer.readInt(fis)];
for(int i = 0; i < numbers.length; i++) {
numbers[i] = new Number();
numbers[i].load(fis);
}
currentNumber = currentNumberIndex >= 0 && currentNumberIndex < numbers.length
? numbers[currentNumberIndex].number : 0;
}
}
Game() {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() { onTime(1); }
}, 1000, 1000);
}
private float internalCalcSize(int index) {
return minSize + (maxSize - minSize)*(float)index/(float)(sizesCount-1);
}
private void internalCalcSizes(int count) {
maxSize = Math.min(width, height)/2f;
minSize = count < 50 ? maxSize/5f : maxSize/10f;
initialMaxSizeIndex = sizesCount/2;
if (count > 25) initialMaxSizeIndex = sizesCount/4;
if (count > 50) initialMaxSizeIndex = sizesCount/6;
if (initialMaxSizeIndex < 1) initialMaxSizeIndex = 1;
String template = "MMMMMMMMMMMMMMMMMMMMMMMMMMMMM";
sizes = new Rect[fonts.length][sizesCount][maxDigits + 1];
paint.setTextScaleX(1f);
for(int fontIndex = 0; fontIndex < fonts.length; fontIndex++) {
paint.setTypeface(fonts[fontIndex]);
for(int sizeIndex = 0; sizeIndex < sizesCount; sizeIndex++) {
paint.setTextSize(internalCalcSize(sizeIndex));
for(int digits = 0; digits <= maxDigits; digits++) {
sizes[fontIndex][sizeIndex][digits] = new Rect();
paint.getTextBounds(template, 0, digits, sizes[fontIndex][sizeIndex][digits]);
}
}
}
}
private boolean internalCheckRect(Rect rect, int count, int exclude) {
if (rect.left < 0 || rect.top < 0 || rect.right > width || rect.bottom > height)
return false;
for(int i = 0; i < count; i++)
if (Rect.intersects(rect, numbers[i].rect))
return false;
return true;
}
private void internalGenerate(int count) {
waitToGenerate = 0;
time = 0;
lockTime = 0;
playing = false;
isWin = false;
isLose = false;
if (timer != null) timer.cancel();
timer = null;
maxDigits = count < 10 ? 1 : (count < 100 ? 2 : (count < 1000 ? 3 : 4));
internalCalcSizes(count);
numbers = new Number[count];
Random random = new Random();
// fill digits
int[] remainingNumbers = new int[count];
for(int i = 0; i < count; i++) remainingNumbers[i] = i+1;
for(int i = 0; i < count; i++) {
int j = random.nextInt(count - i);
numbers[i] = new Number();
numbers[i].number = remainingNumbers[j];
numbers[i].text = Integer.toString(numbers[i].number);
numbers[i].digits = numbers[i].text.length();
for(int k = j+1; k < count-i; k++) remainingNumbers[k-1] = remainingNumbers[k];
}
// generate numbers
boolean success = false;
for(int generationTry = 0; !success && generationTry < generationTrials; generationTry++) {
for(int i = 0; i < count; i++) {
success = false;
Number number = numbers[i];
for(int fontTry = 0; !success && fontTry < fontTrials && fontTry < initialMaxSizeIndex; fontTry++) {
number.fontIndex = random.nextInt(fonts.length);
number.sizeIndex = random.nextInt(initialMaxSizeIndex - fontTry);
number.font = fonts[number.fontIndex];
number.size = internalCalcSize(number.sizeIndex);
number.scaleX = 1f; //random.nextFloat()*1.5f + 0.5f;
Rect r = sizes[number.fontIndex][number.sizeIndex][number.digits];
int w = Math.round((r.right - r.left)*number.scaleX);
int h = r.bottom - r.top;
if (w < width && h < height) {
for(int positionTry = 0; !success && positionTry < positionTrials; positionTry++) {
int x = random.nextInt(width - w);
int y = random.nextInt(height - h);
number.rect.set(x, y, x+w, y+h);
number.position.set(number.rect.left - r.left, number.rect.top - r.top);
if (internalCheckRect(number.rect, i, -1))
success = true;
}
}
}
if (!success) break;
}
}
if (success) {
// enlarge numbers
boolean enlarged = false;
Rect[] rects = new Rect[] { new Rect(), new Rect(), new Rect(), new Rect() };
while (enlarged) {
enlarged = true;
for(int i = 0; i < count; i++) {
Number number = numbers[i];
if (number.sizeIndex < sizesCount) {
Rect r = sizes[number.fontIndex][number.sizeIndex+1][number.digits];
int w = Math.round((r.right - r.left)*number.scaleX);
int h = r.bottom - r.top;
int validCount = 0;
for(int j = 0; j < 3; j++) {
int x = j % 2 == 0 ? number.rect.left : number.rect.right - w;
int y = j / 2 == 0 ? number.rect.top : number.rect.bottom - h;
rects[validCount].set(x, y, x+w, y+h);
if (internalCheckRect(rects[validCount], count, i)) validCount++;
}
if (validCount > 0) {
number.sizeIndex++;
number.size = internalCalcSize(number.sizeIndex);
number.rect.set(rects[random.nextInt(validCount)]);
number.position.set(number.rect.left - r.left, number.rect.top - r.top);
enlarged = true;
}
}
}
}
} else {
numbers = new Number[0];
}
time = 0;
lockTime = 0;
playing = true;
internalSetCurrentNumber(1);
internalUpdateViews();
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() { onTime(1); }
}, 1000, 1000);
}
private static boolean checkDistance(int x0, int y0, int x1, int y1, int distSqr)
{ return (x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) < distSqr; }
private boolean internalCheckTouch(int x, int y) {
Number number = null;
try { number = numbers[currentNumberIndex]; } catch (Exception e) { }
if (number != null) {
int radius = Math.min(width, height)/20;
int radiusSqr = radius*radius;
Rect rect = number.rect;
if (new Rect(rect.left - radius, rect.top, rect.right + 2*radius, rect.bottom ).contains(x, y)
|| new Rect(rect.left, rect.top - radius, rect.right, rect.bottom + 2*radius ).contains(x, y)
|| checkDistance(x, y, rect.left, rect.top, radiusSqr)
|| checkDistance(x, y, rect.right, rect.top, radiusSqr)
|| checkDistance(x, y, rect.left, rect.bottom, radiusSqr)
|| checkDistance(x, y, rect.right, rect.bottom, radiusSqr)
) {
return true;
}
}
return false;
}
private void internalSetCurrentNumber(int value) {
currentNumberIndex = -1;
currentNumber = 0;
for(int i = 0; i < numbers.length; i++)
if (numbers[i].number == value)
{ currentNumberIndex = i; currentNumber = value; break; }
internalUpdateNumberTextView();
}
private void internalUpdateNumberTextView() {
if (numberTextView != null) {
int numbersCount = numbers.length;
int numbersFound = isWin ? numbersCount : currentNumber - 1;
int id = R.string.currentNumber;
if (isLose) id = R.string.currentNumberLose;
if (isWin) { id = R.string.currentNumberWin; numbersFound = numbersCount; }
final TextView textView = numberTextView;
final String text = textView.getContext().getString(id, currentNumber, numbersFound, numbersCount);
textView.post(new Runnable() { @Override public void run() { textView.setText(text); } });
}
}
private void internalUpdateTimeTextView() {
if (timeTextView != null) {
int min = (int)FloatMath.floor(time/60);
int sec = (int)FloatMath.floor(time - min*60);
String minStr = Integer.toString(min);
String secStr = Integer.toString(sec);
while (minStr.length() < 2) minStr = "0" + minStr;
while (secStr.length() < 2) secStr = "0" + secStr;
final TextView textView = timeTextView;
final String text = minStr + ":" + secStr;
textView.post(new Runnable() { @Override public void run() { textView.setText(text); } });
}
}
private void internalUpdatePlayerNameTextView() {
if (playerNameTextView != null) {
final TextView textView = playerNameTextView;
final int visibility = playing ? View.VISIBLE : View.GONE;
textView.post(new Runnable() { @Override public void run() { textView.setVisibility(visibility); } });
}
}
private void internalUpdateNumbersView() {
if (numbersView != null) {
final NumbersView view = numbersView;
view.post(new Runnable() { @Override public void run() { view.invalidate(); } });
}
}
private void internalUpdateLockView() {
if (lockView != null) {
final View view = lockView;
final int visibility = lockTime <= 0 ? View.GONE : View.VISIBLE;
view.post(new Runnable() { @Override public void run() {
if (view.getVisibility() != visibility) view.setVisibility(visibility);
if (visibility == View.VISIBLE) view.invalidate();
} });
}
}
private void internalUpdateViews() {
internalUpdateNumberTextView();
internalUpdateTimeTextView();
internalUpdatePlayerNameTextView();
internalUpdateNumbersView();
internalUpdateLockView();
}
public void start(int count) {
synchronized (handle) {
if (width > 0 && height > 0)
internalGenerate(count);
else
waitToGenerate = count;
}
}
public void stop() {
synchronized (handle) {
playing = false;
isWin = false;
isLose = false;
numbers = new Number[0];
internalUpdateNumberTextView();
internalUpdatePlayerNameTextView();
}
}
public void lose() {
synchronized (handle) {
playing = false;
isWin = false;
isLose = true;
internalUpdateNumberTextView();
internalUpdatePlayerNameTextView();
}
}
public void win() {
synchronized (handle) {
playing = false;
isWin = true;
isLose = false;
internalUpdateNumberTextView();
internalUpdatePlayerNameTextView();
}
}
public static void lock() {
for(Game game : games) {
synchronized (game.handle) {
game.lockTime = 5;
game.internalUpdateLockView();
}
if (game.listener != null) game.listener.onLock(game);
}
}
public boolean getPlaying()
{ synchronized (handle) { return playing; } }
public boolean getPaused()
{ synchronized (handle) { return paused; } }
public void setPaused(boolean value)
{ synchronized (handle) { paused = value; } }
public int getCount()
{ synchronized (handle) { return numbers.length; } }
public int getCurrentNumber() {
synchronized (handle) {
try { return numbers[currentNumberIndex].number; } catch (Exception e) { }
return 0;
}
}
public void onLayout(int width, int height) {
synchronized (handle) {
this.width = width;
this.height = height;
if (width > 0 && height > 0 && waitToGenerate > 0)
internalGenerate(waitToGenerate);
}
}
public void onTouch(int x, int y) {
boolean touch = false;
int touchedNumber = 0;
int maxNumber = 0;
boolean win = false;
float winTime = 0;
synchronized (handle) {
if (playing && !paused && lockTime <= 0) {
if (internalCheckTouch(x, y)) {
touch = true;
touchedNumber = currentNumber;
maxNumber = numbers.length;
if (currentNumber < numbers.length) {
internalSetCurrentNumber(currentNumber + 1);
} else {
this.win();
win = true;
winTime = time;
}
invalidTouches.clear();
} else {
invalidTouches.add(new Point(x, y));
if (invalidTouches.size() > 1) {
int maxDist = Math.min(width, height)/4;
int places = 1;
Point min = new Point(width, height);
Point max = new Point(0, 0);
for(Point p : invalidTouches) {
min.x = Math.min(min.x, p.x);
min.y = Math.min(min.y, p.y);
max.x = Math.max(max.x, p.x);
max.y = Math.max(max.y, p.y);
if (max.x - min.x > maxDist || max.y - min.y > maxDist) {
places++;
min.set(p.x, p.y);
max.set(p.x, p.y);
}
}
if (places > 3) {
lock();
invalidTouches.clear();
}
}
}
}
}
if (touch && listener != null) listener.onTouchNumber(this, touchedNumber, maxNumber, win);
if (win && listener != null) listener.onWin(this, winTime);
}
public void onTime(float time) {
synchronized (handle) {
if (playing && !paused) {
this.time += time;
lockTime -= time;
if (lockTime <= 0) lockTime = 0;
internalUpdateTimeTextView();
internalUpdateLockView();
if (Math.round(this.time) % 3 == 0 && invalidTouches.size() > 0)
invalidTouches.remove(0);
}
}
}
public void draw(Canvas canvas) {
synchronized (handle) {
for(int i = 0; i < numbers.length; i++) {
Number number = numbers[i];
paint.setTypeface(number.font);
paint.setTextSize(number.size);
paint.setTextScaleX(number.scaleX);
canvas.drawText(number.text, number.position.x, number.position.y, paint);
}
}
}
public void setListener(Listener value)
{ synchronized (handle) { listener = value; } }
public void setNumberTextView(TextView value)
{ synchronized (handle) { numberTextView = value; internalUpdateNumberTextView(); } }
public void setTimeTextView(TextView value)
{ synchronized (handle) { timeTextView = value; internalUpdateTimeTextView(); } }
public void setPlayerNameTextView(TextView value)
{ synchronized (handle) { playerNameTextView = value; internalUpdatePlayerNameTextView(); } }
public void setNumbersView(NumbersView value)
{ synchronized (handle) { numbersView = value; internalUpdateNumbersView(); } }
public void setLockView(View value)
{ synchronized (handle) { lockView = value; internalUpdateLockView(); } }
private static void save(Context context) {
try {
FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
for(Game game : games) game.save(fos);
Serializer.writeInt(fos, playerNames.length);
if (playerNames == null) playerNames = new String[0];
for(String name : playerNames)
Serializer.writeString(fos, name);
ScoreboardActivity.save(fos);
Serializer.writeInt(fos, SoundPlayer.getEnabled() ? 1 : 0);
} catch (Exception e) { }
}
private static void load(Context context) {
for(Game game : games) game.stop();
try {
FileInputStream fis = context.openFileInput(filename);
for(Game game : games) game.load(fis);
playerNames = new String[Serializer.readInt(fis)];
for(int i = 0; i < playerNames.length; i++)
playerNames[i] = Serializer.readString(fis);
ScoreboardActivity.load(fis);
SoundPlayer.setEnabled(Serializer.readInt(fis) != 0);
} catch (Exception e) {
for(Game game : games) game.stop();
//e.printStackTrace();
}
if (playerNames == null) playerNames = new String[0];
}
public static void saveState(Context context) {
synchronized (staticHandle) {
save(context);
}
}
public static void loadState(Context context) {
synchronized (staticHandle) {
if (!stateLoaded) {
stateLoaded = true;
load(context);
}
}
}
public static int getMode() {
boolean g0 = games[0].getCount() > 0;
boolean g1 = games[1].getCount() > 0;
if (g0 && g1) return 2;
if (g0 && !g1) return 1;
return 0;
}
public static boolean getAnyPlaying() {
switch (getMode()) {
case 1:
return games[0].getPlaying();
case 2:
return games[0].getPlaying() || games[1].getPlaying();
}
return false;
}
public static String getPlayerName(int index) {
synchronized (staticHandle) {
try { return playerNames[index] == null ? "" : playerNames[index]; } catch (Exception e) { }
return "";
}
}
public static void setPlayerName(int index, String name) {
synchronized (staticHandle) {
if (name != null && name.length() > 0) {
try {
playerNames = Serializer.enlargeArrayIfNeed(String.class, playerNames, index+1);
playerNames[index] = name;
} catch (Exception e) { }
}
}
}
}