ഒരു ബ്രൗസർ ടാബ് അടയ്ക്കുന്നത് നാല് മണിക്കൂർ നീണ്ട പുരോഗതിയെ ഇല്ലാതാക്കരുത്. ഇത് വളരെ വ്യക്തമാണെന്ന് തോന്നാമെങ്കിലും, പല ബ്രൗസർ ഗെയിമുകളും ലോക്കൽ സ്റ്റോറേജിനെ (local storage) ഒരു രണ്ടാംകിട കാര്യമായിട്ടാണ് കാണുന്നത്. ഒരു കളിക്കാരൻ ഉയർന്ന സ്കോർ നേടുന്നു, സെറ്റിംഗുകൾ മാറ്റുന്നു, പിറ്റേന്ന് തിരികെ വരുമ്പോൾ ഒന്നും കാണാനില്ല. ഇതിലും മോശമായത്, ഒരു പാച്ച് (patch) വന്നതിന് ശേഷം അവർ തിരികെ വരുമ്പോൾ, അവരുടെ മെഷീനിലുള്ള സേവ് ഫയൽ നിങ്ങൾ പുതുതായി പുറത്തിറക്കിയ കോഡുമായി പൊരുത്തപ്പെടാത്തതിനാൽ ഗെയിം ഒരു എറർ കാണിക്കുന്നു എന്നതാണ്. Phaser 4 ഉപയോഗിച്ച് ഒരു സർവൈവർ-സ്റ്റൈൽ ഷൂട്ടർ നിർമ്മിക്കുക എന്നതിനർത്ഥം നിരന്തരമായ ശത്രുക്കളുടെ ആക്രമണങ്ങളെ നേരിടുക എന്നതാണ്, എന്നാൽ യഥാർത്ഥ ദീർഘകാല ഭീഷണി നിങ്ങളുടെ തന്നെ ഭാവി അപ്‌ഡേറ്റുകളാണ്.

മിക്ക ഡെവലപ്പർമാരും അവരുടെ ആദ്യത്തെ സേവ് സിസ്റ്റം നിർമ്മിക്കുന്നത് ഒരു ഒബ്‌ജക്റ്റ് എടുത്ത്, അത് JSON.stringify വഴി കടത്തിവിട്ട്, localStorage-ലേക്ക് ഇടുന്ന രീതിയിലാണ്. ലോഡ് ചെയ്യുമ്പോൾ, അവർ അത് പാഴ്സ് (parse) ചെയ്യുകയും ഗെയിമിന് നേരിട്ട് നൽകുകയും ചെയ്യുന്നു. ഇത് ആദ്യ ദിവസം പ്രവർത്തിക്കും. എന്നാൽ ഒരു പുതിയ സെറ്റിംഗ്, പുതിയ അൺലോക്ക് ഫ്ലാഗ്, അല്ലെങ്കിൽ മൂന്നാം പാളിയായ ഒരു നെസ്റ്റഡ് കോൺഫിഗറേഷൻ (nested configuration) എന്നിവ നിങ്ങൾ ചേർക്കുന്ന നിമിഷം ഇത് തകരാറിലാകും. തിരികെ വരുന്ന ഒരു കളിക്കാരന്റെ പക്കൽ vignette എന്ന പ്രോപ്പർട്ടി ഇല്ലാത്ത പഴയ ഒരു സേവ് ഫയൽ ഉണ്ടാവുകയും, നിങ്ങളുടെ പുതിയ കോഡ് അത് ഉണ്ടെന്ന് പ്രതീക്ഷിക്കുകയും ചെയ്താൽ, ഒരു ബൂലിയൻ (boolean) പ്രതീക്ഷിക്കുന്നിടത്ത് നിങ്ങൾക്ക് undefined ലഭിക്കും. ഡസൻ കണക്കിന് പുതിയ ഫീച്ചറുകളിൽ ഇത് സംഭവിക്കുന്നു എന്ന് കരുതുക, അപ്പോൾ നിങ്ങളുടെ ഏറ്റവും വിശ്വസ്തരായ കളിക്കാരെ ആദ്യം ബാധിക്കുന്ന ഒരു ഡിബഗ്ഗിംഗ് ദുസ്വപ്നമായി അത് മാറും.

ഒരു റോ (raw) ഒബ്‌ജക്റ്റിന് പകരം ഒരു കരാറിൽ (Contract) നിന്ന് തുടങ്ങുക

നിങ്ങൾ localStorage ഉപയോഗിക്കുന്നതിന് മുമ്പ്, നിങ്ങളുടെ കോഡ്ബേസിൽ ഒരു ഡിഫോൾട്ട് സേവ് സ്കീമ (default save schema) നിർവചിക്കുക. അത് അഞ്ച് മിനിറ്റ് മുമ്പ് നിർമ്മിച്ചതായാലും അഞ്ച് മാസം മുമ്പ് നിർമ്മിച്ചതായാലും, എല്ലാ സേവ് ഫയലുകളും പാലിക്കേണ്ട ഒരു കരാറായി ഇതിനെ കരുതുക. ഒരു വ്യക്തമായ തുടക്കം ഇപ്രകാരമായിരിക്കാം:

const defaultSave = {
  highScore: 0,
  settings: {
    screenShake: true,
    vignette: true
  }
};

ഈ ഒബ്‌ജക്റ്റ് നിങ്ങളുടെ സോഴ്സ് കോഡിൽ നിലനിൽക്കുന്നു. ഗെയിം ബൂട്ട് ചെയ്യുമ്പോൾ, ഈ ഘടന എപ്പോഴും ലഭ്യമായിരിക്കും. ഇത് നിങ്ങൾക്ക് ഒരു അടിസ്ഥാനം (baseline) നൽകുന്നു. എന്തെങ്കിലും സീരിയലൈസ് (serialize) ചെയ്യുന്നതിന് മുമ്പ് ഘടനയെക്കുറിച്ച് ചിന്തിക്കാൻ ഇത് നിങ്ങളെ പ്രേരിപ്പിക്കുന്നു. നിങ്ങൾ ഈ ഘട്ടം ഒഴിവാക്കി ആ സമയത്ത് സൗകര്യപ്രദമായ സ്റ്റേറ്റ് ഒബ്‌ജക്റ്റ് മാത്രം സ്റ്റോർ ചെയ്യുകയാണെങ്കിൽ, കീകൾ തമ്മിലുള്ള പൊരുത്തക്കേട്, വിട്ടുപോയ ഫീൽഡുകൾ, പഴയ സേവുകൾ നിങ്ങളുടെ പ്രതീക്ഷകൾക്ക് വിരുദ്ധമാകുമ്പോൾ ഉണ്ടാകുന്ന നിശബ്ദമായ പരാജയങ്ങൾ എന്നിവ നേരിടേണ്ടി വരും.

Try/Catch ഉപയോഗിച്ച് സുരക്ഷിതമായ ലോഡിംഗ് (Defensive Loading)

ലോക്കൽ സ്റ്റോറേജ് ഒരു ഡാറ്റാബേസ് അല്ല. അത് ബ്രൗസറിലെ ഒരു സ്ട്രിംഗ് ക്ലോസറ്റ് (string closet) ആണ്, അവിടെ എന്തിനും സാധ്യതയുണ്ട്. ഉപയോക്താവ് ഒരു വാല്യൂ മാനുവലായി എഡിറ്റ് ചെയ്തതാകാം, ഒരു റൈറ്റ് ഓപ്പറേഷൻ പകുതിക്ക് വെച്ച് തടസ്സപ്പെട്ടതാകാം, അല്ലെങ്കിൽ ഒരു ബ്രൗസർ എക്സ്റ്റൻഷൻ നിങ്ങൾ ഉപയോഗിക്കുന്ന കീയിലേക്ക് അനാവശ്യ ഡാറ്റ (garbage) ഇട്ടതാകാം. നിങ്ങൾ ആ സ്ട്രിംഗ് പുറത്തെടുത്ത് JSON.parse-ലേക്ക് നൽകുമ്പോൾ, ഒരു ചെറിയ തെറ്റായ ക്യാരക്ടർ പോലും ഒരു ഹാർഡ് എക്സെപ്ഷൻ (hard exception) ഉണ്ടാക്കും. ഒരു Phaser ഗെയിമിൽ, കൈകാര്യം ചെയ്യാത്ത ആ എറർ നിങ്ങളുടെ ബൂട്ട് സീക്വൻസ് ഫ്രീസ് ചെയ്യുകയോ കളിക്കാരനെ ഒരു ശൂന്യമായ സ്ക്രീനിലേക്ക് എത്തിക്കുകയോ ചെയ്തേക്കാം.

നിങ്ങളുടെ റീഡ് (read), പാഴ്സ് (parse) ലോജിക് എപ്പോഴും ഒരു try/catch ബ്ലോക്കിനുള്ളിൽ ഉൾപ്പെടുത്തുക. പരാജയപ്പെട്ടാൽ, നിങ്ങളുടെ ഡിഫോൾട്ട് സ്കീമയിലേക്ക് മടങ്ങുക. ലക്ഷ്യം ലളിതമാണ്: സേവ് ഫയൽ വായിക്കാൻ കഴിയില്ലെങ്കിൽ, മുഴുവൻ സെഷനും ക്രാഷ് ചെയ്യുന്നതിന് പകരം കളിക്കാരനെ ഒരു പുതിയ ഉപയോക്താവായി പരിഗണിക്കുക. ഈ ഒരു ശീലം ഹോബി പ്രോജക്റ്റുകളെ പ്രൊഡക്ഷൻ-ഗ്രേഡ് ബിൽഡുകളിൽ നിന്ന് വേർതിരിക്കുന്നു. ഇത് നടപ്പിലാക്കാൻ വലിയ പ്രയത്നമൊന്നും ആവശ്യമില്ല, കൂടാതെ വീണ്ടും ഉണ്ടാക്കാൻ പ്രയാസമുള്ള നിഗൂഢമായ ബഗ് റിപ്പോർട്ടുകളിൽ നിന്ന് ഇത് നിങ്ങളെ രക്ഷിക്കുന്നു.

പഴയ ഡാറ്റ ഡിഫോൾട്ടുകളുമായി യോജിപ്പിക്കുക (Merge Old Data with Defaults)

പാഴ്സ് വിജയിച്ചു എന്നത് നിങ്ങൾ സുരക്ഷിതനാണെന്ന് അർത്ഥമാക്കുന്നില്ല. പാഴ്സ് ചെയ്ത ഫലം ഉപയോഗിച്ച് നിങ്ങളുടെ ഡിഫോൾട്ട് ഒബ്‌ജക്റ്റിനെ പൂർണ്ണമായും മാറ്റരുത്. ആ പഴയ സേവ് ഫയലിൽ നിങ്ങളുടെ ഏറ്റവും പുതിയ സെറ്റിംഗുകൾ ഉണ്ടാകണമെന്നില്ല. അതിൽ screenShake ഉണ്ടാകാം, പക്ഷേ vignette ഉണ്ടാകില്ല. ഏറ്റവും പുതിയ അപ്‌ഡേറ്റിനൊപ്പം vignette വന്നതുകൊണ്ട് അത് ഉണ്ടെന്ന് നിങ്ങളുടെ ഗെയിം ലോജിക് കരുതിയാൽ, നിങ്ങൾ വീണ്ടും undefined എററുകൾ തേടി നടക്കേണ്ടി വരും.

പകരം, ലോഡ് ചെയ്ത ഡാറ്റ നിങ്ങളുടെ ഡിഫോൾട്ടുകളുമായി യോജിപ്പിക്കുക. സേവ് ചെയ്ത വാല്യൂകൾ ബേസ്‌ലൈൻ സ്കീമയ്ക്ക് മുകളിൽ ചേർക്കാൻ Object.assign ഉപയോഗിക്കുക. ഡിഫോൾട്ടുകൾ എല്ലാ വിടവുകളും സ്വയമേവ നികത്തും. വെർഷൻ രണ്ടിൽ നിങ്ങൾ ചേർത്ത പുതിയ പ്രോപ്പർട്ടികൾക്ക് ഡിഫോൾട്ട് ഒബ്‌ജക്റ്റിൽ നിന്ന് അവയുടെ പ്രാരംഭ മൂല്യങ്ങൾ ലഭിക്കും. കളിക്കാരൻ മാറ്റം വരുത്തിയ നിലവിലുള്ള പ്രോപ്പർട്ടികൾ അവരുടെ മുൻഗണനകൾക്കനുസരിച്ച് മാറും. എല്ലാവർക്കും ഇത് ഗുണകരമാണ്. തിരികെ വരുന്ന കളിക്കാരന് അവരുടെ ഹൈ സ്കോർ നിലനിർത്താം, കൂടാതെ ഗെയിം തകരാറിലാകാതെ തന്നെ നിങ്ങൾ ഇന്നലെ ചേർത്ത പുതിയ ടോഗിളുകൾ ഉപയോഗിക്കാനും സാധിക്കും.

Object.assign ഒരു ഷാലോ മെർജ് (shallow merge) മാത്രമാണ് ചെയ്യുന്നത് എന്നത് ഓർക്കുക. കാലക്രമേണ നിങ്ങളുടെ സെറ്റിംഗ്സ് ഒബ്‌ജക്റ്റ് കൂടുതൽ നെസ്റ്റഡ് (nested) ആകുകയാണെങ്കിൽ, ആ ഇനർ ഒബ്‌ജക്റ്റുകളെ കുറച്ചുകൂടി ശ്രദ്ധയോടെ കൈകാര്യം ചെയ്യേണ്ടി വന്നേക്കാം. എങ്കിലും തത്വം മാറുന്നില്ല: കളിക്കാരന്റെ ഡാറ്റ നിങ്ങളുടെ ഡിഫോൾട്ടുകളെ അലങ്കരിക്കണം, അവയെ പൂർണ്ണമായും മാറ്റിസ്ഥാപിക്കരുത്.

കീകൾക്ക് വേർഷൻ നൽകുക (Version Your Keys)

ബ്രൗസറുകൾ പഴയ ലോക്കൽ സ്റ്റോറേജ് എൻട്രികൾ സ്വയമേവ നീക്കം ചെയ്യില്ല. നിങ്ങളുടെ ഡാറ്റാ സ്ട്രക്ചറിൽ വലിയ മാറ്റങ്ങൾ വരുത്തുകയാണെങ്കിൽ, പഴയ ഫോർമാറ്റ് ഉപേക്ഷിക്കാൻ നിങ്ങൾക്ക് വ്യക്തമായ ഒരു മാർഗ്ഗം ആവശ്യമാണ്. നിങ്ങളുടെ സ്റ്റോറേജ് കീ ഒരു വേർഷൻ സഫിക്സ് (version suffix) ഉപയോഗിച്ച് പേര് ചെയ്യുക. bitSurvivorsSave_v1 എന്നത് വളരെ വ്യക്തമാണ്. ഏത് സ്കീമയാണ് ആ ഫയൽ എഴുതിയതെന്ന് അത് കൃത്യമായി പറഞ്ഞുതരുന്നു. പിന്നീട്, നിങ്ങൾ പ്രോഗ്രഷൻ മാറ്റുകയോ അല്ലെങ്കിൽ ഒരു ഫുൾ ഇൻവെന്ററി സിസ്റ്റം ചേർക്കുകയോ ചെയ്യുമ്പോൾ, bitSurvivorsSave_v2-ലേക്ക് മാറാം.

ഇത് നിങ്ങൾക്ക് രണ്ട് പ്രായോഗിക നേട്ടങ്ങൾ നൽകുന്നു. ഒന്നാമതായി, നിങ്ങൾ ഒരിക്കലും അബദ്ധവശാൽ ഒരു v2 ലോജിക് ഉപയോഗിച്ച് ഒരു v1 ബ്ലോബ് പാഴ്സ് ചെയ്യില്ല. രണ്ടാമതായി, നിങ്ങൾക്ക് വേണമെങ്കിൽ മൈഗ്രേഷൻ കോഡ് എഴുതാവുന്നതാണ്. ബൂട്ട് ചെയ്യുമ്പോൾ, v1 ഉണ്ടോ എന്ന് പരിശോധിക്കുക. അത് ഉണ്ടാവുകയും v2 ഇല്ലാതിരിക്കുകയും ചെയ്യുന്നുവെങ്കിൽ, പഴയ ഡാറ്റ പുതിയ ഘടനയിലേക്ക് മൈഗ്രേറ്റ് ചെയ്യുക, അത് പുതിയ കീയിലേക്ക് എഴുതുക, തുടർന്ന് മുന്നോട്ട് പോകുക. നിങ്ങൾക്ക് മൈഗ്രേറ്റ് ചെയ്യാൻ താൽപ്പര്യമില്ലെങ്കിൽ, നിങ്ങളുടെ പുതിയ കോഡ് അത് അവഗണിക്കുമ്പോൾ പഴയ കീ സ്റ്റോറേജിൽ സുരക്ഷിതമായി ഇരിക്കും. ഏതായാലും, വേർഷനിംഗ് നിശബ്ദമായ ഡാറ്റാ തകരാറുകൾ (silent corruption) തടയുന്നു.

സേവിംഗ് അദൃശ്യമാക്കുക

പെർസിസ്റ്റൻസ് (Persistence) ശ്വാസമെടുക്കുന്നത് പോലെ സ്വാഭാവികമായിരിക്കണം. കളിക്കാരൻ അതിനെക്കുറിച്ച് ചിന്തിക്കേണ്ടി വരരുത്. നിങ്ങളുടെ സെറ്റിംഗ്സ് മെനുവിൽ ഒരു Apply ബട്ടൺ ചേർക്കരുത്. Apply ബട്ടണുകൾ പ്രക്രിയയിൽ തടസ്സങ്ങൾ സൃഷ്ടിക്കുകയും, തങ്ങൾ തിരഞ്ഞെടുത്ത കാര്യങ്ങൾ ശരിക്കും സേവ് ആയോ എന്ന് ആശങ്കപ്പെടാൻ ഉപയോക്താക്കളെ പ്രേരിപ്പിക്കുകയും ചെയ്യുന്നു. കൂടാതെ, ഒരു കളിക്കാരൻ മൂന്ന് ഓപ്ഷനുകൾ മാറ്റുകയും, Apply അമർത്താൻ മറന്നുപോവുകയും, ടാബ് അടയ്ക്കുകയും ചെയ്യുമ്പോൾ ഇത് ഡാറ്റാ നഷ്ടത്തിന് കാരണമാകുന്നു.

ഒരു ഇന്ററാക്ഷൻ നടക്കുമ്പോൾ തന്നെ സേവ് ചെയ്യുക. സ്ക്രീൻ ഷേക്ക് (screen shake) ഒഴിവാക്കാൻ കളിക്കാരൻ ഒരു ചെക്ക്ബോക്സ് ക്ലിക്ക് ചെയ്യുമ്പോൾ, ഉടൻ തന്നെ നിങ്ങളുടെ റൈറ്റ് ഫംഗ്ഷൻ (write function) വിളിക്കുക. ഒരു റൺ അവസാനിക്കുകയും ഫൈനൽ സ്കോർ കണക്കാക്കുകയും ചെയ്യുമ്പോൾ, ഗെയിം ഓവർ സ്ക്രീൻ അനിമേഷൻ പൂർത്തിയാകുന്നതിന് മുമ്പ് പുതിയ ഹൈ സ്കോർ സേവ് ചെയ്യുക. ഇവന്റ്-ഡ്രൈവൻ സേവിംഗ് (Event-driven saving) നിങ്ങളുടെ ആർക്കിടെക്ചറിനെ പ്രവചിക്കാവുന്നത് ആക്കുന്നു, കാരണം ഡാറ്റ മാറ്റുന്ന ആക്ഷന് തൊട്ടടുത്തായി തന്നെ സേവിംഗും നടക്കുന്നു. നിങ്ങൾക്ക് ഒരു സെൻട്രൽ ബാച്ചിംഗ് ഫംഗ്ഷൻ (central batching function) തിരയാനോ അല്ലെങ്കിൽ പഴയ സ്റ്റേറ്റിനെക്കുറിച്ച് (stale state) ആശങ്കപ്പെടാനോേണ്ടി വരില്ല.

ഈ സമീപനം നിങ്ങളുടെ മെന്റൽ മോഡലിനെയും ലളിതമാക്കുന്നു. പെർസിസ്റ്റൻസ് എവിടെയാണ് സംഭവിക്കുന്നത് എന്ന് നിങ്ങൾക്ക് കൃത്യമായി അറിയാം: ടോഗിൾ കൈകാര്യം ചെയ്യുന്ന കോൾബാക്കിലും (callback), ഡെത്ത് (death) കൈകാര്യം ചെയ്യുന്ന ഫംഗ്ഷനിലും. കോഡ്‌ബേസിലുടനീളം അനാവശ്യമായ റൈറ്റുകൾ (writes) ഉണ്ടാകില്ല.

നിങ്ങൾക്കായി ഒരു റീസെറ്റ് ബട്ടൺ നിർമ്മിക്കുക

ഡെവലപ്‌മെന്റ് സമയത്ത് നിങ്ങളുടെ സ്വന്തം സേവുകൾ നിങ്ങൾ തന്നെ തകരാറിലാക്കിയേക്കാം. നിങ്ങൾ തെറ്റായ ഡാറ്റ എഴുതാം, എഡ്ജ് കേസുകൾ (edge cases) പരിശോധിക്കാം, വേഗത്തിൽ ഒരു ക്ലീൻ സ്റ്റേറ്റിലേക്ക് മടങ്ങേണ്ടി വരാം. ഒരു ഡീബഗ് മെനുവിലോ അല്ലെങ്കിൽ ഒരു ഹിഡൻ കീ കോമ്പിനേഷനിലോ ഒരു റീസെറ്റ് ബട്ടൺ നിർമ്മിക്കുക. ആ റീസെറ്റ് ബട്ടൺ കൃത്യമായ ഈ ക്രമത്തിൽ രണ്ട് കാര്യങ്ങൾ ചെയ്യട്ടെ: നിങ്ങളുടെ ഇൻ-മെമ്മറി സ്റ്റേറ്റിനെ (in-memory state) ഡിഫോൾട്ട് സ്കീമയിലേക്ക് റീസെറ്റ് ചെയ്യുക, തുടർന്ന് ഉടൻ തന്നെ ലോക്കൽ സ്റ്റോറേജിലേക്ക് എഴുതുന്ന അതേ സേവ് ഫംഗ്ഷൻ വിളിക്കുക.

നിങ്ങൾ ലോക്കൽ വേരിയബിൾ മാത്രം ക്ലിയർ ചെയ്യുകയും റൈറ്റ് സ്റ്റെപ്പ് ഒഴിവാക്കുകയും ചെയ്താൽ, നിങ്ങൾ ഒന്നും നേടില്ല. അടുത്ത തവണ പേജ് റീഫ്രഷ് ചെയ്യുമ്പോൾ ബ്രൗസറിൽ നിന്ന് പഴയ ഡാറ്റ വീണ്ടും വരികയും അത് പുനരുജ്ജീവിപ്പിക്കപ്പെടുകയും ചെയ്യും. പെർസിസ്റ്റ് ചെയ്യാൻ മറന്നുപോകുന്ന ഒരു റീസെറ്റ് എന്നത് ഒരു വൈകുന്നേരം മുഴുവൻ പാഴാക്കുന്ന തരത്തിലുള്ള ഒരു ബഗ്ഗാണ്. ഈ ക്രമം കൃത്യമായി ചെയ്താൽ, പ്രോജക്റ്റിന്റെ ബാക്കി സമയത്ത് നിങ്ങളുടെ ടെസ്റ്റിംഗ് ലൂപ്പ് വേഗത്തിൽ നിലനിൽക്കും.

യഥാർത്ഥ പാഠം

സേവിംഗ് എന്നത് അവസാനം വെറുതെ ചേർക്കുന്ന ഒരു ഫീച്ചറല്ല. നിങ്ങളുടെ ഗെയിം എത്രത്തോളം നിലനിൽക്കുന്നതും കളിക്കാരന്റെ സമയത്തെ ബഹുമാനിക്കുന്നതുമാണ് എന്ന് നിർണ്ണയിക്കുന്ന ഇൻഫ്രാസ്ട്രക്ചർ (infrastructure) ആണത്. ഒരു Phaser 4 survivor shooter അതിന്റെ ആവർത്തിച്ചുള്ള റണ്ണുകളെ ആശ്രയിച്ചാണ് നിലനിൽക്കുന്നത്. ബ്രൗസർ ടാബ് കളിക്കാരന്റെ പുരോഗതിക്ക് നേരെ ചൂണ്ടിയുള്ള ഒരു ഭീഷണിയാണെങ്കിൽ, അവർ പിന്നീട് വരുന്നത് നിർത്തും. ഒരു സ്കീമ എഴുതുക, തെറ്റായ ഡാറ്റയിൽ നിന്ന് സംരക്ഷണം നൽകുക, റീപ്ലേസ് ചെയ്യുന്നതിന് പകരം മെർജ് ചെയ്യുക, നിങ്ങളുടെ കീകൾക്ക് വേർഷനിംഗ് നൽകുക, ഓരോ അർത്ഥവത്തായ ഇവന്റിലും സേവ് ചെയ്യുക. നിങ്ങളുടെ ഭാവിയിലെ നിങ്ങളും, നിങ്ങളുടെ അടുത്ത അപ്‌ഡേറ്റിന് ശേഷം മടങ്ങിവരുന്ന ഓരോ കളിക്കാരനും നിങ്ങളോട് നന്ദി പറയും.