Project

General

Profile

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

magiccube / src / main / java / org / distorted / external / RubikScores.java @ 6142069a

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 MAX_RECORD = 10;
34
  public static final int MULT = 1000000;
35
  public static final long NO_RECORD = Long.MAX_VALUE;
36
  private static RubikScores mThis;
37

    
38
  private String mName, mCountry;
39
  private boolean mNameIsVerified;
40
  private int mNumRuns;
41
  private int mNumPlays;
42
  private int mNumWins;
43
  private int mDeviceID;
44

    
45
  private static class MapValue
46
    {
47
    long record;
48
    boolean submitted;
49

    
50
    MapValue(long rec,int sub)
51
      {
52
      record    = rec;
53
      submitted = sub!=0;
54
      }
55
    }
56

    
57
  private final HashMap<Integer,MapValue> mMap;
58

    
59
///////////////////////////////////////////////////////////////////////////////////////////////////
60

    
61
  private RubikScores()
62
    {
63
    mMap = new HashMap<>();
64

    
65
    mName = "";
66
    mCountry = "un";
67

    
68
    mNameIsVerified = false;
69

    
70
    mNumPlays= -1;
71
    mNumRuns = -1;
72
    mDeviceID= -1;
73
    mNumWins =  0;
74
    }
75

    
76
///////////////////////////////////////////////////////////////////////////////////////////////////
77

    
78
  private int mapKey(int object,int level)
79
    {
80
    return object*MULT + level;
81
    }
82

    
83
///////////////////////////////////////////////////////////////////////////////////////////////////
84

    
85
  private int privateGetDeviceID()
86
    {
87
    int id;
88

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

    
100
    return id<0 ? -id : id;
101
    }
102

    
103
///////////////////////////////////////////////////////////////////////////////////////////////////
104

    
105
  synchronized void successfulSubmit()
106
    {
107
    mNameIsVerified = true;
108

    
109
    for(int key: mMap.keySet())
110
      {
111
      MapValue value = mMap.get(key);
112
      if( value!=null ) value.submitted = true;
113
      }
114
    }
115

    
116
///////////////////////////////////////////////////////////////////////////////////////////////////
117

    
118
  int getDeviceID()
119
    {
120
    return mDeviceID;
121
    }
122

    
123
///////////////////////////////////////////////////////////////////////////////////////////////////
124

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

    
133
    return false;
134
    }
135

    
136
///////////////////////////////////////////////////////////////////////////////////////////////////
137

    
138
  synchronized String getRecordList(String strObj, String strLvl, String strTim)
139
    {
140
    StringBuilder builderObj = new StringBuilder();
141
    StringBuilder builderLvl = new StringBuilder();
142
    StringBuilder builderTim = new StringBuilder();
143
    boolean first = true;
144

    
145
    for(int key: mMap.keySet())
146
      {
147
      MapValue value = mMap.get(key);
148

    
149
      if( value!=null && !value.submitted && value.record<NO_RECORD)
150
        {
151
        if( !first )
152
          {
153
          builderObj.append(',');
154
          builderLvl.append(',');
155
          builderTim.append(',');
156
          }
157
        first=false;
158

    
159
        RubikObject object = RubikObjectList.getObject(key/MULT);
160

    
161
        if( object!=null )
162
          {
163
          builderObj.append(object.getUpperName());
164
          builderLvl.append(key%MULT);
165
          builderTim.append(value.record);
166
          }
167
        }
168
      }
169

    
170
    return strObj+builderObj.toString()+strLvl+builderLvl.toString()+strTim+builderTim.toString();
171
    }
172

    
173
///////////////////////////////////////////////////////////////////////////////////////////////////
174
// Public API
175
///////////////////////////////////////////////////////////////////////////////////////////////////
176

    
177
  public boolean isVerified()
178
    {
179
    return mNameIsVerified;
180
    }
181

    
182
///////////////////////////////////////////////////////////////////////////////////////////////////
183

    
184
  public int getNumPlays()
185
    {
186
    return mNumPlays;
187
    }
188

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

    
191
  public int getNumRuns()
192
    {
193
    return mNumRuns;
194
    }
195

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

    
198
  public String getName()
199
    {
200
    return mName;
201
    }
202

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

    
205
  public String getCountry()
206
    {
207
    return mCountry;
208
    }
209

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

    
212
  public void incrementNumPlays()
213
    {
214
    mNumPlays++;
215
    }
216

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

    
219
  public void incrementNumRuns()
220
    {
221
    mNumRuns++;
222
    }
223

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

    
226
  public int incrementNumWins()
227
    {
228
    mNumWins++;
229
    return mNumWins;
230
    }
231

    
232
///////////////////////////////////////////////////////////////////////////////////////////////////
233

    
234
  public void setName(String newName)
235
    {
236
    mName = newName;
237
    }
238

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

    
241
  public synchronized boolean setRecord(int object, int level, long record)
242
    {
243
    int key = mapKey(object,level);
244
    MapValue oldValue = mMap.get(key);
245

    
246
    if( oldValue==null )
247
      {
248
      MapValue value = new MapValue(record,0);
249
      mMap.put(key,value);
250
      return true;
251
      }
252

    
253
    long oldRecord = oldValue.record;
254

    
255
    if( oldRecord>record)
256
      {
257
      MapValue value = new MapValue(record,0);
258
      mMap.put(key,value);
259
      return true;
260
      }
261

    
262
    return false;
263
    }
264
///////////////////////////////////////////////////////////////////////////////////////////////////
265

    
266
  public synchronized long getRecord(int object, int level)
267
    {
268
    int key = mapKey(object,level);
269
    MapValue value = mMap.get(key);
270
    return value!=null ? value.record : NO_RECORD;
271
    }
272

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

    
275
  public synchronized boolean isSolved(int object, int level)
276
    {
277
    int key = mapKey(object,level);
278
    MapValue value = mMap.get(key);
279
    return value!=null && value.record<NO_RECORD;
280
    }
281

    
282
///////////////////////////////////////////////////////////////////////////////////////////////////
283

    
284
  public void setCountry(Context context)
285
    {
286
    TelephonyManager tM =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
287

    
288
    if( tM!=null )
289
      {
290
      mCountry = tM.getSimCountryIso();
291

    
292
      if( mCountry==null || mCountry.length()<=1 )
293
        {
294
        mCountry = tM.getNetworkCountryIso();
295
        }
296
      }
297

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

    
302
    if( mCountry.equals("do") ) mCountry = "dm";
303
    }
304

    
305
///////////////////////////////////////////////////////////////////////////////////////////////////
306

    
307
  public void setCountry(String country)
308
    {
309
    mCountry = country;
310

    
311
    if( mCountry.equals("do") ) mCountry = "dm";  // see above
312
    }
313

    
314
///////////////////////////////////////////////////////////////////////////////////////////////////
315

    
316
  public static RubikScores getInstance()
317
    {
318
    if( mThis==null ) mThis = new RubikScores();
319
    return mThis;
320
    }
321

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

    
324
  public synchronized void savePreferences(SharedPreferences.Editor editor)
325
    {
326
    int numObjects = RubikObjectList.getNumObjects();
327
    StringBuilder builder = new StringBuilder();
328

    
329
    for(int level=0; level<=MAX_RECORD; level++)
330
      {
331
      builder.setLength(0);
332

    
333
      for(int object=0; object<numObjects; object++)
334
        {
335
        int key = mapKey(object,level);
336
        RubikObject obj = RubikObjectList.getObject(object);
337
        MapValue value = mMap.get(key);
338

    
339
        if( obj!=null && value!=null && value.record<NO_RECORD )
340
          {
341
          builder.append(obj.getUpperName());
342
          builder.append("=");
343
          builder.append(value.record);
344
          builder.append(",");
345
          builder.append(value.submitted ? 1:0 );
346
          builder.append(" ");
347
          }
348
        }
349

    
350
      editor.putString("scores_record"+level, builder.toString());
351
      }
352

    
353
    editor.putString("scores_name"  , mName  );
354
    editor.putBoolean("scores_isVerified", mNameIsVerified);
355
    editor.putInt("scores_numPlays", mNumPlays);
356
    editor.putInt("scores_numRuns" , mNumRuns );
357
    editor.putInt("scores_deviceid", mDeviceID);
358
    editor.putInt("scores_review"  , mNumWins );
359
    }
360

    
361
///////////////////////////////////////////////////////////////////////////////////////////////////
362

    
363
  public synchronized void restorePreferences(SharedPreferences preferences)
364
    {
365
    String recordStr, subStr, nameStr, timeStr, submStr, errorStr="";
366
    int start, end, equals, comma, ordinal, subm;
367
    long time;
368
    boolean thereWasError = false;
369
    int numObjects = RubikObjectList.getNumObjects();
370

    
371
    for(int level=0; level<=MAX_SCRAMBLES; level++)
372
      {
373
      recordStr = preferences.getString("scores_record"+level, null);
374
      if( recordStr==null ) continue;
375
      start = end = 0;
376

    
377
      while( end!=-1 )
378
        {
379
        end = recordStr.indexOf(" ", start);
380

    
381
        if( end==-1 ) subStr = recordStr.substring(start);
382
        else          subStr = recordStr.substring(start,end);
383

    
384
        start = end+1;
385

    
386
        equals = subStr.indexOf("=");
387
        comma  = subStr.indexOf(",");
388

    
389
        if( equals>=0 && comma>=0 )
390
          {
391
          nameStr = subStr.substring(0,equals);
392
          timeStr = subStr.substring(equals+1,comma);
393
          submStr = subStr.substring(comma+1);
394

    
395
          ordinal = RubikObjectList.getOrdinal(nameStr);
396

    
397
          if( ordinal>=0 && ordinal<numObjects )
398
            {
399
            time = Long.parseLong(timeStr);
400
            subm = Integer.parseInt(submStr);
401

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

    
423
    mName           = preferences.getString("scores_name"  , "" );
424
    mNameIsVerified = preferences.getBoolean("scores_isVerified", false);
425
    mNumPlays       = preferences.getInt("scores_numPlays", 0);
426
    mNumRuns        = preferences.getInt("scores_numRuns" , 0);
427
    mDeviceID       = preferences.getInt("scores_deviceid",-1);
428
    mNumWins        = preferences.getInt("scores_review"  , 0);
429

    
430
    if( mDeviceID==-1 ) mDeviceID = privateGetDeviceID();
431

    
432
    if( thereWasError ) recordDBError(errorStr);
433
    }
434

    
435
///////////////////////////////////////////////////////////////////////////////////////////////////
436

    
437
  public int numberOfSolvedMAXes()
438
    {
439
    int numObjects = RubikObjectList.getNumObjects();
440
    int ret=0, level = RubikScreenPlay.LEVELS_SHOWN;
441

    
442
    for(int obj=0; obj<numObjects; obj++)
443
      {
444
      if( isSolved(obj,level) ) ret++;
445
      }
446

    
447
    return ret;
448
    }
449

    
450
///////////////////////////////////////////////////////////////////////////////////////////////////
451

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