Git Product home page Git Product logo

lewagon-aoc's Introduction

Le Wagon x Advent of Code

Ruby    3.2.2  
Rails   7.1.3.4

Found a bug? Do not hesitate to open an Issue.
Have a feature request? Let's discuss it on Slack.

Contribute

If you want to help me fix a bug or implement a new requested feature:

  1. Make sure an Issue exists for it
  2. Ask me questions
  3. Fork the project
  4. Code the changes on your fork
  5. Create a Pull Request here from your fork

CI with GitHub Action

Upon Pull Requests (open, push), CI scripts are automatically run:

  • linters (RuboCop, ERBLint)
  • security tools (brakeman, bundler-audit)
  • tests

Your PR should pass the linters, the security checks and the tests to be reviewed.

Run on your machine

  1. Run bin/setup to install dependencies, create and seed the database
  2. Ask me for the credentials key and add it to config/master.key, required for Kitt OAuth
  3. Create a .env root file and add these keys with their appropriate values: AOC_ROOMS, SESSION_COOKIE
  4. Run bin/dev

Required ENV variables

Warning The .env file is used for development purposes only. It is not versioned and never should.

  • AOC_ROOMS is a comma-separated list of private leaderboard IDs that you belong to (e.g. 9999999-a0b1c2d3,7777777-e4f56789)
  • SESSION_COOKIE is your own Advent of Code session cookie (valid ~ 1 month). You need to log in to the platform, then retrieve the value of the session cookie (e.g. 436088a93cbdba07668e76df6d26c0dcb4ef3cbd5728069ffb647678ad38)

Overmind

Note Foreman is the default process manager through the bin/dev command. Overmind is an optional alternative.

Overmind is a process manager for Procfile-based applications like ours, based on tmux. You can install the tool on your machine following these instructions.

Add these lines to your local .env file:

OVERMIND_PORT=3000
OVERMIND_PROCFILE=Procfile.dev

Then, instead of the usual bin/dev, you have to run overmind s.

Use SSL (on macOS)

In short: create an SSL certificate for your localhost, store it in your keychain and run the server using that certificate.

mkcert localhost
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ./localhost.pem # macOS-specific
mv localhost* tmp/ # this is the tmp folder in the project root
bin/dev ssl

Untested on Linux.

Launch webapp on local mobile browser

Because the OAuth will not work on your local IP, you have to bypass authentication by temporarily adding this line, for example in the welcome controller method:

sign_in(User.find_by(github_username: "your_username"))

Then, find the local IP address of the computer you launch the server from (ex: 192.168.1.14) and open the app on your mobile browser from that IP (ex: http://192.168.1.14:3000)

Advent of Code API

On adventofcode.com, a user can create one (and only one) private leaderboard. Up to 200 users can join it using a unique generated code.

A JSON object containing scores can be fetched from a GET request that needs a session cookie to succeed. We store this session cookie in the SESSION_COOKIE environment variable (valid ~ 1 month).

We use multiple private leaderboards to run the platform with more than 200 participants. We store their IDs in the AOC_ROOMS environment variable, comma-separated. One account joins all of them, and we use this account's SESSION_COOKIE.

lewagon-aoc's People

Contributors

pil0u avatar aquaj avatar wjoenn avatar dependabot[bot] avatar ssaunier avatar malex18 avatar

Stargazers

André Menezes avatar Francesca avatar Lucas Grüner avatar Priscila Finkler Innocente avatar Jérôme Tan avatar Jerome Drescig avatar Zakarya KARTTI avatar Diane avatar Francesca Santoriello avatar Sunny Ripert avatar Jules van Rie avatar  avatar Karl Keller avatar Maxime Froment avatar  avatar  avatar  avatar Dareos Khalili avatar JulienErgan avatar Gaëtan Manchon avatar Mickael GASPAR avatar Nikos Agathos avatar Victor Holl avatar Hannah avatar jsphwllng avatar PierreKieffer avatar  avatar  avatar

Watchers

 avatar  avatar

lewagon-aoc's Issues

Store the GitHub username is a separate column

This information might be useful in the future.

After the migration, here is my suggested implementation:

# app/models/user.rb

def self.from_kitt(auth)
  batch_from_oauth = auth.info.last_batch_slug&.gsub(/[^\d]/, "")
  batch = Batch.find_or_create_by(number: batch_from_oauth) if batch_from_oauth.present?

  user = where(provider: auth.provider, uid: auth.uid).first_or_create do |u|
    u.username = auth.info.github_nickname
    u.github_username = auth.info.github_nickname
    u.batch = batch
  end

  user.update(github_username: auth.info.github_nickname)
  user
end

.update triggers an SQL query only if the value is different, thus making the transition smoothly and the information up-to-date with time.
The username can be edited by the user in the app any time, the github_username is retrieved from Kitt.

Communication to-do list

Communication

  • Touch base with Ursula, the new E&C Paris
  • Touch base with French E&Cs
  • Every city in the world is aware of the event (Google Slides)
  • We have a video trailer

Prizes

  • Send a message to Guillaume directly
  • More small prizes for more people (100 t-shirts, 20 virtual escape rooms for squads, 1000€ for a party) => 5700€

Adventofcode.com

  • Get an answer from Eric about larger private leaderboards (it's a no)

Fix the link to join the leaderboard on the AoC platform

On step 2 of the sync process, the click here link is an external form submission that is supposed to make the user join the leaderboard on adventofcode.com.

It was supposed to improve UX, but for some (probably good) reasons, some browsers seem to block that behaviour.

  • Find the root cause
  • Fix

Calendar | Ajouter un indicateur de difficulté des puzzles

Simplest approach is by leveraging timestamps from the top 100 of the past years and determine absolute threshold as an output:

  • 🟢 very easy (< 2 minutes)
  • 🟡 easy (between 2 and 10 minutes)
  • 🟠 hard (between 10 and 20 minutes)
  • 🔴 very hard (> 20 minutes)

Eager load scores Dashboard and Scoreboard

When the scores are retrieved from the API, the JSON is parsed and stored in scores table.

To retrieve information from that table, the ideal scenario is to avoid N+1 queries (:smile:) by properly querying the table in the pages_controller to get nice Hash(es) that can be easily browsed for the stats we want to offer.

  • Scoreboard page
  • Dashboard stats
  • Dashboard calendar

To do:

  1. List all the metrics we want to have
  2. Write down the right Hash structure(s) considering all the stats we want
  3. Build the appropriate queries

Database schema to date:
Database schema

New feature: code sharing

Why

Purpose n°1: allow validators to check the code written by participants and validate rankings
Purpose n°2: make it sharable amongst participants

What

People could share 2 codes for the same challenge:

  • one that corresponds to the immediate resolution and is used for cheating-proof. This one is immutable, not publicly shared
  • one that the user wants to share. This one is publicly available / displayed, editable at any time.

The Validation snippet

"You won't be able to edit or access this code afterwards"
Add a checkbox "Use this code as my Shared snippet as well"

Add an optional "time to solve" declarative field for people to share it. IMHO, as it would always be declarative, we don't need to add a custom timer on our platform and rather let people who want to compute it themselves and let them the opportunity to share it. We could then compute a "Declarative leaderboard".

The Shared snippet

Keep an history of editions?
This snippet is displayed publicly on the platform.

People are allowed to add emoji reactions to the shared snippets:

  • 🎓, for a solution that is exemplary to learn things
  • 🤯, for a solution that blows your mind
  • 👏, for a generic celebration of a solution
  • ♥️, for "Organizers' favorite"

Maybe add a constraint, like only one reaction available per snippet.

For a given challenge, snippets are ordered in 3 virtual blocks:

  1. First appearing block, snippets that were posted in the past 15 minutes
  2. Second appearing block, snippets that were posted between 15 and 60 minutes ago
  3. Third appearing block, all the remaining snippets

Within each block, snippets are ordered by:

  1. number of 🎓 desc
  2. number of total reactions desc
  3. submit time asc

Technicalities

  • We just store the code (= text) as is
  • Don't share Jupyter notebooks (painful to read)

Add Squads

  • Design the table
  • Implement the model and database migration
  • Add validation on name (unique)
  • Auto archive empty squads
  • Add the Create/Join/Leave process in the settings
  • Block join if squad is full
  • Lock squads after December 8 (no join, no leave, no create)

New schema
More info

500 on big batches number

Need to check in the backend the batch input as there is a max value of a 4-bytes integer column in a database.

Not the most important issues but as I found it though I could fix it.

Properly handle multiple AoC rooms

Advent of Code rooms are limited to 200 slots. Some effort was made to anticipate the case where more than 200 people want to join the contest:

  • the use of an environment variable AOC_ROOMS to avoid re-deployment when a new room is required
  • this one variable can handle ∞ rooms

Some work is still required for this feature to be functional:

  • When a new room is added to that variable, the join link on the Dashboard should automatically switch to the new room
    ➡️ Use .last instead of .first for the join link (dashboard.html.erb)
  • Update the refresh sub-task in scores.rake to merge JSONs coming from all the rooms

A little script to retrieve the total number of Alumni by city (with Kitt API)

require "open-uri"

kitt_cookie = "your-kitt-session-cookie"

raw = URI.open("https://kitt.lewagon.com/api/v1/users", "Cookie" => "_kitt2017_=#{kitt_cookie}").read
cities = JSON.parse(raw)["all_places"].map { |h| [h["label"], h["count"]] }.to_h

cities

This is interesting to get a % of participation depending on the "size" of a city.
It's also helpful to adjust the seed:

# db/seeds.rb

# Initialize cities
cities = [
  "Amsterdam",
  "Bali",
  "Barcelona",
  "Beirut",
  "Belo Horizonte",
  "Berlin",
  "Bordeaux",
  "Brasilia",
  "Brussels",
  "Buenos Aires",
  "Cape Town", # not in the API
  "Casablanca",
  "Chengdu",
  "Cologne",
  "Copenhagen",
  "Dubai",
  "Istanbul",
  "Kyoto",
  "Lausanne",
  "Lille",
  "Lima",
  "Lisbon",
  "London",
  "Lyon",
  "Madrid",
  "Malmö",
  "Marseille",
  "Martinique",
  "Mauritius",
  "Medellín",
  "Melbourne",
  "Mexico",
  "Milan",
  "Montréal",
  "Munich",
  "Nantes",
  "Nice",
  "Oslo",
  "Paris",
  "Playa del Carmen", # not in the API
  "Porto",
  "Remote",
  "Rennes",
  "Rio de Janeiro",
  "Santiago",
  "Seine et Marne",
  "Seoul",
  "Shanghai",
  "Shenzhen",
  "Singapore",
  "Stockholm",
  "São Paulo",
  "Tel Aviv",
  "Tokyo",
  "Zurich"
].map do |city|
  { name: city }
end

[Discussion] How to avoid cheating?

We want to encourage people to collaborate, think about problems alone or around a table, but avoid them to copy-paste solutions found elsewhere.

Solution 1 - Reduce cash prize
Solo cheating is driven by high cash prizes. Drastically reducing the incentives would drive cheating down.
My opinion: no more solo cash prize + "group" (tbd) cashprize + symbolic city prize

Solution 2 - Open source participants' solutions
This expensive solution would contribute to two things:

  • cross-validate solutions for top performers and validate rankings
  • share and log code for all the participants

[Discussion] How to improve the scoring system?

How it works currently is explained here: https://aoc.lewagon.community/about

What works great:

  • Any participant earns points (individually) whenever they solve a puzzle

We identified different issues:

  • By nature, groups (batches and cities) have different sizes
  • Batches are mostly very small
  • All scores are based on the time-to-solve, thus time-zone specific
  • In spite of the effort to keep it simple, it requires a 1-page explanation in the Q&A section

Solution 1 - Change batches to groups of fixed size

This requires great changes and raises a number of questions.

  • What size for each group?
  • Can people come and go from a group to another? Should we lock them at a certain point? When?
  • How would it work? A group leader/creator that invites people? Pre-generated groups that people fill?

Solution 2 - Distinguish hardcore competitors vs casual participants

Hardcore competitor: don't care about getting up early during 25 days, they solve puzzles as soon as they're out
Casual participant: want to try to solve the puzzles through their spare time with no intention to hustle

To enter the hardcore competition and appear on this specific leaderboard, individuals would have to check a box on their profile setting for example (disabled by default).

For casual participants, instead of a relative solve time ranking, we grant fixed points based on time frames:

  • If you solve a puzzle (= a part) within 24h, you get 50 points
  • If you solve a puzzle within 48h, you get 30 points
  • If you solve a puzzle anytime before the end of the contest, you get 20 points

The tie breaker would be the median exact solve time for solved puzzles.

Inconsistency between Dashboard and Scoreboard ranks in case of score equality

Currently, all participants have 0 point, thus all with the same rank.

Rank computation is not exactly the same between the dashboard and scoreboard controller methods.
In case of equality, this creates inconsistencies.

Solution? Order by score, then by id.

Edit: Even better, order by

  • username for solo ranking
  • batch number for batch ranking
  • city name for city ranking

Add trends in the scoreboards

A green/red arrow with the change in the ranking in the last 24h.
How? Compare 2 data points at 5.55am Paris Time for example and display the "+2" or "-1" or "=" signs next to each line.

Bonus: we can make a graph out of it.

Remove `CONTRIBUTORS` and add `ORGANIZER`

We tried some incentives to make people contribute to the project, did not really catch.

  • Change the CONTRIBUTORS constant in app/models/user.rb to ORGANIZERS and remove ids
  • Change the parrot gif to the aoc gif

Statistics we want

Statistics

All statistics are displayed, thus visible on the Whimsical link (private).

Additional statistics, low prio

  • Declarative time to solve
  • Number of top 25% / 10% / 5% / 1%
  • A map of the cities with their scores
  • Graph of city scores over time

The website is not responsive on mobile

The page is kind of broken on mobile. The project is using Tailwind CSS.

I won't fix this, unless 25%+ of the participants upvote this issue.
However, anyone can take the issue to solve it 👍

[Discussion] Routes, models and architecture

Routes

Current state:

                      Prefix Verb     URI Pattern                         Controller#Action
user_kitt_omniauth_authorize GET|POST /users/auth/kitt(.:format)          users/omniauth_callbacks#passthru
 user_kitt_omniauth_callback GET|POST /users/auth/kitt/callback(.:format) users/omniauth_callbacks#kitt
        destroy_user_session DELETE   /sign_out(.:format)                 devise/sessions#destroy
                 unauth_root GET      /                                   pages#home
                        root GET      /                                   pages#dashboard
                       about GET      /about(.:format)                    pages#about
                  scoreboard GET      /scoreboard(.:format)               pages#scoreboard
                    settings GET      /settings(.:format)                 users#edit
                             PATCH    /settings(.:format)                 users#update
                       stats GET      /stats(.:format)                    pages#stats
                  stats_user GET      /stats/users/:id(.:format)          stats/users#show
                 stats_batch GET      /stats/batches/:number(.:format)    stats/batches#show
                  stats_city GET      /stats/cities/:slug(.:format)       stats/cities#show
                      status GET      /status(.:format)                   pages#status

New routes are defined on the Whimsical.

Models

Current architecture can be found on Kitt.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.