Project

General

Profile

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

magiccube / src / main / java / org / distorted / external / RubikScores.java @ 1ba56d95

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.main.ObjectType.MAX_SCRAMBLES;
26

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

    
30
public class RubikScores
31
  {
32
  public static final int MAX_RECORD = 10;
33
  public static final int MULT = 1000000;
34
  public static final long NO_RECORD = Long.MAX_VALUE;
35
  private static RubikScores mThis;
36

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

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

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

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

    
58
///////////////////////////////////////////////////////////////////////////////////////////////////
59

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

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

    
67
    mNameIsVerified = false;
68

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

    
75
///////////////////////////////////////////////////////////////////////////////////////////////////
76

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

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

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

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

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

    
102
///////////////////////////////////////////////////////////////////////////////////////////////////
103

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

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

    
115
///////////////////////////////////////////////////////////////////////////////////////////////////
116

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

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

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

    
132
    return false;
133
    }
134

    
135
///////////////////////////////////////////////////////////////////////////////////////////////////
136

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

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

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

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

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

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

    
172
///////////////////////////////////////////////////////////////////////////////////////////////////
173
// Public API
174
///////////////////////////////////////////////////////////////////////////////////////////////////
175

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

    
181
///////////////////////////////////////////////////////////////////////////////////////////////////
182

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
252
    long oldRecord = oldValue.record;
253

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

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

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

    
272
///////////////////////////////////////////////////////////////////////////////////////////////////
273

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

    
281
///////////////////////////////////////////////////////////////////////////////////////////////////
282

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

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

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

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

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

    
304
///////////////////////////////////////////////////////////////////////////////////////////////////
305

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

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

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

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

    
321
///////////////////////////////////////////////////////////////////////////////////////////////////
322

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

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

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

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

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

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

    
360
///////////////////////////////////////////////////////////////////////////////////////////////////
361

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

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

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

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

    
383
        start = end+1;
384

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

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

    
394
          ordinal = RubikObjectList.getOrdinal(nameStr);
395

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

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

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

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

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

    
434
///////////////////////////////////////////////////////////////////////////////////////////////////
435

    
436
  public void recordDBError(String message)
437
    {
438
    if( BuildConfig.DEBUG )
439
      {
440
      android.util.Log.e("scores", message);
441
      }
442
    else
443
      {
444
      Exception ex = new Exception(message);
445
      FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
446
      crashlytics.setCustomKey("scores" , message);
447
      crashlytics.recordException(ex);
448
      }
449
    }
450
  }
(3-3/4)