Classic Solitaire Klondike


#21

I just received few crash reports, maybe you can help, pls?

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Huawei/ALE-L23/hwALE-H:5.0.1/HuaweiALE-L23/C605B150:user/release-keys'
Revision: '0'
ABI: 'arm'
pid: 32342, tid: 32433, name: Thread-303  >>> com.potatojam.classic.solitaire.klondike <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: '../src/opengl/graphics_opengl.cpp:1646: void dmGraphics::SetTexture(dmGraphics::HTexture, const dmGraphics::TextureParams&): assertion "params.m_Height <= g_Context->m_MaxTextureSize" failed'
    r0 00000000  r1 00007eb1  r2 00000006  r3 00000000
    r4 e302bdd8  r5 00000006  r6 00000002  r7 0000010c
    r8 e302b764  r9 e302b740  sl e3291adc  fp ab545438
    ip 00007eb1  sp e302b5e8  lr f707d699  pc f70a3a94  cpsr 600f0010
backtrace:
    #00 pc 0003ca94  /system/lib/libc.so (tgkill+12)
    #01 pc 00016695  /system/lib/libc.so (pthread_kill+52)
    #02 pc 000172a7  /system/lib/libc.so (raise+10)
    #03 pc 00013be5  /system/lib/libc.so (__libc_android_abort+36)
    #04 pc 000123bc  /system/lib/libc.so (abort+4)
    #05 pc 00014dc3  /system/lib/libc.so (__libc_fatal+16)
    #06 pc 00013c69  /system/lib/libc.so (__assert2+20)
    #07 pc 000d795c  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (dmGraphics::SetTexture(dmGraphics::Texture*, dmGraphics::TextureParams const&)+496)
    #08 pc 000d900c  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (dmGraphics::NewRenderTarget(dmGraphics::Context*, unsigned int, dmGraphics::TextureCreationParams const*, dmGraphics::TextureParams const*)+612)
    #09 pc 000e683c  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (dmRender::RenderScript_RenderTarget(lua_State*)+1512)
    #10 pc 0010f4f8  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so
Tombstone written to: /data/tombstones/tombstone_00


logcat.txt.zip (148.1 KB)

And second:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/j1acevelteub/j1acevelte:5.1.1/LMY47V/J111MUBU0AQF2:user/release-keys'
Revision: '4'
ABI: 'arm'
pid: 21771, tid: 21853, name: Thread-1856  >>> com.potatojam.classic.solitaire.klondike <<<
signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
Abort message: '../src/opengl/graphics_opengl.cpp:1646: void dmGraphics::SetTexture(dmGraphics::HTexture, const dmGraphics::TextureParams&): assertion "params.m_Height <= g_Context->m_MaxTextureSize" failed'
    r0 00000000  r1 0000555d  r2 00000006  r3 00000000
    r4 a2dcfdb8  r5 00000006  r6 00000009  r7 0000010c
    r8 a2dcf744  r9 a2dcf720  sl a302eadc  fp b4772e80
    ip 0000555d  sp a2dcf5c8  lr b6de3735  pc b6e06dbc  cpsr 600f0010
backtrace:
    #00 pc 0003adbc  /system/lib/libc.so (tgkill+12)
    #01 pc 00017731  /system/lib/libc.so (pthread_kill+52)
    #02 pc 00018347  /system/lib/libc.so (raise+10)
    #03 pc 00014be1  /system/lib/libc.so (__libc_android_abort+36)
    #04 pc 00012f70  /system/lib/libc.so (abort+4)
    #05 pc 00015e25  /system/lib/libc.so (__libc_fatal+16)
    #06 pc 00014c65  /system/lib/libc.so (__assert2+20)
    #07 pc 000d795c  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (_ZN10dmGraphics10SetTextureEPNS_7TextureERKNS_13TextureParamsE+496)
    #08 pc 000d900c  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (_ZN10dmGraphics15NewRenderTargetEPNS_7ContextEjPKNS_21TextureCreationParamsEPKNS_13TextureParamsE+612)
    #09 pc 000e683c  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so (_ZN8dmRender25RenderScript_RenderTargetEP9lua_State+1512)
    #10 pc 0010f4f8  /data/app/com.potatojam.classic.solitaire.klondike-1/lib/arm/libSolitaire_Klondike.so
Tombstone written to: /data/tombstones/tombstone_01
[ 11-26 01:49:01.121   183:  183 E/         ]
ro.product_ship = true
[ 11-26 01:49:01.121   183:  183 E/         ]
ro.debug_level = 0x4f4c


logcat-2.txt.zip (290.0 KB)

Strange, but some error with m_MaxTextureSize
We have only one texture in game:
1024x256
and second one I make dynamically in render script:

local helper = require "modules.render.helper"
local msgs = require "modules.msgs"

local RT_halfsize = 50

local function update_render_targets(self, initial)
  if not initial then
    render.delete_render_target(self.render_target)
  end
  self.RT_color_params.width, self.RT_depth_params.width = helper.win_width, helper.win_width
  self.RT_color_params.height, self.RT_depth_params.height = helper.win_height, helper.win_height

  self.render_target = render.render_target("target", {[render.BUFFER_COLOR_BIT] = self.RT_color_params,
  [render.BUFFER_DEPTH_BIT] = self.RT_depth_params})

  local x = RT_halfsize * 2 / helper.win_width
  local y = RT_halfsize * 2 / helper.win_height
  self.RT_proj = vmath.matrix4_orthographic(-RT_halfsize + x, RT_halfsize - x,
  - RT_halfsize + y, RT_halfsize - y, - 1, 1)

  self.firstFrame = true
end

local function update_window(self)
  helper.win_width = render.get_window_width()
  helper.win_height = render.get_window_height()

  if helper.win_width >= helper.win_height then
    helper.settings_width = render.get_height()
    helper.settings_height = render.get_width()
  else
    helper.settings_width = render.get_width()
    helper.settings_height = render.get_height()
  end

  helper.zoom_factor = math.min(helper.win_width / helper.settings_width, helper.win_height / helper.settings_height)
  self.projected_width = helper.win_width / helper.zoom_factor
  self.projected_height = helper.win_height / helper.zoom_factor
  helper.xoffset = - (self.projected_width - helper.settings_width) / 2
  helper.yoffset = - (self.projected_height - helper.settings_height) / 2

  helper.adjust_stretch_x = helper.win_width / helper.settings_width
  helper.adjust_stretch_y = helper.win_height / helper.settings_height
  helper.adjust_zoom = math.max(helper.adjust_stretch_x, helper.adjust_stretch_y)
  helper.adjust_fit = math.min(helper.adjust_stretch_x, helper.adjust_stretch_y)
  self.full_proj = vmath.matrix4_orthographic(helper.xoffset, helper.xoffset + self.projected_width,
  helper.yoffset, helper.yoffset + self.projected_height, - 1, 1)

  self.view = vmath.matrix4()
  self.gui_projection = vmath.matrix4_orthographic(0, helper.win_width, 0, helper.win_height, - 1, 1)
end

function init(self)
  self.tile_pred = render.predicate({"tile"})
  self.gui_pred = render.predicate({"gui"})
  self.text_pred = render.predicate({"text"})
  self.render_target_pred = render.predicate({"render_target"})

  helper.settings_width = render.get_width()
  helper.settings_height = render.get_height()

  self.clear_color = vmath.vector4(0, 0, 0, 0)
  self.clear_color.x = sys.get_config("render.clear_color_red", 0)
  self.clear_color.y = sys.get_config("render.clear_color_green", 0)
  self.clear_color.z = sys.get_config("render.clear_color_blue", 0)
  self.clear_color.w = sys.get_config("render.clear_color_alpha", 0)

  self.RT_color_params = {
    format = render.FORMAT_RGBA,
    width = render.get_window_width(),
    height = render.get_window_height(),
    min_filter = render.FILTER_LINEAR,
    mag_filter = render.FILTER_LINEAR,
    u_wrap = render.WRAP_CLAMP_TO_EDGE,
    v_wrap = render.WRAP_CLAMP_TO_EDGE
  }
  self.RT_depth_params = {
    format = render.FORMAT_DEPTH,
    width = render.get_window_width(),
    height = render.get_window_height(),
    u_wrap = render.WRAP_CLAMP_TO_EDGE,
    v_wrap = render.WRAP_CLAMP_TO_EDGE
  }

  self.clear_param = {
    [render.BUFFER_COLOR_BIT] = self.clear_color,
    [render.BUFFER_DEPTH_BIT] = 1,
    [render.BUFFER_STENCIL_BIT] = 0
  }

  self.view = vmath.matrix4()
  update_window(self)
  update_render_targets(self, true)
end

function update(self)
  render.set_depth_mask(true)
  render.clear(self.clear_param)

  render.set_viewport(0, 0, helper.win_width, helper.win_height)
  render.set_view(self.view)
  render.set_depth_mask(false)
  render.disable_state(render.STATE_DEPTH_TEST)
  render.disable_state(render.STATE_STENCIL_TEST)
  render.enable_state(render.STATE_BLEND)
  render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
  render.disable_state(render.STATE_CULL_FACE)

  render.set_projection(self.full_proj)

  if self.is_render_target then

    render.enable_render_target(self.render_target)
    if self.firstFrame then
      self.firstFrame = false
      render.clear(self.clear_param)
    end

    render.draw(self.tile_pred)
    render.disable_render_target(self.render_target)

    render.set_view(self.view)
    render.set_projection(self.RT_proj)
    render.enable_texture(0, self.render_target, render.BUFFER_COLOR_BIT)
    render.draw(self.render_target_pred)
    render.disable_texture(0, self.render_target)
  else
    render.draw(self.tile_pred)
  end

  render.set_view(self.view)
  render.set_projection(self.gui_projection)

  render.enable_state(render.STATE_STENCIL_TEST)
  render.draw(self.gui_pred)
  render.draw(self.text_pred)
  render.disable_state(render.STATE_STENCIL_TEST)

  render.set_depth_mask(false)
end

function on_message(self, message_id, message)
  if message_id == hash("clear_color") then
    self.clear_color = message.color
    self.clear_param[render.BUFFER_COLOR_BIT] = self.clear_color
  elseif message_id == hash("set_view_projection") then
    self.view = message.view
  elseif message_id == msgs.WINDOW_RESIZED then
    update_window(self)
    update_render_targets(self)
    msg.post("main:/main#main", message_id, message)
  elseif message_id == msgs.R_TARGET then
    self.is_render_target = message.isOn
    if self.is_render_target then
      update_render_targets(self)
    end
  end
end

#22

The assert you are seeing is a check so that we don’t create a render target with dimensions greater than what is supported by the device

glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
...
assert(params.m_Height <= max_texture_size);

Maybe helper.win_width for some reason get assigned too big a value?


#23

I have only one place where I set this value (in render script):

 helper.win_width = render.get_window_width()
  helper.win_height = render.get_window_height()

Both device has smaller screen size than supported texture.
Is it possible that Init of render_sript runs earliar that initialization of global render module and I recieve some default value in render.get_window_width() instead of real size?
(I have no this device to check this value - logs from google console)


#24

There are also some glGetError checks when creating textures in dmGraphics::SetTexture which could unfortunately result in assert if they fail. However I can’t see any issues with the parameters you pass it…

But it might be a good idea to do these to things (based on my own experience) anyway:

  • Make sure your render targets are power-of-two in size (even if CLAMP_TO_EDGE should be able to handle NPOT textures).
  • Switch to NEAREST filtering unless you really need LINEAR (I guess you use the render target as a post-process so it’s always 1:1 from texture to screen fragments, so linear doesn’t really do much here anyway).

#25

Cool, thanks.
I also find this:
https://aras-p.info/blog/2012/10/17/non-power-of-two-textures/

ES 2.0 has limited NPOT support (no mipmaps, no Repeat wrap mode) in core; and ES 1.1 has no NPOT support.

I try to set and retest build with next options for material:


but i have no this device(

Have somebody device with Mali-400MP2 or Mali-T830MP2 ?

UPD: :point_up_2: fixed problem on P8 Lite but not with Galaxy J1 Ace =(

UPD2: : next render script with POT render target help me with my issue. Thank you @sven

local helper = require "modules.render.helper"
local msgs = require "modules.msgs"

local RT_halfsize = 50

local function next_pot(num)
  local result = 1
  while num > result do
    result = bit.lshift(result, 1)
  end
  return result
end

local function update_render_targets(self, initial)
  if not initial then
    render.delete_render_target(self.render_target)
  end

  local pot_width = next_pot(helper.win_width)
  local pot_height = next_pot(helper.win_height)

  self.RT_color_params.width, self.RT_depth_params.width = pot_width, pot_width
  self.RT_color_params.height, self.RT_depth_params.height = pot_height, pot_height

  self.render_target = render.render_target("target", {[render.BUFFER_COLOR_BIT] = self.RT_color_params,
  [render.BUFFER_DEPTH_BIT] = self.RT_depth_params})

  local RT_SIZE = RT_halfsize * 2
  local x = RT_SIZE / pot_width * (pot_width - helper.win_width)
  local y = RT_SIZE / pot_height * (pot_height - helper.win_height)
  local nx = RT_SIZE / helper.win_width
  local ny = RT_SIZE / helper.win_height
  self.RT_proj = vmath.matrix4_orthographic(-RT_halfsize + nx, RT_halfsize - x - nx,
  - RT_halfsize + ny, RT_halfsize - y - ny, - 1, 1)

  self.firstFrame = true
end

local function update_window(self)
  helper.win_width = render.get_window_width()
  helper.win_height = render.get_window_height()

  if helper.win_width >= helper.win_height then
    helper.settings_width = render.get_height()
    helper.settings_height = render.get_width()
  else
    helper.settings_width = render.get_width()
    helper.settings_height = render.get_height()
  end

  helper.zoom_factor = math.min(helper.win_width / helper.settings_width, helper.win_height / helper.settings_height)
  self.projected_width = helper.win_width / helper.zoom_factor
  self.projected_height = helper.win_height / helper.zoom_factor
  helper.xoffset = - (self.projected_width - helper.settings_width) / 2
  helper.yoffset = - (self.projected_height - helper.settings_height) / 2

  helper.adjust_stretch_x = helper.win_width / helper.settings_width
  helper.adjust_stretch_y = helper.win_height / helper.settings_height
  helper.adjust_zoom = math.max(helper.adjust_stretch_x, helper.adjust_stretch_y)
  helper.adjust_fit = math.min(helper.adjust_stretch_x, helper.adjust_stretch_y)
  self.full_proj = vmath.matrix4_orthographic(helper.xoffset, helper.xoffset + self.projected_width,
  helper.yoffset, helper.yoffset + self.projected_height, - 1, 1)

  self.view = vmath.matrix4()
  self.gui_projection = vmath.matrix4_orthographic(0, helper.win_width, 0, helper.win_height, - 1, 1)
end

function init(self)
  self.tile_pred = render.predicate({"tile"})
  self.gui_pred = render.predicate({"gui"})
  self.text_pred = render.predicate({"text"})
  self.render_target_pred = render.predicate({"render_target"})

  helper.settings_width = render.get_width()
  helper.settings_height = render.get_height()

  self.clear_color = vmath.vector4(0, 0, 0, 0)
  self.clear_color.x = sys.get_config("render.clear_color_red", 0)
  self.clear_color.y = sys.get_config("render.clear_color_green", 0)
  self.clear_color.z = sys.get_config("render.clear_color_blue", 0)
  self.clear_color.w = sys.get_config("render.clear_color_alpha", 0)

  self.RT_color_params = {
    format = render.FORMAT_RGBA,
    width = render.get_window_width(),
    height = render.get_window_height(),
    min_filter = render.FILTER_LINEAR,
    mag_filter = render.FILTER_LINEAR,
    u_wrap = render.WRAP_CLAMP_TO_EDGE,
    v_wrap = render.WRAP_CLAMP_TO_EDGE
  }
  self.RT_depth_params = {
    format = render.FORMAT_DEPTH,
    width = render.get_window_width(),
    height = render.get_window_height(),
    u_wrap = render.WRAP_CLAMP_TO_EDGE,
    v_wrap = render.WRAP_CLAMP_TO_EDGE
  }

  self.clear_param = {
    [render.BUFFER_COLOR_BIT] = self.clear_color,
    [render.BUFFER_DEPTH_BIT] = 1,
    [render.BUFFER_STENCIL_BIT] = 0
  }

  self.view = vmath.matrix4()
  update_window(self)
  update_render_targets(self, true)
end

function update(self)
  render.set_depth_mask(true)
  render.clear(self.clear_param)

  render.set_viewport(0, 0, helper.win_width, helper.win_height)
  render.set_view(self.view)
  render.set_depth_mask(false)
  render.disable_state(render.STATE_DEPTH_TEST)
  render.disable_state(render.STATE_STENCIL_TEST)
  render.enable_state(render.STATE_BLEND)
  render.set_blend_func(render.BLEND_SRC_ALPHA, render.BLEND_ONE_MINUS_SRC_ALPHA)
  render.disable_state(render.STATE_CULL_FACE)

  render.set_projection(self.full_proj)

  if self.is_render_target then

    render.enable_render_target(self.render_target)
    if self.firstFrame then
      self.firstFrame = false
      render.clear(self.clear_param)
    end

    render.draw(self.tile_pred)
    render.disable_render_target(self.render_target)

    render.set_view(self.view)
    render.set_projection(self.RT_proj)
    render.enable_texture(0, self.render_target, render.BUFFER_COLOR_BIT)
    render.draw(self.render_target_pred)
    render.disable_texture(0, self.render_target)
  else
    render.draw(self.tile_pred)
  end

  render.set_view(self.view)
  render.set_projection(self.gui_projection)

  render.enable_state(render.STATE_STENCIL_TEST)
  render.draw(self.gui_pred)
  render.draw(self.text_pred)
  render.disable_state(render.STATE_STENCIL_TEST)

  render.set_depth_mask(false)
end

function on_message(self, message_id, message)
  if message_id == hash("clear_color") then
    self.clear_color = message.color
    self.clear_param[render.BUFFER_COLOR_BIT] = self.clear_color
  elseif message_id == hash("set_view_projection") then
    self.view = message.view
  elseif message_id == msgs.WINDOW_RESIZED then
    update_window(self)
    update_render_targets(self)
    msg.post("main:/main#main", message_id, message)
  elseif message_id == msgs.R_TARGET then
    self.is_render_target = message.isOn
    if self.is_render_target then
      update_render_targets(self)
    end
  end
end


#26

We made update 1.60:

  • random deck option - now user can play with deck that can be solved 100%;
  • russian localization - we made integration of localization system into game, for now it’s just Russion and English, In future we will add more, I think.

#27

How are player metrics doing?


#28

Pretty good.
Audience is growing up stable, slow but stable (Daily app installs are bigger than daily app removes)

Retention (ios only)
Day 2: 20 - 25%
Day 7: 10-20%
Day 30: 5-10%

Now we are working on the new update with one major feature - undo, and few small improvements.
I think it will be last major update. After that only minor updates with bug fixes and so on.
Maybe in future we itegrate google play services like leaderboards, achievements etc. But for now nobody makes this NE and we have other priorities too.

UPD:
Defold analytics didn’t work for android, maybe because I removed a lot of permissions.
Retention percent actual only for IOS, we have no data for android. I’ll try to collect this data after next update.


#29

Are you tracking any other data? https://www.defold.com/community/projects/86424/ Might be helpful for that

I’d be curious to know what percentage of users play on landscape vs portrait layout for example. Did you have any trouble implementing that?

Are the artifacts in the second button due to Defold’s texture compression?


#30

No, we didn’t integrate any analytics to this project.
I’ll think about it, but I’m not sure that we have time. We do not need analytics in this project.

No, this artefacts in original image (We will fix that when finished with major features and start to distribute HTML5 version.):
google


#31

Have you published postmortems for any of your finished projects before?


#32

No. I have a task in my personal task manager to make postmortem for Bring me cakes, but I have no time, and we still working on the game :slight_smile:


#33

We made update 1.71:

  • Implemented UNDO button!
  • Added sound settings.
  • Minor bugfixes and improvements.

We will back to the project in future for adding of Google Play Services and Game Center but for now project is frozen.
We have plans to use this solitaire “engine” for make more original game in future.


#34

Nice work! :slight_smile:

Just a heads-up: Google has a new “Get it on Google Play” button which aligns with their current brand—you should consider switching to the new one.


#35

Thank you! We made an update of HTML5 version with new buttons, and with newest 1.2.119 update.
P.S: @titkov.epicstar told that now html5 works better on his phone =)


#36

I’m happy to hear that the perf increased on HTML5 for you too!


#37

It moves pretty crisp for a Defold game in HTML.

If you don’t mind sharing, how are you detecting clicks on cards? Collisions?

Also it’s super nice that it resizes to the size of the phone. Any magic there?


#38

Thank you!

No, I removed all physics using buildmanifest.
Click detection is a simple AABB.
A cards deck have only 52 cards and most of them closed. Simple iterator with few condition and AABB are really fast.

No, @Dragosha shared his js code for canvas scale,I made some modernisations and move it to html5 template (I can set this template into settings).


Here is template: engine_template.html.zip (1.6 KB)


#39

Awesome.

Thanks for sharing.


#40

Yes definitely faster on my iPhone6 too

Great work!