DEV Community

shawnhuangfernandes
shawnhuangfernandes

Posted on • Updated on

Booting Up: Bootcamp Week 9 - Project Week & Teamwork

Project Week!

If You'd like to read up on Week 10, click here.

This past week was project week, but they shook it up on us.

We were assigned PARTNERS!

alt text

My partner was Alfonso (Fonzie) Pruneda and we started our project-ing by going over the project guidelines:

  • Must be a HTML/CSS/JS frontend with a Rails API backend. All interactions between the client and the server should be handled asynchronously (AJAX) and use JSON as the communication format.

  • Backend must render a resource with at least one has-many relationship.

  • The backend and frontend must collaborate to demonstrate Read, Create, and either Update or Delete for at least two of the models. The results of each action should be displayed to the user without a page refresh.

So, we were building a single page application (SPA). Fonzie and I started the week by brainstorming an idea for our project that we could have fun with and practice our web dev chops!

We both enjoy games, and wanted to make a game that would make the rest of our cohort laugh. We decided it might be fun to make a SPA where our peers could fight each other.

We called it Conflict Manager!

alt text

Conflict Manager - Planning

I am big on planning, so we spent the first day writing out a plan for what the user story would be, what all the "views" would look like, and what models we'd need to set up.

Here's a picture of part of our plan:

Alt Text

We digi-fied it so it'd look a bit cleaner and we'd both have access to it:

Alt Text

Model Overview

We had three models in our game:

User: A user had an 'account' which they could select. We were told explicitly to not implement authentication because we hadn't learned how to do authentication with Javascript, so there were no passwords. A user's progress in the game (their unlocked characters) would be saved when they logged back in.

Character: These were the selectable characters that would battle each other. They held data like health, damage, agility, animation_url, icon_url etc.

Blood Oath: This was the joiner class for a User and a Character. We called it a blood oath as a reference to one of our previous labs. When a User unlocked a Character, a Blood Oath would be formed between the two.

NOTE: We had a background model, but we decided later that since we didn't have a lot of backgrounds (and they really only had one attribute: their image_url), we could store them in our frontend JS file.

Setting Up the Backend

Per the requirements, we were setting up a backend using rails (I have provided samples of each component below). We had to build the following:

Routes

What data will our front end need to render fun images and text at the right time?

Rails.application.routes.draw do
  get '/characters', to: 'characters#index'
  post '/characters', to: 'characters#attack'
  get '/characters/:id', to: 'characters#show'


  post '/users', to: 'users#create'
  get '/users', to: 'users#index'
  get '/users/:id', to: 'users#show'
  patch '/users/:id', to: 'users#update'
end

Controllers

When the front end makes a request, what stuff in our database will we need to create/update?

class CharactersController < ApplicationController
    def index
        characters = Character.all
        Character.reset_health
        render json: characters
    end

    def show
        character = Character.find(params[:id])
        render json: character
    end

    def attack
        attacker = Character.find(params[:attacker_id]) # get the attacker
        defender = Character.find(params[:defender_id]) # get the defender
        defender.dodgeFlag = params[:defender_dodge] # check if the defender is trying to dodge
        damage_taken = defender.take_damage(attacker.attack) # make the defender take damage (or dodge)
        defender.save # save the new defender's health
        render json: # send back json information back to the fight page
        {
            attacker_defeated_gif: attacker.defeated_gif, # attacker's defeated gif
            defender_health: defender.health, # defender's new health
            defender_id: defender.id, # defender's id
            damage_dealt: damage_taken, # damage dealt
            defender_defeated_gif: defender.defeated_gif # defender's defeated gif
        }
    end

    def random_enemy
        randomEnemy = Character.all.sample
        render json: randomEnemy
    end
end

Models

How do we need to set up relationships between the models, and does each model need any special functionality?

class Character < ApplicationRecord
    has_many :bloodoaths
    has_many :users, through: :bloodoaths

    MAX_HEALTH = 100
    DODGE_RANGE_UPPER = 20
    DODGE_RANGE_LOWER = 17
    DODGE_ACTIVATE_VALUE = 3

    #reduces characters health and returns damage taken
    def take_damage(damage)
        damage_taken = 0
        dodge_roll = rand((self.agility + ((self.dodgeFlag && 1 || 0) * DODGE_ACTIVATE_VALUE)) .. DODGE_RANGE_UPPER)
        if !dodge_roll.between?(DODGE_RANGE_LOWER, DODGE_RANGE_UPPER)
            damage_taken = damage - (damage * (self.defense / 100.0)).to_i
            self.health -= damage_taken
            self.dodgeFlag = false;
            self.save
        end
        damage_taken
    end
    #returns a random character
    def self.get_opponent
        Character.all.sample
    end
    #resets all characters health
    def self.reset_health
        Character.all.each do |character|
            character.health = MAX_HEALTH
            character.save
        end
    end

end

Front-End (Views)

This was the new stuff we learned over the past two weeks. In order to build a single page application with views that were very different from each other, we would have to do some pretty serious DOM manipulation.

Fonzie and I decided that we'd take a more OOP approach to building our front end. We would:

  • Create a class for each and every page
  • Create three static methods that would build the top of each page, the middle of the page, and the bottom of the page
  • Make event listeners for every button that would query the back end through fetches
  • Create a method that clears the contents of the current page for transitions

Here is a sample:

/*-----------------------------------------------------------------------------*/
/*---------------------- Renders the Character selection page ---------------- */
class CharacterSelection{
    static renderPage(bodyElement){
        clearElements(bodyElement)
        let chooseTop = document.createElement('div');
        chooseTop.className = "choose-top";
        let chooseMid = document.createElement('div');
        chooseMid.className = "choose-mid";
        let chooseBottom = document.createElement('div');
        chooseBottom.className = "choose-bottom";
        this.renderTop(chooseTop);
        bodyElement.appendChild(chooseTop);
        this.renderMid(chooseMid);
        bodyElement.appendChild(chooseMid);
        this.renderBottom(chooseBottom);
        bodyElement.appendChild(chooseBottom)
    }

    static renderTop(chooseTop)
    {
        let titleText = document.createElement('p');
        titleText.textContent = "CHOOSE YOUR CHARACTER"
        chooseTop.appendChild(titleText);
    }

    static renderMid(chooseMid)
    {
        let iconContainer = document.createElement('div');
        let dynamicDescription = document.createElement('p');
        let iconName = document.createElement('p');
        iconContainer.id = "images";
        dynamicDescription.id = "description";
        iconName.id = "icon-name";

        chooseMid.appendChild(iconContainer);
        chooseMid.appendChild(iconName);
        chooseMid.appendChild(dynamicDescription);


        fetch(`http://localhost:3000/characters`)
            .then(resp => resp.json())
            .then(json => {
                for (let i = 0; i < json.length; i++)
                {
                    let icon = document.createElement("img"); //input element, text
                    icon.setAttribute('class',`fighter-icon`);
                    icon.setAttribute('id',json[i].id);
                    let iconAudio = document.createElement('audio');
                    iconAudio.volume = .5;
                    if(parseInt(localStorage.getItem("userWins")) >= json[i].wins_required)
                    {
                        console.log("it hits", json[i].win_required)
                        icon.src = json[i].icon_img
                        iconAudio.src = json[i].icon_audio_url
                        icon.addEventListener('click', function(e){
                            localStorage.setItem("fighterId", json[i].id)
                            dynamicDescription.textContent = json[i].description;
                            iconName.textContent = json[i].name;
                            iconAudio.play();
                        });
                    }
                    else
                    {
                        icon.src = 'https://i.imgur.com/VNYNDVn.png'
                    }
                    iconContainer.appendChild(icon);
                }
        })

    }

    static renderBottom(chooseBottom)
    {
        let continueButton = document.createElement('button')
        continueButton.textContent = "Continue";
        continueButton.id = "choose-continue";
        chooseBottom.appendChild(continueButton);

        continueButton.addEventListener('click', function(e){
            buttonClickAudio.play();
            ChooseBackground.renderPage(bodyElement);
        })
    }
}

Our front-end code generated HTML, but it wasn't styled. This meant that the content wasn't properly aligned, fonts weren't the right size or type, and general colors and image formatting wasn't in place. We used CSS to select the generated HTML by class, id and tag.

We used CSS Grid and CSS Flexbox for the alignment, and we used the Semantic UI for button styling.

Here's a sample from our CSS file:

@import url(https://fonts.googleapis.com/css?family=Anton);

body
{
    background-color: gray;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr 8fr 1fr;
}

.root-top
{
    display: grid;
    font-family: 'Anton', sans-serif;
    line-height: 1;
    font-size: 3em;
    color: #000;
    justify-content: center;
    align-content: center;
}

.root-mid
{
    display: flex;
    flex-direction: column;
    align-self: center;
}

The Effort

Fonzie and I worked on each part of the web application together. We did this on purpose so that we both got the same exposure to each part of the development process (both backend and frontend). We spent pretty much every day of project week from 8am-8pm working on the project. It was the most intense team work I've been involved with, ever. We both learned from each other and also kept each other on track!

alt text

The Final Product

We removed parts of our initial scope to allow us to dive deeper into parts of the application we really enjoyed. We learned some really important skills on our way to the finish line, such as:

  • Learning how to properly scope a project
  • How to review code written by others
  • Learning to store media on Amazon Web Services S3 (we had gifs, images, and sounds)
  • Learning to incorporate Semantic UI elements to help with styling
  • How to incorporate gifs and videos into web applications
  • Learning how to incorporate sound (and the hard truth about trying to autoplay audio with modern browsers... you can't).
  • and more!

Here's some images from our presentation about our project:

Alt Text

Alt Text

If you are curious about the project, feel free to grab it on GIThub!

Here are the links:
Front-End: https://github.com/915fonzie/conflict-manager-frontend
Back-End: https://github.com/915fonzie/conflict-manager-backend
Presentation: https://docs.google.com/presentation/d/1Mj8NvnZwx21PDR9XutrxVbiTS7cTFROrYOBXnDenHlM/edit?usp=sharing

Hope that this gives you a little bit of insight on what goes on in a bootcamp!

Good luck in your own endeavors!
Shawn

Top comments (0)