From 26d672af182d48bafc59301ab40c71fa7601e5ba Mon Sep 17 00:00:00 2001 From: Mattias Bodlund Date: Fri, 5 Jun 2026 12:00:03 +0200 Subject: [PATCH] na --- app/assets/images/ico-marketplace.svg | 1 + app/assets/images/ico-storage.svg | 1 + app/assets/images/ico-transport.svg | 1 + app/assets/stylesheets/application.css | 20 +++ app/controllers/game_controller.rb | 39 +++- app/models/player.rb | 18 ++ app/views/layouts/application.html.erb | 17 +- config/locales/en.yml | 3 + config/question_scores.json | 168 ++++++++++++++++++ .../20260605000000_add_scores_to_players.rb | 5 + .../20260605000001_add_is_done_to_players.rb | 7 + db/schema.rb | 6 +- 12 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 app/assets/images/ico-marketplace.svg create mode 100644 app/assets/images/ico-storage.svg create mode 100644 app/assets/images/ico-transport.svg create mode 100644 config/question_scores.json create mode 100644 db/migrate/20260605000000_add_scores_to_players.rb create mode 100644 db/migrate/20260605000001_add_is_done_to_players.rb diff --git a/app/assets/images/ico-marketplace.svg b/app/assets/images/ico-marketplace.svg new file mode 100644 index 0000000..1f17f4c --- /dev/null +++ b/app/assets/images/ico-marketplace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/ico-storage.svg b/app/assets/images/ico-storage.svg new file mode 100644 index 0000000..885c6a9 --- /dev/null +++ b/app/assets/images/ico-storage.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/ico-transport.svg b/app/assets/images/ico-transport.svg new file mode 100644 index 0000000..53ef95d --- /dev/null +++ b/app/assets/images/ico-transport.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 3c82728..6c4801b 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -111,6 +111,7 @@ } + body { margin: 0; display: flex; @@ -124,6 +125,25 @@ body { background-color: var(--clr-white); } +.debug-score { + position: fixed; + font-size: var(--fs-s); + right: 0; + top: 0; + padding: 1rem; + background-color: var(--clr-white); + + & dl { + display: grid; + grid-template-columns: max-content max-content; + gap: 0 1ch; + + & dd { + text-align: right; + } + } +} + button { cursor: pointer; } diff --git a/app/controllers/game_controller.rb b/app/controllers/game_controller.rb index b0c0074..0dd68c9 100644 --- a/app/controllers/game_controller.rb +++ b/app/controllers/game_controller.rb @@ -44,8 +44,11 @@ class GameController < ApplicationController @answer = @answer.children.sample if @answer&.chance? current_player.record_answer(stage_index, @answer.id) + apply_score(score_entry_for(stage_index, @answer)) - if stage_index >= n_stages + bad_chance_outcome = @answer.parent&.chance? && @answer.bad_answer? + + if stage_index >= n_stages && !bad_chance_outcome redirect_to action: :done else redirect_to action: :stage_result, id: params[:id] @@ -73,6 +76,7 @@ class GameController < ApplicationController @answer = @node.children.find_by(id: params[:value]) current_player.record_last_save_answer(@answer.id) + apply_score(last_save_score_entry_for(@answer)) redirect_to action: :done end @@ -134,4 +138,37 @@ private # return File.join('', params[:url]) if I18n.default_locale == I18n.locale File.join("", I18n.locale.to_s, params[:url]) end + + + def question_scores + @@question_scores ||= JSON.parse(Rails.root.join("config", "question_scores.json").read) + end + + + def score_entry_for(stage_idx, answer_node) + stage_data = question_scores["stages"][stage_idx - 1] + return nil unless stage_data + + if answer_node.parent&.chance? + chance_entry = stage_data["answers"][answer_node.parent.position - 1] + chance_entry&.dig("outcomes", answer_node.position - 1) + else + stage_data["answers"][answer_node.position - 1] + end + end + + + def last_save_score_entry_for(answer_node) + question_scores.dig("last_save", "answers", answer_node.position - 1) + end + + + def apply_score(entry) + return unless entry + + current_player.add_to_score( + overall: entry["overall"].to_i, + impact: entry["impact"] || {} + ) + end end diff --git a/app/models/player.rb b/app/models/player.rb index ff05398..8f9d12e 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -5,6 +5,8 @@ class Player < ApplicationRecord LAST_SAVE_KEY = "last_save".freeze + SCORE_KEYS = %w[food_waste emissions income].freeze + def record_answer(stage, answer_id) stage_data(stage)["answer_id"] = answer_id @@ -12,6 +14,22 @@ class Player < ApplicationRecord end + def add_to_score(overall:, impact: {}) + self.score = score.to_i + overall.to_i + impact.each do |key, value| + key = key.to_s + next unless SCORE_KEYS.include?(key) + scores[key] = scores[key].to_i + value.to_i + end + save + end + + + def score_for(key) + scores[key.to_s].to_i + end + + def record_result(stage, result_id) stage_data(stage)["result_id"] = result_id save diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 3ba9e0e..7b800cb 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -21,7 +21,22 @@ <%= content_for :header %> - + + <% if respond_to?(:current_player) && current_player %> + + <% end %> + <%= tag.main class: [@result_node, @node].compact.map { |n| n.template }.first, data: { controller: content_for(:main_controller) diff --git a/config/locales/en.yml b/config/locales/en.yml index d30ea9c..5d93a3e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -343,6 +343,9 @@ en: - - ico-soil - ico-water + - ico-storage + - ico-transport + - ico-marketplace sessions: login: Log in diff --git a/config/question_scores.json b/config/question_scores.json new file mode 100644 index 0000000..f8aa61d --- /dev/null +++ b/config/question_scores.json @@ -0,0 +1,168 @@ +{ + "stages": [ + { + "id": "soil", + "answers": [ + { + "type": "good_answer", + "overall": 2, + "impact": { "food_waste": 1, "emissions": 1, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": 0, "emissions": -2, "income": 0 } + }, + { + "type": "chance", + "outcomes": [ + { + "type": "good_answer", + "overall": 1, + "impact": { "food_waste": 1, "emissions": 2, "income": 0 } + }, + { + "type": "good_answer", + "overall": 1, + "impact": { "food_waste": 1, "emissions": 1, "income": 0 } + } + ] + } + ] + }, + { + "id": "water", + "answers": [ + { + "type": "good_answer", + "overall": 2, + "impact": { "food_waste": 1, "emissions": 1, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": -1, "emissions": -1, "income": 0 } + }, + { + "type": "chance", + "outcomes": [ + { + "type": "good_answer", + "overall": 1, + "impact": { "food_waste": 1, "emissions": 2, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": -3, "emissions": -1, "income": 0 }, + "early_exit": true + } + ] + } + ] + }, + { + "id": "storage", + "answers": [ + { + "type": "good_answer", + "overall": 2, + "impact": { "food_waste": 2, "emissions": 1, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": -2, "emissions": 0, "income": 0 } + }, + { + "type": "chance", + "outcomes": [ + { + "type": "good_answer", + "overall": 1, + "impact": { "food_waste": 2, "emissions": 0, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": -3, "emissions": -1, "income": 0 }, + "early_exit": true + } + ] + } + ] + }, + { + "id": "transport", + "answers": [ + { + "type": "good_answer", + "overall": 2, + "impact": { "food_waste": 2, "emissions": -1, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": 0, "emissions": -2, "income": 0 } + }, + { + "type": "chance", + "outcomes": [ + { + "type": "good_answer", + "overall": 1, + "impact": { "food_waste": 1, "emissions": 2, "income": 0 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": -3, "emissions": -1, "income": 0 }, + "early_exit": true + } + ] + } + ] + }, + { + "id": "market", + "answers": [ + { + "type": "good_answer", + "overall": 2, + "impact": { "food_waste": 1, "emissions": 2, "income": 2 } + }, + { + "type": "bad_answer", + "overall": 2, + "impact": { "food_waste": 1, "emissions": 2, "income": 2 } + }, + { + "type": "bad_answer", + "overall": 1, + "impact": { "food_waste": 1, "emissions": 1, "income": 1 } + }, + { + "type": "bad_answer", + "overall": 0, + "impact": { "food_waste": 1, "emissions": 0, "income": -1 } + } + ] + } + ], + "last_save": { + "answers": [ + { + "type": "good_answer", + "result_type": "compost", + "overall": 0, + "impact": { "food_waste": 1, "emissions": 1, "income": 0 } + }, + { + "type": "bad_answer", + "result_type": "landfill", + "overall": 0, + "impact": { "food_waste": -2, "emissions": -3, "income": 0 } + } + ] + } +} diff --git a/db/migrate/20260605000000_add_scores_to_players.rb b/db/migrate/20260605000000_add_scores_to_players.rb new file mode 100644 index 0000000..cf0ba2a --- /dev/null +++ b/db/migrate/20260605000000_add_scores_to_players.rb @@ -0,0 +1,5 @@ +class AddScoresToPlayers < ActiveRecord::Migration[8.1] + def change + add_column :players, :scores, :jsonb, default: {}, null: false + end +end diff --git a/db/migrate/20260605000001_add_is_done_to_players.rb b/db/migrate/20260605000001_add_is_done_to_players.rb new file mode 100644 index 0000000..f7e8b5f --- /dev/null +++ b/db/migrate/20260605000001_add_is_done_to_players.rb @@ -0,0 +1,7 @@ +class AddIsDoneToPlayers < ActiveRecord::Migration[8.1] + def change + add_column :players, :is_done, :boolean, default: false, null: false + add_index :players, :is_done + add_index :players, :locale + end +end diff --git a/db/schema.rb b/db/schema.rb index f727eeb..a781d03 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_06_03_141335) do +ActiveRecord::Schema[8.1].define(version: 2026_06_05_000001) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -116,10 +116,14 @@ ActiveRecord::Schema[8.1].define(version: 2026_06_03_141335) do create_table "players", force: :cascade do |t| t.datetime "created_at", null: false + t.boolean "is_done", default: false, null: false t.string "locale", null: false t.jsonb "progress", default: {}, null: false t.integer "score", default: 0, null: false + t.jsonb "scores", default: {}, null: false t.datetime "updated_at", null: false + t.index ["is_done"], name: "index_players_on_is_done" + t.index ["locale"], name: "index_players_on_locale" end create_table "quiz_results", force: :cascade do |t|