Project

General

Profile

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

magiccube / src / main / java / org / distorted / external / RubikScores.java @ ff377568

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

    
25
import static org.distorted.objectlib.metadata.ListObjects.MAX_SCRAMBLES;
26

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

    
30
public class RubikScores
31
  {
32
  public static final int LEVELS_SHOWN   = 8;
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
  private static final int MULT = 1000000;
39
  public static final int NO_RECORD = Integer.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
  private int mNumStars;
49

    
50
  private static class MapValue
51
    {
52
    int record;
53
    boolean submitted;
54

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

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

    
64
///////////////////////////////////////////////////////////////////////////////////////////////////
65

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

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

    
73
    mNameIsVerified = false;
74

    
75
    mNumPlays= -1;
76
    mNumRuns = -1;
77
    mDeviceID= -1;
78
    mNumWins =  0;
79
    mNumStars=  0;
80
    }
81

    
82
///////////////////////////////////////////////////////////////////////////////////////////////////
83

    
84
  private int mapKey(int object,int level)
85
    {
86
    return object*MULT + (level<0 ? MULT-1 : level);
87
    }
88

    
89
///////////////////////////////////////////////////////////////////////////////////////////////////
90

    
91
  private int privateGetDeviceID()
92
    {
93
    int id;
94

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

    
106
    return id<0 ? -id : id;
107
    }
108

    
109
///////////////////////////////////////////////////////////////////////////////////////////////////
110

    
111
  synchronized void successfulSubmit()
112
    {
113
    mNameIsVerified = true;
114

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

    
122
///////////////////////////////////////////////////////////////////////////////////////////////////
123

    
124
  int getDeviceID()
125
    {
126
    return mDeviceID;
127
    }
128

    
129
///////////////////////////////////////////////////////////////////////////////////////////////////
130

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

    
139
    return false;
140
    }
141

    
142
///////////////////////////////////////////////////////////////////////////////////////////////////
143

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

    
151
    for(int key: mMap.keySet())
152
      {
153
      MapValue value = mMap.get(key);
154
      int level = key%MULT;
155

    
156
      if( level<MULT-1 && value!=null && !value.submitted && value.record<NO_RECORD)
157
        {
158
        if( !first )
159
          {
160
          builderObj.append(',');
161
          builderLvl.append(',');
162
          builderTim.append(',');
163
          }
164
        first=false;
165

    
166
        RubikObject object = RubikObjectList.getObject(key/MULT);
167

    
168
        if( object!=null )
169
          {
170
          builderObj.append(object.getUpperName());
171
          builderLvl.append(level);
172
          builderTim.append(value.record);
173
          }
174
        }
175
      }
176

    
177
    return strObj+builderObj+strLvl+builderLvl+strTim+builderTim;
178
    }
179

    
180
///////////////////////////////////////////////////////////////////////////////////////////////////
181
// Public API
182
///////////////////////////////////////////////////////////////////////////////////////////////////
183

    
184
  public boolean isVerified()
185
    {
186
    return mNameIsVerified;
187
    }
188

    
189
///////////////////////////////////////////////////////////////////////////////////////////////////
190

    
191
  public int getNumPlays()
192
    {
193
    return mNumPlays;
194
    }
195

    
196
///////////////////////////////////////////////////////////////////////////////////////////////////
197

    
198
  public int getNumRuns()
199
    {
200
    return mNumRuns;
201
    }
202

    
203
///////////////////////////////////////////////////////////////////////////////////////////////////
204

    
205
  public String getName()
206
    {
207
    return mName;
208
    }
209

    
210
///////////////////////////////////////////////////////////////////////////////////////////////////
211

    
212
  public String getCountry()
213
    {
214
    return mCountry;
215
    }
216

    
217
///////////////////////////////////////////////////////////////////////////////////////////////////
218

    
219
  public void incrementNumPlays()
220
    {
221
    mNumPlays++;
222
    }
223

    
224
///////////////////////////////////////////////////////////////////////////////////////////////////
225

    
226
  public void incrementNumRuns()
227
    {
228
    mNumRuns++;
229
    }
230

    
231
///////////////////////////////////////////////////////////////////////////////////////////////////
232

    
233
  public int incrementNumWins()
234
    {
235
    mNumWins++;
236
    return mNumWins;
237
    }
238

    
239
///////////////////////////////////////////////////////////////////////////////////////////////////
240

    
241
  public void changeNumStars(int stars)
242
    {
243
    mNumStars += stars;
244
    if( mNumStars<0 ) mNumStars = 0;
245
    }
246

    
247
///////////////////////////////////////////////////////////////////////////////////////////////////
248

    
249
  public int getNumStars()
250
    {
251
    return mNumStars;
252
    }
253

    
254
///////////////////////////////////////////////////////////////////////////////////////////////////
255

    
256
  public void correctNumStars()
257
    {
258
    int numObjects = RubikObjectList.getNumObjects();
259

    
260
    for(int obj=0; obj<numObjects; obj++)
261
      {
262
      for(int level=0; level<=LEVELS_SHOWN; level++)
263
        {
264
        if( isSolved(obj,level) )
265
          {
266
          int numStars = computeNumStars(level+1);
267
          mNumStars += numStars;
268
          }
269
        }
270
      }
271
    }
272

    
273
///////////////////////////////////////////////////////////////////////////////////////////////////
274

    
275
  public int computeNumStars(int level)
276
    {
277
    return level>LEVELS_SHOWN ? 50 : level;
278
    }
279

    
280
///////////////////////////////////////////////////////////////////////////////////////////////////
281

    
282
  public void setName(String newName)
283
    {
284
    mName = newName;
285
    }
286

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

    
289
  public synchronized int setRecord(int object, int level, int record)
290
    {
291
    int key = mapKey(object,level);
292
    MapValue oldValue = mMap.get(key);
293

    
294
    if( oldValue==null )
295
      {
296
      MapValue value = new MapValue(record,0);
297
      mMap.put(key,value);
298
      return RECORD_FIRST;
299
      }
300

    
301
    long oldRecord = oldValue.record;
302

    
303
    if( oldRecord>record )
304
      {
305
      MapValue value = new MapValue(record,0);
306
      mMap.put(key,value);
307
      return RECORD_NEW;
308
      }
309

    
310
    return RECORD_NOT_NEW;
311
    }
312

    
313
///////////////////////////////////////////////////////////////////////////////////////////////////
314

    
315
  public synchronized int getRecord(int object, int level)
316
    {
317
    int key = mapKey(object,level);
318
    MapValue value = mMap.get(key);
319
    return value!=null ? value.record : NO_RECORD;
320
    }
321

    
322
///////////////////////////////////////////////////////////////////////////////////////////////////
323

    
324
  public synchronized boolean isSolved(int object, int level)
325
    {
326
    int key = mapKey(object,level);
327
    MapValue value = mMap.get(key);
328
    return value!=null && value.record<NO_RECORD;
329
    }
330

    
331
///////////////////////////////////////////////////////////////////////////////////////////////////
332

    
333
  public void setCountry(Context context)
334
    {
335
    TelephonyManager tM =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
336

    
337
    if( tM!=null )
338
      {
339
      mCountry = tM.getSimCountryIso();
340

    
341
      if( mCountry==null || mCountry.length()<=1 )
342
        {
343
        mCountry = tM.getNetworkCountryIso();
344
        }
345
      }
346

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

    
351
    if( mCountry.equals("do") ) mCountry = "dm";
352
    }
353

    
354
///////////////////////////////////////////////////////////////////////////////////////////////////
355

    
356
  public void setCountry(String country)
357
    {
358
    mCountry = country;
359

    
360
    if( mCountry.equals("do") ) mCountry = "dm";  // see above
361
    }
362

    
363
///////////////////////////////////////////////////////////////////////////////////////////////////
364

    
365
  public static RubikScores getInstance()
366
    {
367
    if( mThis==null ) mThis = new RubikScores();
368
    return mThis;
369
    }
370

    
371
///////////////////////////////////////////////////////////////////////////////////////////////////
372

    
373
  public synchronized void savePreferences(SharedPreferences.Editor editor)
374
    {
375
    int numObjects = RubikObjectList.getNumObjects();
376
    StringBuilder builder = new StringBuilder();
377

    
378
    for(int level=0; level<=MAX_RECORD; level++)
379
      {
380
      builder.setLength(0);
381

    
382
      for(int object=0; object<numObjects; object++)
383
        {
384
        int key = mapKey(object,level);
385
        RubikObject obj = RubikObjectList.getObject(object);
386
        MapValue value = mMap.get(key);
387

    
388
        if( obj!=null && value!=null && value.record<NO_RECORD )
389
          {
390
          builder.append(obj.getUpperName());
391
          builder.append("=");
392
          builder.append(value.record);
393
          builder.append(",");
394
          builder.append(value.submitted ? 1:0 );
395
          builder.append(" ");
396
          }
397
        }
398

    
399
      editor.putString("scores_record"+level, builder.toString());
400
      }
401

    
402
    editor.putString("scores_name"  , mName  );
403
    editor.putBoolean("scores_isVerified", mNameIsVerified);
404
    editor.putInt("scores_numPlays", mNumPlays);
405
    editor.putInt("scores_numRuns" , mNumRuns );
406
    editor.putInt("scores_deviceid", mDeviceID);
407
    editor.putInt("scores_review"  , mNumWins );   // legacy name
408
    editor.putInt("scores_numStars", mNumStars );
409
    }
410

    
411
///////////////////////////////////////////////////////////////////////////////////////////////////
412

    
413
  public synchronized void savePreferencesMinimal(SharedPreferences.Editor editor)
414
    {
415
    editor.putInt("scores_numStars", mNumStars );
416
    }
417

    
418
///////////////////////////////////////////////////////////////////////////////////////////////////
419

    
420
  public synchronized void restorePreferences(SharedPreferences preferences)
421
    {
422
    String recordStr, subStr, nameStr, timeStr, submStr, errorStr="";
423
    int start, end, equals, comma, ordinal, subm, time;
424
    boolean thereWasError = false;
425
    int numObjects = RubikObjectList.getNumObjects();
426

    
427
    for(int level=0; level<=MAX_SCRAMBLES; level++)
428
      {
429
      recordStr = preferences.getString("scores_record"+level, null);
430
      if( recordStr==null ) continue;
431
      start = end = 0;
432

    
433
      while( end!=-1 )
434
        {
435
        end = recordStr.indexOf(" ", start);
436

    
437
        if( end==-1 ) subStr = recordStr.substring(start);
438
        else          subStr = recordStr.substring(start,end);
439

    
440
        start = end+1;
441

    
442
        equals = subStr.indexOf("=");
443
        comma  = subStr.indexOf(",");
444

    
445
        if( equals>=0 && comma>=0 )
446
          {
447
          nameStr = subStr.substring(0,equals);
448
          timeStr = subStr.substring(equals+1,comma);
449
          submStr = subStr.substring(comma+1);
450

    
451
          ordinal = RubikObjectList.getOrdinal(nameStr);
452

    
453
          if( ordinal>=0 && ordinal<numObjects )
454
            {
455
            try
456
              {
457
              time = Integer.parseInt(timeStr);
458
              subm = Integer.parseInt(submStr);
459
              }
460
            catch(NumberFormatException ex)
461
              {
462
              subm = 1;
463
              time = 0;
464
              errorStr += ("error1: timeStr="+timeStr+" submStr: "+submStr+"\n");
465
              thereWasError= true;
466
              }
467

    
468
            if( subm>=0 && subm<=1 )
469
              {
470
              MapValue value = new MapValue(time,subm);
471
              int key = mapKey(ordinal,level);
472
              mMap.put(key,value);
473
              }
474
            else
475
              {
476
              errorStr += ("error1: subm="+subm+" obj: "+nameStr+"\n");
477
              thereWasError= true;
478
              }
479
            }
480
          else
481
            {
482
            errorStr += ("error2: object="+ordinal+" obj: "+nameStr+"\n");
483
            thereWasError = true;
484
            }
485
          }
486
        }
487
      }
488

    
489
    mName           = preferences.getString("scores_name"  , "" );
490
    mNameIsVerified = preferences.getBoolean("scores_isVerified", false);
491
    mNumPlays       = preferences.getInt("scores_numPlays", 0);
492
    mNumRuns        = preferences.getInt("scores_numRuns" , 0);
493
    mDeviceID       = preferences.getInt("scores_deviceid",-1);
494
    mNumWins        = preferences.getInt("scores_review"  , 0);
495
    mNumStars       = preferences.getInt("scores_numStars", 0);
496

    
497
    if( mDeviceID==-1 ) mDeviceID = privateGetDeviceID();
498

    
499
    if( thereWasError ) recordDBError(errorStr);
500
    }
501

    
502
///////////////////////////////////////////////////////////////////////////////////////////////////
503

    
504
  public int numberOfSolvedMAXes()
505
    {
506
    int numObjects = RubikObjectList.getNumObjects();
507
    int ret=0;
508

    
509
    for(int obj=0; obj<numObjects; obj++)
510
      {
511
      if( isSolved(obj,LEVELS_SHOWN) ) ret++;
512
      }
513

    
514
    return ret;
515
    }
516

    
517
///////////////////////////////////////////////////////////////////////////////////////////////////
518

    
519
  public void recordDBError(String message)
520
    {
521
    if( BuildConfig.DEBUG )
522
      {
523
      android.util.Log.e("scores", message);
524
      }
525
    else
526
      {
527
      Exception ex = new Exception(message);
528
      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
529
      crashlytics.setCustomKey("scores" , message);
530
      crashlytics.recordException(ex);
531
      }
532
    }
533
  }
(3-3/4)