Project

General

Profile

Download (13.3 KB) Statistics
| Branch: | Tag: | Revision:

magiccube / src / main / java / org / distorted / external / RubikScores.java @ 24679c47

1
///////////////////////////////////////////////////////////////////////////////////////////////////
2
// Copyright 2020 Leszek Koltunski                                                               //
3
//                                                                                               //
4
// This file is part of Magic Cube.                                                              //
5
//                                                                                               //
6
// Magic Cube is proprietary software licensed under an EULA which you should have received      //
7
// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
8
///////////////////////////////////////////////////////////////////////////////////////////////////
9

    
10
package org.distorted.external;
11

    
12
import java.util.HashMap;
13
import java.util.UUID;
14

    
15
import android.content.Context;
16
import android.content.SharedPreferences;
17
import android.telephony.TelephonyManager;
18

    
19
import com.google.firebase.crashlytics.FirebaseCrashlytics;
20

    
21
import org.distorted.main.BuildConfig;
22
import org.distorted.objects.RubikObject;
23
import org.distorted.objects.RubikObjectList;
24
import org.distorted.screens.RubikScreenPlay;
25

    
26
import static org.distorted.objectlib.main.ObjectType.MAX_SCRAMBLES;
27

    
28
///////////////////////////////////////////////////////////////////////////////////////////////////
29
// hold my own scores, and some other statistics.
30

    
31
public class RubikScores
32
  {
33
  public static final int RECORD_FIRST   = 0;
34
  public static final int RECORD_NEW     = 1;
35
  public static final int RECORD_NOT_NEW = 2;
36

    
37
  public static final int MAX_RECORD = 10;
38
  public static final int MULT = 1000000;
39
  public static final long NO_RECORD = Long.MAX_VALUE;
40
  private static RubikScores mThis;
41

    
42
  private String mName, mCountry;
43
  private boolean mNameIsVerified;
44
  private int mNumRuns;
45
  private int mNumPlays;
46
  private int mNumWins;
47
  private int mDeviceID;
48

    
49
  private static class MapValue
50
    {
51
    long record;
52
    boolean submitted;
53

    
54
    MapValue(long rec,int sub)
55
      {
56
      record    = rec;
57
      submitted = sub!=0;
58
      }
59
    }
60

    
61
  private final HashMap<Integer,MapValue> mMap;
62

    
63
///////////////////////////////////////////////////////////////////////////////////////////////////
64

    
65
  private RubikScores()
66
    {
67
    mMap = new HashMap<>();
68

    
69
    mName = "";
70
    mCountry = "un";
71

    
72
    mNameIsVerified = false;
73

    
74
    mNumPlays= -1;
75
    mNumRuns = -1;
76
    mDeviceID= -1;
77
    mNumWins =  0;
78
    }
79

    
80
///////////////////////////////////////////////////////////////////////////////////////////////////
81

    
82
  private int mapKey(int object,int level)
83
    {
84
    return object*MULT + level;
85
    }
86

    
87
///////////////////////////////////////////////////////////////////////////////////////////////////
88

    
89
  private int privateGetDeviceID()
90
    {
91
    int id;
92

    
93
    try
94
      {
95
      String s = UUID.randomUUID().toString();
96
      id = s.hashCode();
97
      }
98
    catch(Exception ex)
99
      {
100
      id = 0;
101
      android.util.Log.e("scores", "Exception in getDeviceID()");
102
      }
103

    
104
    return id<0 ? -id : id;
105
    }
106

    
107
///////////////////////////////////////////////////////////////////////////////////////////////////
108

    
109
  synchronized void successfulSubmit()
110
    {
111
    mNameIsVerified = true;
112

    
113
    for(int key: mMap.keySet())
114
      {
115
      MapValue value = mMap.get(key);
116
      if( value!=null ) value.submitted = true;
117
      }
118
    }
119

    
120
///////////////////////////////////////////////////////////////////////////////////////////////////
121

    
122
  int getDeviceID()
123
    {
124
    return mDeviceID;
125
    }
126

    
127
///////////////////////////////////////////////////////////////////////////////////////////////////
128

    
129
  synchronized boolean thereAreUnsubmittedRecords()
130
    {
131
    for(int key: mMap.keySet())
132
      {
133
      MapValue value = mMap.get(key);
134
      if( value!=null && !value.submitted && value.record<NO_RECORD) return true;
135
      }
136

    
137
    return false;
138
    }
139

    
140
///////////////////////////////////////////////////////////////////////////////////////////////////
141

    
142
  synchronized String getRecordList(String strObj, String strLvl, String strTim)
143
    {
144
    StringBuilder builderObj = new StringBuilder();
145
    StringBuilder builderLvl = new StringBuilder();
146
    StringBuilder builderTim = new StringBuilder();
147
    boolean first = true;
148

    
149
    for(int key: mMap.keySet())
150
      {
151
      MapValue value = mMap.get(key);
152

    
153
      if( value!=null && !value.submitted && value.record<NO_RECORD)
154
        {
155
        if( !first )
156
          {
157
          builderObj.append(',');
158
          builderLvl.append(',');
159
          builderTim.append(',');
160
          }
161
        first=false;
162

    
163
        RubikObject object = RubikObjectList.getObject(key/MULT);
164

    
165
        if( object!=null )
166
          {
167
          builderObj.append(object.getUpperName());
168
          builderLvl.append(key%MULT);
169
          builderTim.append(value.record);
170
          }
171
        }
172
      }
173

    
174
    return strObj+builderObj.toString()+strLvl+builderLvl.toString()+strTim+builderTim.toString();
175
    }
176

    
177
///////////////////////////////////////////////////////////////////////////////////////////////////
178
// Public API
179
///////////////////////////////////////////////////////////////////////////////////////////////////
180

    
181
  public boolean isVerified()
182
    {
183
    return mNameIsVerified;
184
    }
185

    
186
///////////////////////////////////////////////////////////////////////////////////////////////////
187

    
188
  public int getNumPlays()
189
    {
190
    return mNumPlays;
191
    }
192

    
193
///////////////////////////////////////////////////////////////////////////////////////////////////
194

    
195
  public int getNumRuns()
196
    {
197
    return mNumRuns;
198
    }
199

    
200
///////////////////////////////////////////////////////////////////////////////////////////////////
201

    
202
  public String getName()
203
    {
204
    return mName;
205
    }
206

    
207
///////////////////////////////////////////////////////////////////////////////////////////////////
208

    
209
  public String getCountry()
210
    {
211
    return mCountry;
212
    }
213

    
214
///////////////////////////////////////////////////////////////////////////////////////////////////
215

    
216
  public void incrementNumPlays()
217
    {
218
    mNumPlays++;
219
    }
220

    
221
///////////////////////////////////////////////////////////////////////////////////////////////////
222

    
223
  public void incrementNumRuns()
224
    {
225
    mNumRuns++;
226
    }
227

    
228
///////////////////////////////////////////////////////////////////////////////////////////////////
229

    
230
  public int incrementNumWins()
231
    {
232
    mNumWins++;
233
    return mNumWins;
234
    }
235

    
236
///////////////////////////////////////////////////////////////////////////////////////////////////
237

    
238
  public void setName(String newName)
239
    {
240
    mName = newName;
241
    }
242

    
243
///////////////////////////////////////////////////////////////////////////////////////////////////
244

    
245
  public synchronized int setRecord(int object, int level, long record)
246
    {
247
    int key = mapKey(object,level);
248
    MapValue oldValue = mMap.get(key);
249

    
250
    if( oldValue==null )
251
      {
252
      MapValue value = new MapValue(record,0);
253
      mMap.put(key,value);
254
      return RECORD_FIRST;
255
      }
256

    
257
    long oldRecord = oldValue.record;
258

    
259
    if( oldRecord>record)
260
      {
261
      MapValue value = new MapValue(record,0);
262
      mMap.put(key,value);
263
      return RECORD_NEW;
264
      }
265

    
266
    return RECORD_NOT_NEW;
267
    }
268

    
269
///////////////////////////////////////////////////////////////////////////////////////////////////
270

    
271
  public synchronized long getRecord(int object, int level)
272
    {
273
    int key = mapKey(object,level);
274
    MapValue value = mMap.get(key);
275
    return value!=null ? value.record : NO_RECORD;
276
    }
277

    
278
///////////////////////////////////////////////////////////////////////////////////////////////////
279

    
280
  public synchronized boolean isSolved(int object, int level)
281
    {
282
    int key = mapKey(object,level);
283
    MapValue value = mMap.get(key);
284
    return value!=null && value.record<NO_RECORD;
285
    }
286

    
287
///////////////////////////////////////////////////////////////////////////////////////////////////
288

    
289
  public void setCountry(Context context)
290
    {
291
    TelephonyManager tM =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
292

    
293
    if( tM!=null )
294
      {
295
      mCountry = tM.getSimCountryIso();
296

    
297
      if( mCountry==null || mCountry.length()<=1 )
298
        {
299
        mCountry = tM.getNetworkCountryIso();
300
        }
301
      }
302

    
303
    // Special case: Dominicana. Its ISO-3166-alpha-2 country code is 'do' which we can't have here
304
    // because we later on map this to a resource name (the flag) and 'do' is a reserved Java keyword
305
    // and can't be a resource name.
306

    
307
    if( mCountry.equals("do") ) mCountry = "dm";
308
    }
309

    
310
///////////////////////////////////////////////////////////////////////////////////////////////////
311

    
312
  public void setCountry(String country)
313
    {
314
    mCountry = country;
315

    
316
    if( mCountry.equals("do") ) mCountry = "dm";  // see above
317
    }
318

    
319
///////////////////////////////////////////////////////////////////////////////////////////////////
320

    
321
  public static RubikScores getInstance()
322
    {
323
    if( mThis==null ) mThis = new RubikScores();
324
    return mThis;
325
    }
326

    
327
///////////////////////////////////////////////////////////////////////////////////////////////////
328

    
329
  public synchronized void savePreferences(SharedPreferences.Editor editor)
330
    {
331
    int numObjects = RubikObjectList.getNumObjects();
332
    StringBuilder builder = new StringBuilder();
333

    
334
    for(int level=0; level<=MAX_RECORD; level++)
335
      {
336
      builder.setLength(0);
337

    
338
      for(int object=0; object<numObjects; object++)
339
        {
340
        int key = mapKey(object,level);
341
        RubikObject obj = RubikObjectList.getObject(object);
342
        MapValue value = mMap.get(key);
343

    
344
        if( obj!=null && value!=null && value.record<NO_RECORD )
345
          {
346
          builder.append(obj.getUpperName());
347
          builder.append("=");
348
          builder.append(value.record);
349
          builder.append(",");
350
          builder.append(value.submitted ? 1:0 );
351
          builder.append(" ");
352
          }
353
        }
354

    
355
      editor.putString("scores_record"+level, builder.toString());
356
      }
357

    
358
    editor.putString("scores_name"  , mName  );
359
    editor.putBoolean("scores_isVerified", mNameIsVerified);
360
    editor.putInt("scores_numPlays", mNumPlays);
361
    editor.putInt("scores_numRuns" , mNumRuns );
362
    editor.putInt("scores_deviceid", mDeviceID);
363
    editor.putInt("scores_review"  , mNumWins );
364
    }
365

    
366
///////////////////////////////////////////////////////////////////////////////////////////////////
367

    
368
  public synchronized void restorePreferences(SharedPreferences preferences)
369
    {
370
    String recordStr, subStr, nameStr, timeStr, submStr, errorStr="";
371
    int start, end, equals, comma, ordinal, subm;
372
    long time;
373
    boolean thereWasError = false;
374
    int numObjects = RubikObjectList.getNumObjects();
375

    
376
    for(int level=0; level<=MAX_SCRAMBLES; level++)
377
      {
378
      recordStr = preferences.getString("scores_record"+level, null);
379
      if( recordStr==null ) continue;
380
      start = end = 0;
381

    
382
      while( end!=-1 )
383
        {
384
        end = recordStr.indexOf(" ", start);
385

    
386
        if( end==-1 ) subStr = recordStr.substring(start);
387
        else          subStr = recordStr.substring(start,end);
388

    
389
        start = end+1;
390

    
391
        equals = subStr.indexOf("=");
392
        comma  = subStr.indexOf(",");
393

    
394
        if( equals>=0 && comma>=0 )
395
          {
396
          nameStr = subStr.substring(0,equals);
397
          timeStr = subStr.substring(equals+1,comma);
398
          submStr = subStr.substring(comma+1);
399

    
400
          ordinal = RubikObjectList.getOrdinal(nameStr);
401

    
402
          if( ordinal>=0 && ordinal<numObjects )
403
            {
404
            time = Long.parseLong(timeStr);
405
            subm = Integer.parseInt(submStr);
406

    
407
            if( subm>=0 && subm<=1 )
408
              {
409
              MapValue value = new MapValue(time,subm);
410
              int key = mapKey(ordinal,level);
411
              mMap.put(key,value);
412
              }
413
            else
414
              {
415
              errorStr += ("error1: subm="+subm+" obj: "+nameStr+"\n");
416
              thereWasError= true;
417
              }
418
            }
419
          else
420
            {
421
            errorStr += ("error2: object="+ordinal+" obj: "+nameStr+"\n");
422
            thereWasError = true;
423
            }
424
          }
425
        }
426
      }
427

    
428
    mName           = preferences.getString("scores_name"  , "" );
429
    mNameIsVerified = preferences.getBoolean("scores_isVerified", false);
430
    mNumPlays       = preferences.getInt("scores_numPlays", 0);
431
    mNumRuns        = preferences.getInt("scores_numRuns" , 0);
432
    mDeviceID       = preferences.getInt("scores_deviceid",-1);
433
    mNumWins        = preferences.getInt("scores_review"  , 0);
434

    
435
    if( mDeviceID==-1 ) mDeviceID = privateGetDeviceID();
436

    
437
    if( thereWasError ) recordDBError(errorStr);
438
    }
439

    
440
///////////////////////////////////////////////////////////////////////////////////////////////////
441

    
442
  public int numberOfSolvedMAXes()
443
    {
444
    int numObjects = RubikObjectList.getNumObjects();
445
    int ret=0, level = RubikScreenPlay.LEVELS_SHOWN;
446

    
447
    for(int obj=0; obj<numObjects; obj++)
448
      {
449
      if( isSolved(obj,level) ) ret++;
450
      }
451

    
452
    return ret;
453
    }
454

    
455
///////////////////////////////////////////////////////////////////////////////////////////////////
456

    
457
  public void recordDBError(String message)
458
    {
459
    if( BuildConfig.DEBUG )
460
      {
461
      android.util.Log.e("scores", message);
462
      }
463
    else
464
      {
465
      Exception ex = new Exception(message);
466
      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
467
      crashlytics.setCustomKey("scores" , message);
468
      crashlytics.recordException(ex);
469
      }
470
    }
471
  }
(3-3/4)