Project

General

Profile

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

magiccube / src / main / java / org / distorted / external / RubikScores.java @ 0c233a9a

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
  private int mNumStars;
49

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

    
55
    MapValue(long 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;
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

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

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

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

    
176
    return strObj+builderObj.toString()+strLvl+builderLvl.toString()+strTim+builderTim.toString();
177
    }
178

    
179
///////////////////////////////////////////////////////////////////////////////////////////////////
180
// Public API
181
///////////////////////////////////////////////////////////////////////////////////////////////////
182

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

    
188
///////////////////////////////////////////////////////////////////////////////////////////////////
189

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

    
195
///////////////////////////////////////////////////////////////////////////////////////////////////
196

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

    
202
///////////////////////////////////////////////////////////////////////////////////////////////////
203

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

    
209
///////////////////////////////////////////////////////////////////////////////////////////////////
210

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

    
216
///////////////////////////////////////////////////////////////////////////////////////////////////
217

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

    
223
///////////////////////////////////////////////////////////////////////////////////////////////////
224

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

    
230
///////////////////////////////////////////////////////////////////////////////////////////////////
231

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

    
238
///////////////////////////////////////////////////////////////////////////////////////////////////
239

    
240
  public void changeNumStars(int stars)
241
    {
242
    mNumStars += stars;
243
    }
244

    
245
///////////////////////////////////////////////////////////////////////////////////////////////////
246

    
247
  public int getNumStars()
248
    {
249
    return mNumStars;
250
    }
251

    
252
///////////////////////////////////////////////////////////////////////////////////////////////////
253

    
254
  public void setName(String newName)
255
    {
256
    mName = newName;
257
    }
258

    
259
///////////////////////////////////////////////////////////////////////////////////////////////////
260

    
261
  public synchronized int setRecord(int object, int level, long record)
262
    {
263
    int key = mapKey(object,level);
264
    MapValue oldValue = mMap.get(key);
265

    
266
    if( oldValue==null )
267
      {
268
      MapValue value = new MapValue(record,0);
269
      mMap.put(key,value);
270
      return RECORD_FIRST;
271
      }
272

    
273
    long oldRecord = oldValue.record;
274

    
275
    if( oldRecord>record)
276
      {
277
      MapValue value = new MapValue(record,0);
278
      mMap.put(key,value);
279
      return RECORD_NEW;
280
      }
281

    
282
    return RECORD_NOT_NEW;
283
    }
284

    
285
///////////////////////////////////////////////////////////////////////////////////////////////////
286

    
287
  public synchronized long getRecord(int object, int level)
288
    {
289
    int key = mapKey(object,level);
290
    MapValue value = mMap.get(key);
291
    return value!=null ? value.record : NO_RECORD;
292
    }
293

    
294
///////////////////////////////////////////////////////////////////////////////////////////////////
295

    
296
  public synchronized boolean isSolved(int object, int level)
297
    {
298
    int key = mapKey(object,level);
299
    MapValue value = mMap.get(key);
300
    return value!=null && value.record<NO_RECORD;
301
    }
302

    
303
///////////////////////////////////////////////////////////////////////////////////////////////////
304

    
305
  public void setCountry(Context context)
306
    {
307
    TelephonyManager tM =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
308

    
309
    if( tM!=null )
310
      {
311
      mCountry = tM.getSimCountryIso();
312

    
313
      if( mCountry==null || mCountry.length()<=1 )
314
        {
315
        mCountry = tM.getNetworkCountryIso();
316
        }
317
      }
318

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

    
323
    if( mCountry.equals("do") ) mCountry = "dm";
324
    }
325

    
326
///////////////////////////////////////////////////////////////////////////////////////////////////
327

    
328
  public void setCountry(String country)
329
    {
330
    mCountry = country;
331

    
332
    if( mCountry.equals("do") ) mCountry = "dm";  // see above
333
    }
334

    
335
///////////////////////////////////////////////////////////////////////////////////////////////////
336

    
337
  public static RubikScores getInstance()
338
    {
339
    if( mThis==null ) mThis = new RubikScores();
340
    return mThis;
341
    }
342

    
343
///////////////////////////////////////////////////////////////////////////////////////////////////
344

    
345
  public synchronized void savePreferences(SharedPreferences.Editor editor)
346
    {
347
    int numObjects = RubikObjectList.getNumObjects();
348
    StringBuilder builder = new StringBuilder();
349

    
350
    for(int level=0; level<=MAX_RECORD; level++)
351
      {
352
      builder.setLength(0);
353

    
354
      for(int object=0; object<numObjects; object++)
355
        {
356
        int key = mapKey(object,level);
357
        RubikObject obj = RubikObjectList.getObject(object);
358
        MapValue value = mMap.get(key);
359

    
360
        if( obj!=null && value!=null && value.record<NO_RECORD )
361
          {
362
          builder.append(obj.getUpperName());
363
          builder.append("=");
364
          builder.append(value.record);
365
          builder.append(",");
366
          builder.append(value.submitted ? 1:0 );
367
          builder.append(" ");
368
          }
369
        }
370

    
371
      editor.putString("scores_record"+level, builder.toString());
372
      }
373

    
374
    editor.putString("scores_name"  , mName  );
375
    editor.putBoolean("scores_isVerified", mNameIsVerified);
376
    editor.putInt("scores_numPlays", mNumPlays);
377
    editor.putInt("scores_numRuns" , mNumRuns );
378
    editor.putInt("scores_deviceid", mDeviceID);
379
    editor.putInt("scores_review"  , mNumWins );
380
    editor.putInt("scores_review"  , mNumWins );   // legacy name
381
    editor.putInt("scores_numStars", mNumStars );
382
    }
383

    
384
///////////////////////////////////////////////////////////////////////////////////////////////////
385

    
386
  public synchronized void restorePreferences(SharedPreferences preferences)
387
    {
388
    String recordStr, subStr, nameStr, timeStr, submStr, errorStr="";
389
    int start, end, equals, comma, ordinal, subm;
390
    long time;
391
    boolean thereWasError = false;
392
    int numObjects = RubikObjectList.getNumObjects();
393

    
394
    for(int level=0; level<=MAX_SCRAMBLES; level++)
395
      {
396
      recordStr = preferences.getString("scores_record"+level, null);
397
      if( recordStr==null ) continue;
398
      start = end = 0;
399

    
400
      while( end!=-1 )
401
        {
402
        end = recordStr.indexOf(" ", start);
403

    
404
        if( end==-1 ) subStr = recordStr.substring(start);
405
        else          subStr = recordStr.substring(start,end);
406

    
407
        start = end+1;
408

    
409
        equals = subStr.indexOf("=");
410
        comma  = subStr.indexOf(",");
411

    
412
        if( equals>=0 && comma>=0 )
413
          {
414
          nameStr = subStr.substring(0,equals);
415
          timeStr = subStr.substring(equals+1,comma);
416
          submStr = subStr.substring(comma+1);
417

    
418
          ordinal = RubikObjectList.getOrdinal(nameStr);
419

    
420
          if( ordinal>=0 && ordinal<numObjects )
421
            {
422
            time = Long.parseLong(timeStr);
423
            subm = Integer.parseInt(submStr);
424

    
425
            if( subm>=0 && subm<=1 )
426
              {
427
              MapValue value = new MapValue(time,subm);
428
              int key = mapKey(ordinal,level);
429
              mMap.put(key,value);
430
              }
431
            else
432
              {
433
              errorStr += ("error1: subm="+subm+" obj: "+nameStr+"\n");
434
              thereWasError= true;
435
              }
436
            }
437
          else
438
            {
439
            errorStr += ("error2: object="+ordinal+" obj: "+nameStr+"\n");
440
            thereWasError = true;
441
            }
442
          }
443
        }
444
      }
445

    
446
    mName           = preferences.getString("scores_name"  , "" );
447
    mNameIsVerified = preferences.getBoolean("scores_isVerified", false);
448
    mNumPlays       = preferences.getInt("scores_numPlays", 0);
449
    mNumRuns        = preferences.getInt("scores_numRuns" , 0);
450
    mDeviceID       = preferences.getInt("scores_deviceid",-1);
451
    mNumWins        = preferences.getInt("scores_review"  , 0);
452
    mNumStars       = preferences.getInt("scores_numStars", 0);
453

    
454
    if( mDeviceID==-1 ) mDeviceID = privateGetDeviceID();
455

    
456
    if( thereWasError ) recordDBError(errorStr);
457
    }
458

    
459
///////////////////////////////////////////////////////////////////////////////////////////////////
460

    
461
  public int numberOfSolvedMAXes()
462
    {
463
    int numObjects = RubikObjectList.getNumObjects();
464
    int ret=0, level = RubikScreenPlay.LEVELS_SHOWN;
465

    
466
    for(int obj=0; obj<numObjects; obj++)
467
      {
468
      if( isSolved(obj,level) ) ret++;
469
      }
470

    
471
    return ret;
472
    }
473

    
474
///////////////////////////////////////////////////////////////////////////////////////////////////
475

    
476
  public void recordDBError(String message)
477
    {
478
    if( BuildConfig.DEBUG )
479
      {
480
      android.util.Log.e("scores", message);
481
      }
482
    else
483
      {
484
      Exception ex = new Exception(message);
485
      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
486
      crashlytics.setCustomKey("scores" , message);
487
      crashlytics.recordException(ex);
488
      }
489
    }
490
  }
(3-3/4)