From 2ed1ff41f4819267f15dcfe7ab4b4f2dd9b1fcfa Mon Sep 17 00:00:00 2001 From: Mattias Bodlund Date: Wed, 25 Mar 2026 14:07:30 +0100 Subject: [PATCH] na --- .gitignore | 2 + Gemfile.lock | 136 +++++++++--------- README DB.txt | 1 + app/controllers/players_controller.rb | 2 +- app/controllers/stages_controller.rb | 88 ++++++++---- .../concerns/ancestry_with_sorted_url.rb | 19 +-- app/models/node.rb | 9 +- app/models/player.rb | 8 +- app/views/stages/bonus.html.erb | 21 +++ app/views/stages/result.html.erb | 53 ++++--- config/locales/en.yml | 9 +- config/routes.rb | 7 +- db/migrate/20250527114853_create_players.rb | 2 +- db/schema.rb | 4 +- lib/tasks/nodes.rake | 60 ++++++++ lib/tasks/quiz_stats.rake | 81 ----------- 16 files changed, 278 insertions(+), 224 deletions(-) create mode 100644 app/views/stages/bonus.html.erb create mode 100644 lib/tasks/nodes.rake delete mode 100644 lib/tasks/quiz_stats.rake diff --git a/.gitignore b/.gitignore index 41d2701..04664af 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,10 @@ # Ignore all logfiles and tempfiles. /log/* /tmp/* +/dumps/* !/log/.keep !/tmp/.keep +!/dumps/.keep # Ignore pidfiles, but keep the directory. /tmp/pids/* diff --git a/Gemfile.lock b/Gemfile.lock index e0e4daf..8c382fb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,29 +3,29 @@ GEM specs: action_text-trix (2.1.17) railties - actioncable (8.1.2.1) - actionpack (= 8.1.2.1) - activesupport (= 8.1.2.1) + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.2.1) - actionpack (= 8.1.2.1) - activejob (= 8.1.2.1) - activerecord (= 8.1.2.1) - activestorage (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (8.1.2.1) - actionpack (= 8.1.2.1) - actionview (= 8.1.2.1) - activejob (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.2.1) - actionview (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -33,36 +33,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.2.1) + actiontext (8.1.3) action_text-trix (~> 2.1.15) - actionpack (= 8.1.2.1) - activerecord (= 8.1.2.1) - activestorage (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.2.1) - activesupport (= 8.1.2.1) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.1.2.1) - activesupport (= 8.1.2.1) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (8.1.2.1) - activesupport (= 8.1.2.1) - activerecord (8.1.2.1) - activemodel (= 8.1.2.1) - activesupport (= 8.1.2.1) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) - activestorage (8.1.2.1) - actionpack (= 8.1.2.1) - activejob (= 8.1.2.1) - activerecord (= 8.1.2.1) - activesupport (= 8.1.2.1) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (8.1.2.1) + activesupport (8.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -247,20 +247,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.2.1) - actioncable (= 8.1.2.1) - actionmailbox (= 8.1.2.1) - actionmailer (= 8.1.2.1) - actionpack (= 8.1.2.1) - actiontext (= 8.1.2.1) - actionview (= 8.1.2.1) - activejob (= 8.1.2.1) - activemodel (= 8.1.2.1) - activerecord (= 8.1.2.1) - activestorage (= 8.1.2.1) - activesupport (= 8.1.2.1) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 8.1.2.1) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -268,9 +268,9 @@ GEM rails-html-sanitizer (1.7.0) loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.1.2.1) - actionpack (= 8.1.2.1) - activesupport (= 8.1.2.1) + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -325,7 +325,7 @@ GEM ffi (~> 1.12) logger securerandom (0.4.1) - sidekiq (8.1.1) + sidekiq (8.1.2) connection_pool (>= 3.0.0) json (>= 2.16.0) logger (>= 1.7.0) @@ -405,17 +405,17 @@ DEPENDENCIES CHECKSUMS action_text-trix (2.1.17) sha256=b44691639d77e67169dc054ceacd1edc04d44dc3e4c6a427aa155a2beb4cc951 - actioncable (8.1.2.1) sha256=a2f88cecce148b3fcb63d2e517d7694e119830a85baa7d6cf59e5453dcf32e8d - actionmailbox (8.1.2.1) sha256=c2e45c0c1e5687e35e050838c94a8aed0d954c56a32ea411d54cd848c338c54e - actionmailer (8.1.2.1) sha256=d7d62fbc2197f1a7006bb5af4c665edf999adf79ab6c10337c088d27e6622071 - actionpack (8.1.2.1) sha256=a6b69cd10ec4c8d978c8eee51206e34152b1c1be017e534236dbc89a3d00ffb8 - actiontext (8.1.2.1) sha256=1e503ce600a6ab2e12a46f999959a7d8e2fdaff910ca01dcf3b968934b55d957 - actionview (8.1.2.1) sha256=38daa7b87bca427e2967f139e5b7f0d1081271bdafd0e015d8ef97a006f570a6 - activejob (8.1.2.1) sha256=c89c311d07fd358b76c581ed8fee87c5b4351fb44994f3389385c014d22182fe - activemodel (8.1.2.1) sha256=8f31a6f9c12fecb8e5a0fce8a8950cfd94f0d75829322935f99e8217a3e5f3c6 - activerecord (8.1.2.1) sha256=3f79140318ff6d23376f5d9b1b5b5e2c7d3cc8979dd71367e9a8394378ca630a - activestorage (8.1.2.1) sha256=36794c9b8853ac9276b0386cb1f8973374d8e71e8a9666bb02e70f5b7c9c5391 - activesupport (8.1.2.1) sha256=beec20ced12ad569194554399449a6372fdab03061b8f48a9ed6ef9b7dc251b2 + actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0 + actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59 + actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d + actionpack (8.1.3) sha256=af998cae4d47c5d581a2cc363b5c77eb718b7c4b45748d81b1887b25621c29a3 + actiontext (8.1.3) sha256=d291019c00e1ea9e6463011fa214f6081a56d7b9a1d224e7d3f6384c1dafc7d2 + actionview (8.1.3) sha256=1347c88c7f3edb38100c5ce0e9fb5e62d7755f3edc1b61cce2eb0b2c6ea2fd5d + activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3 + activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219 + activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab + activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d + activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8 addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485 ancestry (5.1.0) sha256=8a073cf6f7e306eeed36af72595abd19602ef4a197bf4beda2f31cf8f55de27b @@ -509,10 +509,10 @@ CHECKSUMS rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9 rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868 - rails (8.1.2.1) sha256=93ebf1efc792c9bc47e9795259c920312d3920008dad3ae634b7a0457ffe0af8 + rails (8.1.3) sha256=6d017ba5348c98fc909753a8169b21d44de14d2a0b92d140d1a966834c3c9cd3 rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89 - railties (8.1.2.1) sha256=f4d902869541af4e5b5552d726062fa59ec0fd9078f7ab87720dbd93f22c43ee + railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192 @@ -529,7 +529,7 @@ CHECKSUMS ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 - sidekiq (8.1.1) sha256=afdf2736883e1e80c1c9b7c47afe0387e1a4997c87170978a45560d35d429151 + sidekiq (8.1.2) sha256=4a1266f22bb1dad675d77bf32e8d4c04e990640e3b271650f47ea7299c38fceb stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1 thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73 diff --git a/README DB.txt b/README DB.txt index 3d67ea2..834fe7a 100644 --- a/README DB.txt +++ b/README DB.txt @@ -22,6 +22,7 @@ CREATE EXTENSION hstore; sudo -u postgres psql -d ikea_foundtation_week_2026 +pg_dump -Fc ikea_foundtation_week_2026 > db20140909.dump pg_dump -U ikea_foundation_2026 -Fc ikea_foundtation_week_2026 > db20140909.dump diff --git a/app/controllers/players_controller.rb b/app/controllers/players_controller.rb index 3f6cc6f..cd76ca7 100644 --- a/app/controllers/players_controller.rb +++ b/app/controllers/players_controller.rb @@ -17,7 +17,7 @@ class PlayersController < ApplicationController def go_again @player = Player.create( locale: current_player.locale, - extra_lives_stage: current_player.extra_lives_stage + bonus_points: current_player.bonus_points ) session[:player_id] = @player.id diff --git a/app/controllers/stages_controller.rb b/app/controllers/stages_controller.rb index dd46b61..3216ad7 100644 --- a/app/controllers/stages_controller.rb +++ b/app/controllers/stages_controller.rb @@ -9,7 +9,7 @@ class StagesController < ApplicationController before_action :set_outcome, only: [ :reveal, :pick, :result ] - helper_method :stage_index + helper_method :stage_index, :bonus_node # GET @@ -35,7 +35,7 @@ class StagesController < ApplicationController # GET Chance or Choice def reveal @node = @stage.children.viewable.find_by(template: @outcome) - redirect_to root_path and return if @outcome.blank? or @node.blank? + # redirect_to root_path and return if @outcome.blank? or @node.blank? render action: @outcome end @@ -43,18 +43,20 @@ class StagesController < ApplicationController # POST def pick - redirect_to root_path and return if @outcome.blank? + # redirect_to root_path and return if @outcome.blank? @node = @stage.children.viewable.find_by(template: @outcome) possible_answers = @node.children.viewable - if @outcome == "chance" + case @outcome + when "chance" result = possible_answers.pluck(:template).sample - elsif @outcome == "choice" + # result = "game_over" if stage_index == 2 # DEV + when "choice" result = possible_answers.find_by(id: params[:option])&.template end - current_player.record_pick_result(stage_index, result) unless result.blank? + current_player.record_pick_result(stage_index, result) unless result.blank? redirect_to action: "result" end @@ -62,14 +64,19 @@ class StagesController < ApplicationController # GET def result - redirect_to root_path and return if @outcome.blank? + # redirect_to root_path and return if @outcome.blank? - @result = current_player.progress[stage_index.to_s]["result"] - @node = @stage.children.viewable.find_by(template: @outcome)&.children&.viewable&.find_by(template: @result) + if current_player.progress[stage_index.to_s].keys.include?("bonus_result") + @result = current_player.progress[stage_index.to_s]&.dig("bonus_result") + @node = @stage.children.viewable.find_by(template: "bonus")&.children&.viewable&.find_by(template: @result) + else + @result = current_player.progress[stage_index.to_s]&.dig("result") + @node = @stage.children.viewable.find_by(template: @outcome)&.children&.viewable&.find_by(template: @result) - current_player.update(is_done: true) if @node.game_over? + current_player.update(is_done: true) if @node&.game_over? + end - redirect_to root_path and return if @node.blank? + # redirect_to root_path if @node.blank? end @@ -79,33 +86,57 @@ class StagesController < ApplicationController current_player.update(current_stage: stage_index + 1) redirect_to stage_path(id: current_player.current_stage) else - redirect_to root_path + # redirect_to root_path + not_found end end # POST - def bonus + def try_bonus @result = current_player.progress[stage_index.to_s]["result"] - redirect_to root_path unless @result == "bonus" + # redirect_to root_path and return unless @result == "bonus" - # Set extra lives stage - current_player.update( - extra_lives_stage: ((rand < 0.5) ? [ stage_index - 1, 0 ].max : stage_index), - is_done: true - ) + current_player.record_bonus_pick_result(stage_index, nil) + + redirect_to action: "bonus" + end - # Reset progress - # current_player.reset_when_game_over - redirect_to action: "game_over" + # GET + def bonus + @result = current_player.progress[stage_index.to_s]["result"] + # redirect_to root_path and return unless @result == "bonus" + + @node = bonus_node end # POST - def get_extra_life + def pick_bonus + @result = current_player.progress[stage_index.to_s]["result"] + # redirect_to root_path and return unless @result == "bonus" + + @node = @stage.children.viewable.find_by(template: "bonus") + possible_answers = @node.children.viewable + + result = possible_answers.find_by(id: params[:option])&.template + + current_player.record_bonus_pick_result(stage_index, result) unless result.blank? + + redirect_to action: "result" + end + + # POST + def get_bonus_point + if current_player.progress[stage_index.to_s]&.dig("bonus_result") == "good" + bonus_points_value = stage_index + else + bonus_points_value = [ stage_index - 1, 0 ].max + end + current_player.update( - extra_lives_stage: [ stage_index - 1, 0 ].max, + bonus_points: bonus_points_value, is_done: true ) @@ -130,7 +161,7 @@ private return false if stage_index != current_player.current_stage return true if %w[good best].include?(result) - return true if result == "bad" and current_player.extra_lives_stage >= stage_index + return true if result == "bad" and current_player.bonus_points >= stage_index false end @@ -138,7 +169,12 @@ private def set_stage @stage = Node.at_depth(1).stage.viewable.ordered[stage_index - 1] - redirect_to root_path and return if @stage.nil? + # redirect_to root_path and return if @stage.nil? + end + + + def bonus_node + @bonus_node ||= @stage.children&.viewable&.bonus&.first end diff --git a/app/models/concerns/ancestry_with_sorted_url.rb b/app/models/concerns/ancestry_with_sorted_url.rb index f553084..f574b8e 100644 --- a/app/models/concerns/ancestry_with_sorted_url.rb +++ b/app/models/concerns/ancestry_with_sorted_url.rb @@ -3,11 +3,10 @@ module AncestryWithSortedUrl included do - has_ancestry orphan_strategy: :restrict, - cache_depth: true + cache_depth: true - acts_as_list scope: [:ancestry] + acts_as_list scope: [ :ancestry ] before_validation :format_slug, :generate_url @@ -15,21 +14,17 @@ module AncestryWithSortedUrl # validates_uniqueness_of :slug, scope: [:ancestry] after_commit :update_decendensts_url_if_changed - end - - - private def format_slug I18n.available_locales.each do |l| - l_key = l.to_s.downcase.sub('-', '_') + l_key = l.to_s.downcase.sub("-", "_") v = self.root? ? l.to_s : - self.send("slug_#{l_key}").blank? ? (self.title(locale: l) || '').parameterize : self.send("slug_#{l_key}").parameterize + self.send("slug_#{l_key}").blank? ? (self.title(locale: l) || "").parameterize : self.send("slug_#{l_key}").parameterize self.send(:slug=, v, locale: l) end @@ -39,8 +34,8 @@ private def generate_url I18n.available_locales.each do |l| Mobility.with_locale(l) do - v = File.join(self.ancestors.map { |node| node.slug || '' }, self.slug || '') - v = File.join('', v) #unless I18n.default_locale == l + v = File.join(self.ancestors.map { |node| node.slug || "" }, self.slug || "") + v = File.join("", v) # unless I18n.default_locale == l self.send(:url=, v, locale: l) end end @@ -50,6 +45,4 @@ private def update_decendensts_url_if_changed self.reload.descendants.find_each(&:save) if self.previous_changes[:slug].present? or self.previous_changes[:ancestry] end - - end diff --git a/app/models/node.rb b/app/models/node.rb index e6a8d3a..de600ae 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -34,7 +34,8 @@ class Node < ApplicationRecord best: 5, bad: 6, good: 7, - game_over: 8 + game_over: 8, + score: 9 } def available_templates @@ -42,14 +43,16 @@ class Node < ApplicationRecord case depth when 1 - [ :stage, :game_over ] + [ :stage, :game_over, :score ] when 2 - [ :choice, :chance ] + [ :choice, :chance, :bonus ] when 3 if parent&.chance? [ :good, :bad, :game_over, :bonus ] elsif parent&.choice? [ :best, :good, :bad ] + elsif parent&.bonus? + [ :good, :bad ] else [] end diff --git a/app/models/player.rb b/app/models/player.rb index f126d23..4e5edf1 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -1,6 +1,6 @@ class Player < ApplicationRecord attribute :current_stage, :integer, default: 1 - attribute :extra_lives_stage, :integer, default: 0 + attribute :bonus_points, :integer, default: 0 attribute :progress, :json, default: {} attribute :is_done, :boolean, default: false @@ -37,5 +37,9 @@ class Player < ApplicationRecord save end - + def record_bonus_pick_result(stage_index, result) + self.progress[stage_index.to_s] ||= {} + self.progress[stage_index.to_s]["bonus_result"] = result + save + end end diff --git a/app/views/stages/bonus.html.erb b/app/views/stages/bonus.html.erb new file mode 100644 index 0000000..610d0f3 --- /dev/null +++ b/app/views/stages/bonus.html.erb @@ -0,0 +1,21 @@ +<%- + content_for :title, @node.page_title.blank? ? @node.title : @node.page_title + content_for :meta_description, @node.page_description + + content_for :debug, current_player.inspect +%> + + +
+ + <% @node.attachments.each do |attachment| %> + <%= attachment.body.html_safe %> + <% end %> + +
+ <% @node.children.viewable.ordered.each do |answer_option| %> + <%= button_to answer_option.title, url_for(action: 'pick_bonus', option: answer_option.id), method: :post %> + <% end %> +
+ +
\ No newline at end of file diff --git a/app/views/stages/result.html.erb b/app/views/stages/result.html.erb index 38f76f0..4c78f5f 100644 --- a/app/views/stages/result.html.erb +++ b/app/views/stages/result.html.erb @@ -3,38 +3,47 @@ content_for :meta_description, @node.page_description content_for :debug, current_player.inspect + + %>
- <% @node.attachments.each do |attachment| %> - <%= attachment.body.html_safe %> - <% end %> - + <%= @node.attachments[(@result == "bad" and current_player.bonus_points >= stage_index) ? 1 : 0]&.body&.html_safe %> +
- <% case @result %> - <% when "best", "good" %> - <%= button_to t("next_stage"), url_for(action: 'next'), method: :post %> - <% when "bad" %> - <% if current_player.extra_lives_stage >= stage_index %> - <%= button_to t("use_extra_life"), url_for(action: 'next'), method: :post %> - <% else %> - <% if stage_index > 1 %> - <%= button_to t("get_extra_life"), url_for(action: 'get_extra_life'), method: :post %> - <% else %> + <% if @node.parent&.bonus? %> + <%= button_to t("get_bonus_point"), url_for(action: 'get_bonus_point'), method: :post %> + <% else %> + + <% case @result %> + <% when "best", "good" %> + <%= button_to t("next_stage"), url_for(action: 'next'), method: :post %> + + <% when "bad" %> + <% if current_player.bonus_points >= stage_index %> + <%= button_to t("use_bonus_point"), url_for(action: 'next'), method: :post %> + <% else %> + <% if bonus_node %> + <%= button_to t("bonus"), url_for(action: 'try_bonus'), method: :post %> + <% else %> + <%= button_to t("go_again"), go_again_path(), method: :post %> + <% end %> + <% end %> + + <% when "bonus" %> + <%= button_to t("let_my_try"), url_for(action: 'try_bonus'), method: :post %> + + <% when "game_over" %> + <% if bonus_node %> + <%= button_to t("bonus"), url_for(action: 'try_bonus'), method: :post %> + <% else %> <%= button_to t("go_again"), go_again_path(), method: :post %> <% end %> <% end %> - - <% when "bonus" %> - <%= button_to t("bonus"), url_for(action: 'bonus'), method: :post %> - - <% when "game_over" %> - <%= button_to t("go_again"), go_again_path(), method: :post %> - - <% end %> + <% end %>
\ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index ccd1a88..48c650f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4,13 +4,15 @@ en: client_name: IKEA Foundation let_me_try: Let me try! + flip_the_card: Flip the card next_stage: Proceed to the next stage bonus: Bonus go_again: Go again - use_extra_life: Use extra life - get_extra_life: Get extra life - + use_bonus_point: Use bonus point + get_bonus_point: Get bonus point + let_my_try: Let me try + languages: en: English zh: 中文 @@ -211,6 +213,7 @@ en: game_over: Game Over good: Good bonus: Bonus + score: Result page categories: box: Box diff --git a/config/routes.rb b/config/routes.rb index 85d1870..e46fbf2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -63,9 +63,12 @@ Rails.application.routes.draw do get "result", to: "stages#result" post "next", to: "stages#next" - post "bonus", to: "stages#bonus" - post "get_extra_life", to: "stages#get_extra_life" + post "bonus", to: "stages#try_bonus" + get "bonus", to: "stages#bonus" + post "pick_bonus(/:option)", to: "stages#pick_bonus" + + post "get_bonus_point", to: "stages#get_bonus_point" end get "game_over", to: "stages#game_over" diff --git a/db/migrate/20250527114853_create_players.rb b/db/migrate/20250527114853_create_players.rb index 12792f4..cb3f8e4 100644 --- a/db/migrate/20250527114853_create_players.rb +++ b/db/migrate/20250527114853_create_players.rb @@ -3,7 +3,7 @@ class CreatePlayers < ActiveRecord::Migration[8.1] create_table :players do |t| t.text :locale t.integer :current_stage, index: true - t.integer :extra_lives_stage, index: true + t.integer :bonus_points, index: true t.jsonb :progress, default: {} t.boolean :is_done, default: false, index: true t.timestamps diff --git a/db/schema.rb b/db/schema.rb index 533ab8a..3a39b5a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -115,15 +115,15 @@ ActiveRecord::Schema[8.1].define(version: 2025_05_27_114853) do end create_table "players", force: :cascade do |t| + t.integer "bonus_points" t.datetime "created_at", null: false t.integer "current_stage" - t.integer "extra_lives_stage" t.boolean "is_done", default: false t.text "locale" t.jsonb "progress", default: {} t.datetime "updated_at", null: false + t.index ["bonus_points"], name: "index_players_on_bonus_points" t.index ["current_stage"], name: "index_players_on_current_stage" - t.index ["extra_lives_stage"], name: "index_players_on_extra_lives_stage" t.index ["is_done"], name: "index_players_on_is_done" end diff --git a/lib/tasks/nodes.rake b/lib/tasks/nodes.rake new file mode 100644 index 0000000..5fdb45c --- /dev/null +++ b/lib/tasks/nodes.rake @@ -0,0 +1,60 @@ +namespace :nodes do + desc "Deep copy a node including descendants and attachments. Usage: rake nodes:copy[22]" + task :copy, [ :node_id ] => :environment do |t, args| + node_id = args[:node_id] || 22 + begin + source = Node.find(node_id) + puts "Starting deep copy of Node #{source.id}: \"#{source.title}\"..." + + # We wrap in a transaction to ensure integrity + new_root = Node.transaction do + copy_node_recursively(source) + end + + puts "Successfully created deep copy!" + puts "Source ID: #{source.id}" + puts "New ID: #{new_root.id}" + rescue ActiveRecord::RecordNotFound + puts "Error: Node with ID #{node_id} not found." + rescue => e + puts "Error during copy: #{e.message}" + puts e.backtrace.first(5) + end + end + + def copy_node_recursively(source, parent = nil) + # 1. Duplicate the node (this copies attributes including JSONB translations) + new_node = source.dup + + # 2. Set parent (Ancestry gem handles the tree logic) + # If no parent provided, we default to the same parent as the source (sibling) + new_node.parent = parent || source.parent + + # 3. Modify title of the root copy to distinguish it + if parent.nil? + I18n.available_locales.each do |locale| + title = source.title(locale: locale) + if title.present? + new_node.send(:title=, "#{title} (Copy)", locale: locale) + end + end + end + + # 4. Save to get an ID + new_node.save! + + # 5. Duplicate Attachments + source.attachments.each do |attachment| + new_attachment = attachment.dup + new_attachment.attachable_for = new_node + new_attachment.save! + end + + # 6. Recurse for children + source.children.ordered.each do |child| + copy_node_recursively(child, new_node) + end + + new_node + end +end diff --git a/lib/tasks/quiz_stats.rake b/lib/tasks/quiz_stats.rake deleted file mode 100644 index e9c282d..0000000 --- a/lib/tasks/quiz_stats.rake +++ /dev/null @@ -1,81 +0,0 @@ -# lib/tasks/quiz_stats.rake -namespace :quiz do - desc "Show quiz statistics from Oct 17, 2025" - task stats: :environment do - results = QuizResult.where("created_at >= ?", Date.new(2025, 10, 17)) - - total = results.count - by_locale = results.group(:locale).count - scores = results.group(:score).count - avg_score = results.average(:score) - - # Calculate result categories - questions = Node.at_depth(1).tmpl_article.viewable.ordered.to_a - questions_count = questions.count - result_categories = { "People" => 0, "Planet" => 0, "Balanced" => 0 } - - results.each do |qr| - people_score = questions_count - qr.score - score_diff = people_score - qr.score - - category = case - when score_diff >= 2 - "People" - when score_diff <= -2 - "Planet" - else - "Balanced" - end - result_categories[category] += 1 - end - - puts "Quiz Results from Oct 17, 2025" - puts "=" * 40 - puts "Total: #{total}" - - puts "\nBy locale:" - by_locale.sort.each do |locale, count| - locale_name = I18n.t(locale, scope: 'languages') - puts " #{locale_name}: #{count}" - end - - puts "\nScore distribution:" - scores.sort.each do |score, count| - puts " Score #{score}: #{count}" - end - - puts "\nAverage score: #{avg_score&.round(2)}" - - puts "\nResult categories:" - result_categories.each do |category, count| - percentage = total > 0 ? (count.to_f / total * 100).round(1) : 0 - puts " #{category}: #{count} (#{percentage}%)" - end - - # Question by question breakdown - - puts "\n\nQuestion breakdown:" - puts "=" * 40 - questions.each_with_index do |question, i| - answers = Answer.where(node_id: question.id).where("created_at >= ?", Date.new(2025, 10, 17)) - answer_counts = answers.group(:value).count - - planet_answer = (i == 0 || i == 3) ? 0 : 1 - people_answer = (i == 0 || i == 3) ? 1 : 0 - - planet_count = answer_counts[planet_answer] || 0 - people_count = answer_counts[people_answer] || 0 - total_answers = planet_count + people_count - - puts "\nQ#{i + 1}: #{question.title}" - if total_answers > 0 - planet_pct = (planet_count.to_f / total_answers * 100).round(1) - people_pct = (people_count.to_f / total_answers * 100).round(1) - puts " Planet: #{planet_count} (#{planet_pct}%)" - puts " People: #{people_count} (#{people_pct}%)" - else - puts " No answers" - end - end - end -end