├── public ├── logo.png ├── favicon.ico ├── assets │ ├── ana-portrait.png │ ├── d-va-portrait.png │ ├── genji-portrait.png │ ├── hanzo-portrait.png │ ├── lucio-portrait.png │ ├── mei-portrait.png │ ├── mercy-portrait.png │ ├── orisa-portrait.png │ ├── zarya-portrait.png │ ├── bastion-portrait.png │ ├── junkrat-portrait.png │ ├── mccree-portrait.png │ ├── pharah-portrait.png │ ├── reaper-portrait.png │ ├── roadhog-portrait.png │ ├── sombra-portrait.png │ ├── tracer-portrait.png │ ├── winston-portrait.png │ ├── reinhardt-portrait.png │ ├── soldier-76-portrait.png │ ├── symmetra-portrait.png │ ├── torbjorn-portrait.png │ ├── widowmaker-portrait.png │ └── zenyatta-portrait.png └── index.html ├── src ├── styles │ ├── HeroSet.css │ ├── TeamSelect.css │ ├── HoverInfo.css │ ├── TeamStats.css │ ├── TeamRoster.css │ ├── HeroSlot.css │ └── HeroButton.css ├── App.test.js ├── index.css ├── components │ ├── HeroSlot.jsx │ ├── HoverInfo.jsx │ ├── TeamRoster.jsx │ ├── HeroButton.jsx │ ├── HeroSet.jsx │ ├── TeamSelect.jsx │ └── TeamStats.jsx ├── index.js ├── App.css ├── reducer.js ├── events.js ├── App.jsx ├── core.js └── heroes.js ├── README.md ├── .gitignore └── package.json /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/favicon.ico -------------------------------------------------------------------------------- /public/assets/ana-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/ana-portrait.png -------------------------------------------------------------------------------- /public/assets/d-va-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/d-va-portrait.png -------------------------------------------------------------------------------- /public/assets/genji-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/genji-portrait.png -------------------------------------------------------------------------------- /public/assets/hanzo-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/hanzo-portrait.png -------------------------------------------------------------------------------- /public/assets/lucio-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/lucio-portrait.png -------------------------------------------------------------------------------- /public/assets/mei-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/mei-portrait.png -------------------------------------------------------------------------------- /public/assets/mercy-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/mercy-portrait.png -------------------------------------------------------------------------------- /public/assets/orisa-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/orisa-portrait.png -------------------------------------------------------------------------------- /public/assets/zarya-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/zarya-portrait.png -------------------------------------------------------------------------------- /public/assets/bastion-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/bastion-portrait.png -------------------------------------------------------------------------------- /public/assets/junkrat-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/junkrat-portrait.png -------------------------------------------------------------------------------- /public/assets/mccree-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/mccree-portrait.png -------------------------------------------------------------------------------- /public/assets/pharah-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/pharah-portrait.png -------------------------------------------------------------------------------- /public/assets/reaper-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/reaper-portrait.png -------------------------------------------------------------------------------- /public/assets/roadhog-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/roadhog-portrait.png -------------------------------------------------------------------------------- /public/assets/sombra-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/sombra-portrait.png -------------------------------------------------------------------------------- /public/assets/tracer-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/tracer-portrait.png -------------------------------------------------------------------------------- /public/assets/winston-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/winston-portrait.png -------------------------------------------------------------------------------- /public/assets/reinhardt-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/reinhardt-portrait.png -------------------------------------------------------------------------------- /public/assets/soldier-76-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/soldier-76-portrait.png -------------------------------------------------------------------------------- /public/assets/symmetra-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/symmetra-portrait.png -------------------------------------------------------------------------------- /public/assets/torbjorn-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/torbjorn-portrait.png -------------------------------------------------------------------------------- /public/assets/widowmaker-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/widowmaker-portrait.png -------------------------------------------------------------------------------- /public/assets/zenyatta-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/zenyatta-portrait.png -------------------------------------------------------------------------------- /src/styles/HeroSet.css: -------------------------------------------------------------------------------- 1 | .hero-set h2 { 2 | text-align: center; 3 | } 4 | .hero-set > span { 5 | width: 100%; 6 | height: 2px; 7 | opacity: .2; 8 | background: #fff; 9 | } 10 | -------------------------------------------------------------------------------- /src/styles/TeamSelect.css: -------------------------------------------------------------------------------- 1 | .TeamSelect { 2 | display: flex; 3 | justify-content: space-around; 4 | } 5 | .stat-bar { 6 | display: inline-block; 7 | transition: 0.5s; 8 | height: 1.2em; 9 | } 10 | -------------------------------------------------------------------------------- /src/styles/HoverInfo.css: -------------------------------------------------------------------------------- 1 | .hover-info { 2 | border: 1px solid black; 3 | position: absolute; 4 | background-color: lightgrey; 5 | border-radius: 10px; 6 | padding: 1em; 7 | pointer-events: none; 8 | } 9 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/styles/TeamStats.css: -------------------------------------------------------------------------------- 1 | .TeamStats h2 { 2 | text-align: center; 3 | } 4 | .left-column { 5 | width: 10%; 6 | } 7 | .center-column { 8 | width: 50%; 9 | } 10 | .right-column { 11 | width: 25%; 12 | } 13 | table td:nth-child(1) { 14 | text-align: right; 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Overcomp](https://captainstack.github.io/overcomp/) 2 | A team composition tool for Overwatch built in ReactJS/Redux and bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). App prototype hosted on [GitHub Pages](https://captainstack.github.io/overcomp/). 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 1em; 3 | padding-left: 2em; 4 | padding-right: 2em; 5 | font-family: sans-serif; 6 | background-color: #8b99a7; 7 | max-width: 1800px; 8 | margin-left: auto; 9 | margin-right: auto; 10 | } 11 | .App { 12 | display: flex; 13 | flex-direction: column; 14 | flex-wrap: wrap; 15 | } 16 | -------------------------------------------------------------------------------- /src/styles/TeamRoster.css: -------------------------------------------------------------------------------- 1 | #hero-slots { 2 | display: flex; 3 | flex-wrap: wrap; 4 | flex-direction: row; 5 | justify-content: center; 6 | align-items: center; 7 | width: 35%; 8 | min-width: 380px; 9 | } 10 | #team-roster { 11 | display: flex; 12 | flex-wrap: wrap; 13 | justify-content: space-around; 14 | } 15 | .TeamStats { 16 | width: 40%; 17 | min-width: 575px; 18 | } 19 | @media (max-width: 720px) { 20 | .TeamStats { 21 | width: 100%; 22 | } 23 | #hero-slots { 24 | width: 100%; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/HeroSlot.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/HeroSlot.css'; 3 | 4 | const HeroSlot = ({hero, clickAction}) => { 5 | let filled_class = hero.name !== "Empty" ? 'selected' : 'unselected'; 6 | let portrait = hero.portrait ? portrait : null; 7 | return ( 8 |
9 |
10 | {portrait} 11 |
12 |

{hero.name}

13 |
14 | ); 15 | }; 16 | 17 | export default HeroSlot; 18 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | import { createStore } from 'redux'; 6 | import reducer from './reducer'; 7 | import {trackMouse} from './events'; 8 | 9 | document.addEventListener('DOMContentLoaded', () => { 10 | document.querySelector('body').addEventListener('mousemove', (e) => { 11 | trackMouse(e); 12 | }); 13 | }, false); 14 | 15 | export const store = createStore(reducer); 16 | 17 | const render = () => ReactDOM.render(, document.getElementById('root')); 18 | render(); 19 | store.subscribe(render); 20 | -------------------------------------------------------------------------------- /src/components/HoverInfo.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/HoverInfo.css'; 3 | 4 | const HoverInfo = ({hero, left, top}) => { 5 | return ( 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
{hero.name}
Hitpoints:{hero.hitpoints}
Armor:{hero.armor}
Shields:{hero.shields}
Barriers:{hero.barrier}
14 |
15 | ); 16 | }; 17 | 18 | export default HoverInfo; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "overcomp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "immutable": "^3.8.1", 7 | "react": "15.4.2", 8 | "react-dom": "15.4.2", 9 | "redux": "^3.6.0" 10 | }, 11 | "devDependencies": { 12 | "gh-pages": "^0.12.0", 13 | "react-scripts": "0.9.3" 14 | }, 15 | "homepage": "https://captainstack.github.io/overcomp", 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "predeploy": "npm run build", 20 | "deploy": "gh-pages -d build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/TeamRoster.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeroSlot from './HeroSlot'; 3 | import TeamStats from './TeamStats'; 4 | import '../styles/TeamRoster.css'; 5 | import { slotClick } from '../events'; 6 | 7 | const TeamRoster = ({selected_heroes, clickAction}) => { 8 | let hero_slots = selected_heroes.map((selected_hero) => 9 | ); 10 | return ( 11 |
12 |
{hero_slots}
13 | 14 |
15 | ); 16 | }; 17 | 18 | export default TeamRoster; 19 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .row { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: wrap; 5 | justify-content: center; 6 | align-items: top; 7 | } 8 | #logo { 9 | margin-left: auto; 10 | margin-right: auto; 11 | width: 25em; 12 | margin-bottom: 2em; 13 | } 14 | footer { 15 | display: flex; 16 | justify-content: space-between; 17 | font-size: 0.75em; 18 | border-top: 1px solid rgba(51, 51, 51, 0.5); 19 | margin-top: 1em; 20 | padding-top: 0.5em; 21 | } 22 | footer a { 23 | text-decoration: none; 24 | color: rgba(51, 51, 51, 1); 25 | } 26 | footer a:hover { 27 | color: rgba(51, 51, 51, 0.6); 28 | } 29 | @media (max-width: 500px) { 30 | #logo { 31 | width: 100%; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/styles/HeroSlot.css: -------------------------------------------------------------------------------- 1 | .hero-slot { 2 | padding: 0.5em; 3 | display: inline-block; 4 | width: 112px; 5 | } 6 | .hero-slot h3 { 7 | font: italic normal normal 10px Big Noodle Too,impact,sans-serif; 8 | font-size: 1.2rem; 9 | line-height: normal; 10 | letter-spacing: .08rem; 11 | margin: 0; 12 | text-align: center; 13 | } 14 | .slot-portrait { 15 | border: 1px solid black; 16 | width: 112px; 17 | height: 100px; 18 | background-color: rgb(200, 199, 207); 19 | border-radius: 0.4em; 20 | overflow: hidden; 21 | } 22 | .slot-portrait.selected { 23 | background-color: lightblue; 24 | } 25 | .selected.slot-portrait:hover { 26 | cursor: pointer; 27 | border: 1px solid #f9ab4f; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/HeroButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/HeroButton.css'; 3 | 4 | const HeroButton = ({hero, roster, clickAction, hoverAction, hoverExitAction}) => { 5 | let selected_class = roster.findIndex((roster_hero) => hero.id === roster_hero.id) !== -1 ? 'selected' : 'unselected'; 6 | return ( 7 |
8 |
9 |
10 |
11 | {hero.name} 12 |
13 | ); 14 | }; 15 | 16 | export default HeroButton; 17 | -------------------------------------------------------------------------------- /src/components/HeroSet.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeroButton from './HeroButton'; 3 | import '../styles/HeroSet.css'; 4 | import { buttonClick, heroHover, hoverExit } from '../events'; 5 | 6 | const HeroSet = ({heroes_set, selected_heroes, category}) => { 7 | let hero_buttons = heroes_set.map((hero) => 8 | ); 14 | return ( 15 |
16 |

{category}

17 | {hero_buttons} 18 |
19 | ); 20 | }; 21 | 22 | export default HeroSet; 23 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import {add_hero_to_roster, remove_hero_from_roster, 2 | add_hover_hero, remove_hover_hero, update_mouse_position, INITIAL_STATE} from './core'; 3 | 4 | export default function reducer(state = INITIAL_STATE, action) { 5 | switch (action.type) { 6 | case 'ADD_HERO_TO_ROSTER': 7 | return add_hero_to_roster(state, action.hero); 8 | case 'REMOVE_HERO_FROM_ROSTER': 9 | return remove_hero_from_roster(state, action.hero); 10 | case 'ADD_HOVER_HERO': 11 | return add_hover_hero(state, action.hero); 12 | case 'REMOVE_HOVER_HERO': 13 | return remove_hover_hero(state); 14 | case 'UPDATE_MOUSE_POSITION': 15 | return update_mouse_position(state, action.event); 16 | default: 17 | return state; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | import { store } from './index' 2 | 3 | export const buttonClick = clicked_hero => e => { 4 | let roster = store.getState().get('team'); 5 | if (roster.indexOf(clicked_hero) === -1) { 6 | store.dispatch({ type: 'ADD_HERO_TO_ROSTER', hero: clicked_hero }); 7 | } else { 8 | store.dispatch({ type: 'REMOVE_HERO_FROM_ROSTER', hero: clicked_hero }); 9 | } 10 | } 11 | 12 | export const slotClick = slot_id => e => { 13 | let roster = store.getState().get('team'); 14 | let clicked_index = roster.findIndex((slot) => slot.id === slot_id); 15 | if (roster.get(clicked_index).name !== "Empty") { 16 | store.dispatch({ type: 'REMOVE_HERO_FROM_ROSTER', hero: roster.get(clicked_index) }); 17 | } 18 | } 19 | 20 | export const heroHover = hero => e => { 21 | store.dispatch({ type: 'ADD_HOVER_HERO', hero: hero }); 22 | } 23 | 24 | export const hoverExit = hero => e => { 25 | store.dispatch({ type: 'REMOVE_HOVER_HERO' }); 26 | } 27 | 28 | export const trackMouse = e => { 29 | store.dispatch({ type: 'UPDATE_MOUSE_POSITION', event: e }); 30 | } 31 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import TeamRoster from './components/TeamRoster'; 4 | import TeamSelect from './components/TeamSelect'; 5 | import HoverInfo from './components/HoverInfo'; 6 | 7 | const App = ({state}) => { 8 | 9 | let hover_info; 10 | if (state.get('hover_hero')) { 11 | hover_info = 12 | } 13 | 14 | return ( 15 |
16 | 17 | 18 | 19 | {hover_info} 20 | 24 |
25 | ); 26 | }; 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /src/components/TeamSelect.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import HeroSet from './HeroSet'; 3 | import '../styles/TeamSelect.css'; 4 | import { heroes } from '../heroes'; 5 | 6 | const TeamSelect = ({selected_heroes, buttonClick}) => { 7 | let offense_heroes = heroes.filter((hero) => hero.category === 'offense'); 8 | let defense_heroes = heroes.filter((hero) => hero.category === 'defense'); 9 | let tank_heroes = heroes.filter((hero) => hero.category === 'tank'); 10 | let support_heroes = heroes.filter((hero) => hero.category === 'support'); 11 | return ( 12 |
13 | 14 | 15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default TeamSelect; 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 16 | Overcomp 17 | 18 | 19 |
20 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/styles/HeroButton.css: -------------------------------------------------------------------------------- 1 | .hero-button { 2 | cursor: pointer; 3 | display: inline-block; 4 | } 5 | .clipping-mask.selected { 6 | background-color: lightblue; 7 | } 8 | .hero-image { 9 | display: inline-block; 10 | width: calc(100% + 10px); 11 | height: 100%; 12 | background-size: auto 100%; 13 | background-position: 50% 50%; 14 | position: relative; 15 | -webkit-transform: skew(-25rad) translateX(-5px); 16 | -ms-transform: skew(-25rad) translateX(-5px); 17 | transform: skew(-25rad) translateX(-5px); 18 | } 19 | .clipping-mask { 20 | display: block; 21 | position: relative; 22 | width: 70px; 23 | height: 84px; 24 | margin: 2.4px 6px; 25 | border-top: 3px solid #f0edf2; 26 | border-bottom: 3px solid #f0edf2; 27 | border-left: 3px solid transparent; 28 | border-right: 3px solid transparent; 29 | background-color: rgba(51,51,51,.9); 30 | background-clip: padding-box; 31 | overflow: hidden; 32 | border-radius: 7px; 33 | transform: skew(25rad); 34 | } 35 | .clipping-mask:hover { 36 | background-color: #f9ab4f; 37 | border: 3px solid #f0edf2; 38 | } 39 | .hero-portrait-label { 40 | position: relative; 41 | display: inline-block; 42 | top: -4.2px; 43 | margin-left: -10px; 44 | padding: 1px 10px; 45 | font-size: 1.3rem; 46 | color: #333; 47 | white-space: nowrap; 48 | font: italic normal normal 14px Big Noodle Too,impact,sans-serif; 49 | } 50 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | let empty_hero = id => { return {id: id, name: "Empty", hitpoints: 0, armor: 0, shields: 0, barrier: 0, healing: 0, tank: 0} } 4 | export const INITIAL_STATE = Map({ 5 | team: List([empty_hero(25), empty_hero(26), empty_hero(27), empty_hero(28), empty_hero(29), empty_hero(30)]), 6 | hover_hero: null, 7 | mouse: Map({x: null, y: null}) 8 | }); 9 | 10 | export const add_hero_to_roster = (state, hero) => { 11 | let empty_index = state.get('team').findIndex((roster_hero) => roster_hero.name === "Empty"); 12 | if (empty_index !== -1) { 13 | return state.set('team', state.get('team').set(empty_index, hero)); 14 | } else { 15 | return state; 16 | } 17 | } 18 | 19 | export const remove_hero_from_roster = (state, hero) => { 20 | let hero_index = state.get('team').findIndex((roster_hero) => roster_hero === hero); 21 | if (hero_index !== -1) { 22 | return state.set('team', state.get('team').set(hero_index, empty_hero(hero_index + 25))); 23 | } else { 24 | return state; 25 | } 26 | } 27 | 28 | export const add_hover_hero = (state, hero) => { 29 | return state.set('hover_hero', hero); 30 | } 31 | 32 | export const remove_hover_hero = (state) => { 33 | return state.set('hover_hero', null); 34 | } 35 | 36 | export const update_mouse_position = (state, event) => { 37 | let mouse = state.get('mouse'); 38 | let updateX = mouse.set('x', event.pageX); 39 | let updateY = updateX.set('y', event.pageY); 40 | return state.set('mouse', updateY); 41 | } 42 | -------------------------------------------------------------------------------- /src/components/TeamStats.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../styles/TeamStats.css'; 3 | 4 | const TeamStats = ({team}) => { 5 | let hitpoints = team.reduce((acc, hero) => acc + hero.hitpoints, 0); 6 | let armor = team.reduce((acc, hero) => acc + hero.armor, 0); 7 | let shields = team.reduce((acc, hero) => acc + hero.shields, 0); 8 | let healing = team.reduce((acc, hero) => acc + hero.healing, 0); 9 | let tank = team.reduce((acc, hero) => acc + hero.tank, 0); 10 | let barrier = team.reduce((acc, hero) => acc + hero.barrier, 0); 11 | 12 | let healing_message; 13 | let tank_message; 14 | let healing_color; 15 | let tank_color; 16 | 17 | if (healing === 0) { healing_message = 'No healer'; healing_color = 'indianred'; } 18 | else if (healing > 0 && healing < 4) { healing_message = 'Need more healing'; healing_color = 'indianred'; } 19 | else if (healing > 3 && healing < 6) { healing_message = 'Low healing'; healing_color = 'palegoldenrod'; } 20 | else { healing_color = 'darkseagreen'; healing_message = 'Good healing'; } 21 | 22 | if (tank === 0) { tank_message = 'No tank'; tank_color = 'indianred'; } 23 | else if (tank === 1) { tank_message = 'Need more tank'; tank_color = 'indianred'; } 24 | else if (tank < 3) { tank_message = 'Low tank'; tank_color = 'palegoldenrod'; } 25 | else { tank_color = 'darkseagreen'; tank_message = 'Good tank'; } 26 | 27 | return ( 28 |
29 |

Team Stats

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
HP/Armor/Shields: 40 |
41 |
42 |
43 |
{hitpoints + '/' + armor + '/' + shields}
Barriers:
{barrier}
Healing:
{healing_message}
Tank:
{tank_message}
63 |
64 | ); 65 | }; 66 | 67 | export default TeamStats; 68 | -------------------------------------------------------------------------------- /src/heroes.js: -------------------------------------------------------------------------------- 1 | export const heroes = [{ 2 | id: 1, 3 | name: "Genji", 4 | hitpoints: 200, 5 | armor: 0, 6 | shields: 0, 7 | barrier: 0, 8 | portrait: "genji-portrait.png", 9 | category: "offense", 10 | healing: 0, 11 | tank: 0 12 | }, 13 | { 14 | id: 2, 15 | name: "McCree", 16 | hitpoints: 200, 17 | armor: 0, 18 | shields: 0, 19 | barrier: 0, 20 | portrait: "mccree-portrait.png", 21 | category: "offense", 22 | healing: 0, 23 | tank: 0 24 | }, 25 | { 26 | id: 3, 27 | name: "Pharah", 28 | hitpoints: 200, 29 | armor: 0, 30 | shields: 0, 31 | barrier: 0, 32 | portrait: "pharah-portrait.png", 33 | category: "offense", 34 | healing: 0, 35 | tank: 0 36 | }, 37 | { 38 | id: 4, 39 | name: "Reaper", 40 | hitpoints: 250, 41 | armor: 0, 42 | shields: 0, 43 | barrier: 0, 44 | portrait: "reaper-portrait.png", 45 | category: "offense", 46 | healing: 0, 47 | tank: 0 48 | }, 49 | { 50 | id: 5, 51 | name: "Soldier: 76", 52 | hitpoints: 200, 53 | armor: 0, 54 | shields: 0, 55 | barrier: 0, 56 | portrait: "soldier-76-portrait.png", 57 | category: "offense", 58 | healing: 2, 59 | tank: 0 60 | }, 61 | { 62 | id: 6, 63 | name: "Sombra", 64 | hitpoints: 200, 65 | armor: 0, 66 | shields: 0, 67 | barrier: 0, 68 | portrait: "sombra-portrait.png", 69 | category: "offense", 70 | healing: 0, 71 | tank: 0 72 | }, 73 | { 74 | id: 7, 75 | name: "Tracer", 76 | hitpoints: 150, 77 | armor: 0, 78 | shields: 0, 79 | barrier: 0, 80 | portrait: "tracer-portrait.png", 81 | category: "offense", 82 | healing: 0, 83 | tank: 0 84 | }, 85 | { 86 | id: 8, 87 | name: "Bastion", 88 | hitpoints: 200, 89 | armor: 100, 90 | shields: 0, 91 | barrier: 0, 92 | portrait: "bastion-portrait.png", 93 | category: "defense", 94 | healing: 1, 95 | tank: 0 96 | }, 97 | { 98 | id: 9, 99 | name: "Hanzo", 100 | hitpoints: 200, 101 | armor: 0, 102 | shields: 0, 103 | barrier: 0, 104 | portrait: "hanzo-portrait.png", 105 | category: "defense", 106 | healing: 0, 107 | tank: 0 108 | }, 109 | { 110 | id: 10, 111 | name: "Junkrat", 112 | hitpoints: 200, 113 | armor: 0, 114 | shields: 0, 115 | barrier: 0, 116 | portrait: "junkrat-portrait.png", 117 | category: "defense", 118 | healing: 0, 119 | tank: 0 120 | }, 121 | { 122 | id: 11, 123 | name: "Mei", 124 | hitpoints: 250, 125 | armor: 0, 126 | shields: 0, 127 | barrier: 500, 128 | portrait: "mei-portrait.png", 129 | category: "defense", 130 | healing: 1, 131 | tank: 0 132 | }, 133 | { 134 | id: 12, 135 | name: "Torbjörn", 136 | hitpoints: 200, 137 | armor: 0, 138 | shields: 0, 139 | barrier: 0, 140 | portrait: "torbjorn-portrait.png", 141 | category: "defense", 142 | healing: 0, 143 | tank: 0 144 | }, 145 | { 146 | id: 13, 147 | name: "Widowmaker", 148 | hitpoints: 200, 149 | armor: 0, 150 | shields: 0, 151 | barrier: 0, 152 | portrait: "widowmaker-portrait.png", 153 | category: "defense", 154 | healing: 0, 155 | tank: 0 156 | }, 157 | { 158 | id: 14, 159 | name: "D.Va", 160 | hitpoints: 400, 161 | armor: 200, 162 | shields: 0, 163 | barrier: 1500, 164 | portrait: "d-va-portrait.png", 165 | category: "tank", 166 | healing: 0, 167 | tank: 2 168 | }, 169 | { 170 | id: 15, 171 | name: "Orisa", 172 | hitpoints: 200, 173 | armor: 200, 174 | shields: 0, 175 | barrier: 900, 176 | portrait: "orisa-portrait.png", 177 | category: "tank", 178 | healing: 0, 179 | tank: 1 180 | }, 181 | { 182 | id: 16, 183 | name: "Reinhardt", 184 | hitpoints: 300, 185 | armor: 200, 186 | shields: 0, 187 | barrier: 2000, 188 | portrait: "reinhardt-portrait.png", 189 | category: "tank", 190 | healing: 0, 191 | tank: 2 192 | }, 193 | { 194 | id: 17, 195 | name: "Roadhog", 196 | hitpoints: 600, 197 | armor: 0, 198 | shields: 0, 199 | barrier: 0, 200 | portrait: "roadhog-portrait.png", 201 | category: "tank", 202 | healing: 1, 203 | tank: 1 204 | }, 205 | { 206 | id: 18, 207 | name: "Winston", 208 | hitpoints: 400, 209 | armor: 100, 210 | shields: 0, 211 | barrier: 600, 212 | portrait: "winston-portrait.png", 213 | category: "tank", 214 | healing: 0, 215 | tank: 1 216 | }, 217 | { 218 | id: 19, 219 | name: "Zarya", 220 | hitpoints: 200, 221 | armor: 0, 222 | shields: 200, 223 | barrier: 400, 224 | portrait: "zarya-portrait.png", 225 | category: "tank", 226 | healing: 0, 227 | tank: 1 228 | }, 229 | { 230 | id: 20, 231 | name: "Ana", 232 | hitpoints: 200, 233 | armor: 0, 234 | shields: 0, 235 | barrier: 0, 236 | portrait: "ana-portrait.png", 237 | category: "support", 238 | healing: 2, 239 | tank: 0 240 | }, 241 | { 242 | id: 21, 243 | name: "Lúcio", 244 | hitpoints: 200, 245 | armor: 0, 246 | shields: 0, 247 | barrier: 0, 248 | portrait: "lucio-portrait.png", 249 | category: "support", 250 | healing: 4, 251 | tank: 0 252 | }, 253 | { 254 | id: 22, 255 | name: "Mercy", 256 | hitpoints: 200, 257 | armor: 0, 258 | shields: 0, 259 | barrier: 0, 260 | portrait: "mercy-portrait.png", 261 | category: "support", 262 | healing: 4, 263 | tank: 0 264 | }, 265 | { 266 | id: 23, 267 | name: "Symmetra", 268 | hitpoints: 100, 269 | armor: 0, 270 | shields: 100, 271 | barrier: 1025, 272 | portrait: "symmetra-portrait.png", 273 | category: "support", 274 | healing: 0, 275 | tank: 0 276 | }, 277 | { 278 | id: 24, 279 | name: "Zenyatta", 280 | hitpoints: 50, 281 | armor: 0, 282 | shields: 150, 283 | barrier: 0, 284 | portrait: "zenyatta-portrait.png", 285 | category: "support", 286 | healing: 2, 287 | tank: 0 288 | }]; --------------------------------------------------------------------------------