diff --git a/services/app/assets/css/app.scss b/services/app/assets/css/app.scss index 0003dee61..35103bdf8 100644 --- a/services/app/assets/css/app.scss +++ b/services/app/assets/css/app.scss @@ -82,3 +82,15 @@ a { background: rgba(0, 0, 0, 0); padding: 0; } +.polyglot { + width: 50px; + height: 50px; + background: url('/assets/images/achievements/polyglot.png'); + background-size: cover; + padding: 5px; +} +.profile .polyglot{ + width: 200px; + height: 200px; + padding: 12px; +} diff --git a/services/app/assets/js/widgets/components/UserAchievements.jsx b/services/app/assets/js/widgets/components/UserAchievements.jsx new file mode 100644 index 000000000..5a44bd80c --- /dev/null +++ b/services/app/assets/js/widgets/components/UserAchievements.jsx @@ -0,0 +1,30 @@ +import React from 'react'; +import _ from 'lodash'; + +const renderPolyglotAchievement = (languages) => { + return ( +
+
+ {languages.map(el => )} +
+
+ ); +}; + +const UserAchievements = (achievements) => { + if (!_.isEmpty(achievements)) { + return ( +
+ {achievements.map((el) => { + const [name, languages] = el.split('?'); + if (name === 'win_games_with') { + return renderPolyglotAchievement(languages.split('_')); + } + return {el}}) + } +
+ ); + } + return ''; +}; +export default UserAchievements; diff --git a/services/app/assets/js/widgets/components/UserStats.jsx b/services/app/assets/js/widgets/components/UserStats.jsx index d878e884a..ff1907257 100644 --- a/services/app/assets/js/widgets/components/UserStats.jsx +++ b/services/app/assets/js/widgets/components/UserStats.jsx @@ -1,6 +1,7 @@ import React from 'react'; import _ from 'lodash'; import Loading from './Loading'; +import UserAchievements from './UserAchievements'; const UserStats = ({ data }) => { if (data) { @@ -23,15 +24,7 @@ const UserStats = ({ data }) => { {achivementsTitle} - {!_.isEmpty(achievements) && ( - - )} + {UserAchievements(achievements)} ); } diff --git a/services/app/assets/static/images/achievements/clojure.png b/services/app/assets/static/images/achievements/clojure.png new file mode 100644 index 000000000..dc29a8293 Binary files /dev/null and b/services/app/assets/static/images/achievements/clojure.png differ diff --git a/services/app/assets/static/images/achievements/elixir.png b/services/app/assets/static/images/achievements/elixir.png new file mode 100644 index 000000000..f7d809ccd Binary files /dev/null and b/services/app/assets/static/images/achievements/elixir.png differ diff --git a/services/app/assets/static/images/achievements/haskell.png b/services/app/assets/static/images/achievements/haskell.png new file mode 100644 index 000000000..b158bec7e Binary files /dev/null and b/services/app/assets/static/images/achievements/haskell.png differ diff --git a/services/app/assets/static/images/achievements/js.png b/services/app/assets/static/images/achievements/js.png new file mode 100644 index 000000000..890eb593c Binary files /dev/null and b/services/app/assets/static/images/achievements/js.png differ diff --git a/services/app/assets/static/images/achievements/perl.png b/services/app/assets/static/images/achievements/perl.png new file mode 100644 index 000000000..84ecbcc9f Binary files /dev/null and b/services/app/assets/static/images/achievements/perl.png differ diff --git a/services/app/assets/static/images/achievements/php.png b/services/app/assets/static/images/achievements/php.png new file mode 100644 index 000000000..5e8b57df7 Binary files /dev/null and b/services/app/assets/static/images/achievements/php.png differ diff --git a/services/app/assets/static/images/achievements/polyglot.png b/services/app/assets/static/images/achievements/polyglot.png new file mode 100644 index 000000000..10b92dc7d Binary files /dev/null and b/services/app/assets/static/images/achievements/polyglot.png differ diff --git a/services/app/assets/static/images/achievements/python.png b/services/app/assets/static/images/achievements/python.png new file mode 100644 index 000000000..3f6442c74 Binary files /dev/null and b/services/app/assets/static/images/achievements/python.png differ diff --git a/services/app/assets/static/images/achievements/ruby.png b/services/app/assets/static/images/achievements/ruby.png new file mode 100644 index 000000000..0da08e29e Binary files /dev/null and b/services/app/assets/static/images/achievements/ruby.png differ diff --git a/services/app/lib/codebattle/game_process/engine/base.ex b/services/app/lib/codebattle/game_process/engine/base.ex index 1a26142b4..cf4913007 100644 --- a/services/app/lib/codebattle/game_process/engine/base.ex +++ b/services/app/lib/codebattle/game_process/engine/base.ex @@ -54,7 +54,7 @@ defmodule Codebattle.GameProcess.Engine.Base do creator: winner.creator, rating: new_winner_rating, rating_diff: winner_rating_diff, - lang: Map.get(winner, :lang, nil) + lang: Map.get(winner, :editor_lang) }) create_user_game!(%{ @@ -64,7 +64,7 @@ defmodule Codebattle.GameProcess.Engine.Base do creator: loser.creator, rating: new_loser_rating, rating_diff: loser_rating_diff, - lang: Map.get(loser, :lang, nil) + lang: Map.get(loser, :editor_lang) }) winner_achievements = Achievements.recalculate_achievements(winner) diff --git a/services/app/lib/codebattle/game_process/engine/standard.ex b/services/app/lib/codebattle/game_process/engine/standard.ex index 10523ee07..df6b7313d 100644 --- a/services/app/lib/codebattle/game_process/engine/standard.ex +++ b/services/app/lib/codebattle/game_process/engine/standard.ex @@ -103,7 +103,6 @@ defmodule Codebattle.GameProcess.Engine.Standard do def handle_give_up(game_id, loser, fsm) do winner = FsmHelpers.get_opponent(fsm, loser.id) - store_game_result_async!(fsm, {winner, "won"}, {loser, "gave_up"}) ActiveGames.terminate_game(game_id) end diff --git a/services/app/lib/codebattle/user/achievements.ex b/services/app/lib/codebattle/user/achievements.ex index 3ce88e604..6295d577a 100644 --- a/services/app/lib/codebattle/user/achievements.ex +++ b/services/app/lib/codebattle/user/achievements.ex @@ -10,6 +10,7 @@ defmodule Codebattle.User.Achievements do def recalculate_achievements(user) do {user.achievements, user} |> count_played_games + |> count_wins |> elem(0) end @@ -62,4 +63,33 @@ defmodule Codebattle.User.Achievements do {achievements, user} end end + + def count_wins({achievements, user}) do + query = + from(ug in UserGame, + select: { + ug.lang, + count(ug.id) + }, + where: ug.user_id == ^user.id and ug.result == "won" and not is_nil(ug.lang), + group_by: ug.lang + ) + + languages = Repo.all(query) |> Enum.into(%{}) |> Map.keys() + exist_achievement = Enum.filter(achievements, fn x -> String.contains?(x, "win_games_with") end) |> Enum.at(0) + new_achievement = "win_games_with?#{Enum.join(languages, "_")}" + cond do + Enum.count(languages) >= 3 -> + if (new_achievement !== exist_achievement) do + new_list = List.delete(achievements, exist_achievement) + {new_list ++ [new_achievement], user} + else + {achievements, user} + end + true -> + {achievements, user} + end + + + end end diff --git a/services/app/lib/codebattle_web/templates/user/show.html.slim b/services/app/lib/codebattle_web/templates/user/show.html.slim index dd577ff25..99f8c9948 100644 --- a/services/app/lib/codebattle_web/templates/user/show.html.slim +++ b/services/app/lib/codebattle_web/templates/user/show.html.slim @@ -35,8 +35,16 @@ .col-12.text-center.mt-4 h2.mt-1.mb-0 | Achievements: - ul.list-inline - = for achievement <- @user.achievements do - li.list-inline-item - img.img-fluid.rounded[alt="#{achievement}" title="#{achievement}" src="/assets/images/achievements/#{achievement}.png" width="200" height="200"] + div.d-flex.justify-content-center.profile + = Enum.map @user.achievements, fn achievement -> + - condition = String.contains?(achievement, "win_games_with") + = if condition do + div.polyglot[title="#{achievement}"] + div.d-flex.h-75.flex-wrap.align-items-center.justify-content-around + - langs = String.split(achievement, "?") |> Enum.at(1) |> String.split("_") + = Enum.map langs, fn lang -> + img.[alt="#{lang}" title="#{lang}" src="/assets/images/achievements/#{lang}.png" width="38" height="38"] + - else + img.mr-1[alt="#{achievement}" title="#{achievement}" src="/assets/images/achievements/#{achievement}.png" width="200" height="200"] + \ No newline at end of file diff --git a/services/app/test/codebattle_web/integration/recalculate_achivements_test.exs b/services/app/test/codebattle_web/integration/recalculate_achivements_test.exs index c1c5cdf8f..28899c9bd 100644 --- a/services/app/test/codebattle_web/integration/recalculate_achivements_test.exs +++ b/services/app/test/codebattle_web/integration/recalculate_achivements_test.exs @@ -56,7 +56,7 @@ defmodule RecalculateAchivementsTest do conn = conn1 |> get(page_path(conn1, :index)) - |> post(game_path(conn1, :create, level: "easy")) + |> post(game_path(conn1, :create, level: "easy", lang: "js")) game_id = game_id_from_conn(conn) @@ -66,7 +66,48 @@ defmodule RecalculateAchivementsTest do # Second player join game post(conn2, game_path(conn2, :join, game_id)) {:ok, _response, socket2} = subscribe_and_join(socket2, GameChannel, game_topic) + # First player won + editor_text1 = "Hello world1!" + Phoenix.ChannelTest.push(socket1, "check_result", %{editor_text: editor_text1, lang: "js"}) + :timer.sleep(100) + fsm = Server.fsm(game_id) + + user = Repo.get(User, user1.id) + assert user.achievements == ["played_ten_games", "win_games_with?js_php_ruby"] + end + end + + test "calculate polyglot achievement", %{ + conn1: conn1, + conn2: conn2, + socket1: socket1, + socket2: socket2, + user1: user1, + user2: user2 + } do + with_mocks [ + {Codebattle.CodeCheck.Checker, [], [check: fn _a, _b, _c -> {:ok, "asdf", "asdf"} end]} + ] do + + ["js", "php", "ruby"] + |> Enum.each(fn x -> + insert_list(3, :user_game, %{user: user1, lang: x, result: "won"}) + end) + + # Create game + conn = + conn1 + |> get(page_path(conn1, :index)) + |> post(game_path(conn1, :create, level: "easy", lang: "js")) + game_id = game_id_from_conn(conn) + + game_topic = "game:" <> to_string(game_id) + {:ok, _response, socket1} = subscribe_and_join(socket1, GameChannel, game_topic) + + # Second player join game + post(conn2, game_path(conn2, :join, game_id)) + {:ok, _response, socket2} = subscribe_and_join(socket2, GameChannel, game_topic) # First player won editor_text1 = "Hello world1!" Phoenix.ChannelTest.push(socket1, "check_result", %{editor_text: editor_text1, lang: "js"}) @@ -74,7 +115,7 @@ defmodule RecalculateAchivementsTest do fsm = Server.fsm(game_id) user = Repo.get(User, user1.id) - assert user.achievements == ["played_ten_games"] + assert user.achievements == ["played_ten_games", "win_games_with?js_php_ruby"] end end end diff --git a/services/app/test/codebattle_web/integration/user_achivements_test.ex b/services/app/test/codebattle_web/integration/user_achivements_test.exs similarity index 96% rename from services/app/test/codebattle_web/integration/user_achivements_test.ex rename to services/app/test/codebattle_web/integration/user_achivements_test.exs index 7ddac26b3..c0f7bb361 100644 --- a/services/app/test/codebattle_web/integration/user_achivements_test.ex +++ b/services/app/test/codebattle_web/integration/user_achivements_test.exs @@ -51,7 +51,7 @@ defmodule Codebattle.RandomTaskSelectorTest do conn = conn1 |> get(page_path(conn1, :index)) - |> post(game_path(conn1, :create, level: "easy")) + |> post(game_path(conn1, :create, level: "easy", lang: "js")) game_id = game_id_from_conn(conn)