diff --git a/README.md b/README.md index a8bb60a..fdfac6b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ ## Table of contents -* [Intro](#CodeWords) -* [Screenshots](#Screenshots) -* [Getting Started](#Getting-Started) -* [How to Use](#How-to-Use) -* [Project Emphasis](#Project-Emphasis) -* [UI Design](#UI-Design) -* [Future Plans](#Future-Plans) -* [License](#License) +- [CodeWords ![Build Status](https://travis-ci.com/code-words/codewords-ui)](#codewords-build-statushttpstravis-cicomcode-wordscodewords-ui) + - [Screenshots](#screenshots) + - [Getting Started](#getting-started) + - [How to Use](#how-to-use) + - [UI Design](#ui-design) + - [Future Plans](#future-plans) + - [Project Emphasis](#project-emphasis) + - [Front-End](#front-end) + - [Back-end](#back-end) + - [Licensing](#licensing) # CodeWords [![Build Status](https://travis-ci.com/code-words/codewords-ui.svg?branch=master)](https://travis-ci.com/code-words/codewords-ui) diff --git a/src/components/AboutDevs/_AboutDevs.scss b/src/components/AboutDevs/_AboutDevs.scss new file mode 100644 index 0000000..83605f5 --- /dev/null +++ b/src/components/AboutDevs/_AboutDevs.scss @@ -0,0 +1,111 @@ +// ** TEMP +// .dev-pic { +// content: ""; +// background: $intel-assassin-accent; +// height: 75px; +// width: 75px; +// margin: 5px auto; +// border-radius: 50%; +// } + +.AboutDevs { + display: flex; + flex-direction: column; + justify-content: center; + background: $intel-assassin; + position: absolute; + z-index: 3; + left: 10vw; + top: 10vh; + height: 70vh; + width: 75vw; + padding: 30px; + box-shadow: 8px 8px 12px $overlay-filter, 2px 2px 10px rgba(250, 250,250, .5); + border: $intel-assassin-accent .5px inset; + border-radius: 20px; + + .divider { + content: ""; + height: 2px; + width: 100%; + border: 1px inset gray; + } + + .dev-pic { + margin: auto; + border-radius: 50%; + // border: $intel-assassin-accent 1px 1px ridge; + width: 12vh; + } + + .dev-img { + width: 10vh; + border-radius: 50%; + border: silver ridge 2px; + } + + a { + text-decoration: none; + } + + h2 { + margin: 0 0 10px 30px; + text-shadow: 2px 2px 3px black; + } + + h2, h4, h5 { + font-family: $header-font; + } +} + +.devs { + display: flex; + align-items: center; + justify-content: center; + height: 85%; + width: 100%; +} + +.dev { + display: grid; + grid-auto-flow: dense; + background: $button-default; + border-radius: 5px; + box-shadow: 3px 3px 5px $overlay-filter; + color: black; + padding: 20px; + margin: 30px; + height: 80%; + width: 25%; + + p { + overflow: scroll; + min-height: 25vh; + max-height: 25vh; + } + + h4 { + color: black; + } + + .social { + display: flex; + font-size: 3vh; + justify-content: space-evenly; + align-items: flex-end; + position: relative; + left: 20%; + width: 60%; + + a { + text-decoration: none; + color: $intel-assassin; + &:hover { + color: $player-active; + cursor: pointer; + text-shadow: 1px 1px 2px black; + } + } + } +} + diff --git a/src/components/AboutDevs/index.js b/src/components/AboutDevs/index.js new file mode 100644 index 0000000..203df6c --- /dev/null +++ b/src/components/AboutDevs/index.js @@ -0,0 +1,116 @@ +/* eslint-disable jsx-a11y/anchor-has-content */ +import React from 'react'; + +const AboutDevs = (props) => { + const devs = [ + { + name: "Jon Peterson", + gH_Link: "https://github.com/joequincy", + l_Link: "https://www.linkedin.com/in/joequincy/", + p_Link: "https://alumni.turing.io/alumni/jon-peterson", + role: "BE", + img: + "https://media.licdn.com/dms/image/C4E03AQEhILN-5s-jEw/profile-displayphoto-shrink_800_800/0?e=1571270400&v=beta&t=WBZ4ztxssw3W-PwaLADES30A7HvX-4anP6oPfkRLpYs", + summary: + "A tinkerer by nature, I enjoy hacking away on an interesting idea and learning new tricks along the way. I’m always looking for a new idea or technology to explore!" + }, + { + name: "Justin Pyktel", + gH_Link: "https://github.com/SiimonStark", + l_Link: "https://www.linkedin.com/in/justinpyktel/", + p_Link: "https://alumni.turing.io/alumni/justin-pyktel", + role: "FE", + img: + "https://media.licdn.com/dms/image/C4E03AQGFJ9zu6O-7Zw/profile-displayphoto-shrink_200_200/0?e=1571270400&v=beta&t=KmObYjlcIRPiKy0wB2itsWgIJAwBNVyHPKoXlobY_0o", + summary: + "Transitioning into the tech field I intend to keep my audience at the forefront of design choices. Creating a more enjoyable and fun experience is certainly a win win!" + }, + { + name: "Lynne Rang", + gH_Link: "https://github.com/lynnerang", + l_Link: "https://www.linkedin.com/in/lynne-rang/", + p_Link: "https://alumni.turing.io/alumni/lynne-rang", + role: "FE", + img: + "https://media.licdn.com/dms/image/C4E03AQFu8BraqA9MJA/profile-displayphoto-shrink_800_800/0?e=1571270400&v=beta&t=u4QsbYsdbK4CYp0MaKpv0I6Ky9jKs3h0b_lF_3VC8vY", + summary: + "My skills as a developer come together with a holistic understanding of what it takes to make a great product" + }, + { + name: "Rachael Drennan", + gH_Link: "https://github.com/rdren0", + l_Link: "https://www.linkedin.com/in/rachael-drennan/", + p_Link: "https://alumni.turing.io/alumni/rachael-drennan", + role: "FE/BE", + img: + "https://media.licdn.com/dms/image/C4E03AQFmgk9OS5Z88Q/profile-displayphoto-shrink_800_800/0?e=1571270400&v=beta&t=A8lyTbvCZ7IxW1ctg19Oqw0EfVq06xUh1kMK14vybSI", + summary: + "As an artist and problem solver, I was drawn to software engineering because it allows me to be both creative and analytical in my approach to a project." + } + ]; + + const randomizeDevs = () => { + // Resource: + // https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array + + for (let i = devs.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [devs[i], devs[j]] = [devs[j], devs[i]]; + } + } + + const renderDevs = () => { + randomizeDevs(); + + return devs.map(dev => { + return ( +
+
+ {`Pic +
+

{dev.name}

+
Role: {dev.role}
+
+

{dev.summary}

+
+ + + +
+
+ ); + }) + } + + return ( +
+
+ props.history.goBack()} + /> +

Top Secret Case Files:

+
{renderDevs()}
+
+
+ ); +} + +export default AboutDevs; \ No newline at end of file diff --git a/src/components/AboutDevs/index.test.js b/src/components/AboutDevs/index.test.js new file mode 100644 index 0000000..e69de29 diff --git a/src/components/App/index.js b/src/components/App/index.js index cc100a1..1ef2cb1 100644 --- a/src/components/App/index.js +++ b/src/components/App/index.js @@ -3,6 +3,7 @@ import { Route, Switch } from 'react-router-dom'; import Header from '../Header'; import StartScreen from '../StartScreen'; import RuleList from '../RuleList'; +import AboutDevs from '../AboutDevs'; import NewGame from '../NewGame'; import JoinGame from '../JoinGame'; import Lobby from '../Lobby'; @@ -37,12 +38,12 @@ export class App extends Component { }; } - handleUserInit = result => { + handleUserInit = (result, inviteCode) => { const { id, name, token } = result; localStorage.setItem("Token", token); const user = { id, name, token } - if (result.invite_code) this.setState({ invite_code: result.invite_code }); - this.setState({ user }, () => this.createCable(token)); + // if (result.invite_code) this.setState({ invite_code: result.invite_code }); + this.setState({ user, invite_code: inviteCode || result.invite_code }, () => this.createCable(token)); }; updateHintLogs = data => { @@ -140,6 +141,31 @@ export class App extends Component { } } + closeReplay = () => { + this.setState({ + user: {}, + invite_code: '', + playerRoster: [], + hintLogs: [], + cardData: [], + cable: {}, + isLobbyFull: false, + currentPlayerId: null, + remainingAttempts: null, + winner: "", + currentHint: { + hintWord: "", + relatedCards: null + }, + showDialog: false, + confMsg: "" + }); + } + + finishGame = (code) => { + this.setState({invite_code: code}); + } + dataSwitch = result => { const { type, data } = result; switch (type) { @@ -156,9 +182,8 @@ export class App extends Component { this.setGuess(data); break; case 'game-over': - console.log(data) this.setGuess(data); - // add resetting of invite code from response and resetting of some state properties + this.finishGame(data.nextGame); setTimeout(() => this.setState({ winner: data.winningTeam}), 2500); break; case 'illegal-action': @@ -194,10 +219,16 @@ export class App extends Component { }; render() { - const { showDialog, cardData, confMsg } = this.state; + const { showDialog, cardData, confMsg, user, invite_code } = this.state; - const replay = !this.state.winner ? null - : < ReplayScreen winner={this.state.winner} closeReplay={() => this.setState({winner: ''})}/> + const replay = !this.state.winner ? null : ( + + ); const dialog = showDialog ? + } /> diff --git a/src/components/Header/_Header.scss b/src/components/Header/_Header.scss index 2fc84d2..2b060ac 100644 --- a/src/components/Header/_Header.scss +++ b/src/components/Header/_Header.scss @@ -4,6 +4,12 @@ justify-content: space-between; align-items: center; margin: 0 20px; + position: relative; + z-index: 99; + + .header-left { + cursor: pointer; + } .header-title { color: white; @@ -15,6 +21,8 @@ color: white; align-items: center; + + .new-game-btn { margin-left: 30px; padding: 5px 15px; @@ -36,3 +44,9 @@ } } } + +.label { + position: absolute; + left: 2%; + background: $button-default; +} \ No newline at end of file diff --git a/src/components/Header/index.js b/src/components/Header/index.js index 80b9564..a8f9074 100644 --- a/src/components/Header/index.js +++ b/src/components/Header/index.js @@ -1,23 +1,59 @@ -import React from 'react'; +import React, {Component} from 'react'; import { NavLink } from 'react-router-dom'; +import { CopyToClipboard } from "react-copy-to-clipboard"; -const Header = () => { - return ( +class Header extends Component { + constructor() { + super() + this.state = { + isHover: false + } + } + + toggleHover = () => { + this.setState({isHover: !this.state.isHover}) + } + + render() { + const label = ( + +
+ Click to Copy Link + +
+
+ ); -
-
-

CodeWords

-
- -
- ); + return ( +
+
+ +

CodeWords

+
+ {this.state.isHover && label} +
+ +
+ ); + } }; export default Header; \ No newline at end of file diff --git a/src/components/JoinGame/index.js b/src/components/JoinGame/index.js index 9f1ea16..b4cb46f 100644 --- a/src/components/JoinGame/index.js +++ b/src/components/JoinGame/index.js @@ -1,10 +1,10 @@ import React, { Component } from 'react'; import { API_ROOT, HEADERS } from "../../variables"; -import { Redirect } from "react-router-dom"; +import { Redirect, withRouter } from "react-router-dom"; class JoinGame extends Component { - constructor() { - super() + constructor(props) { + super(props); this.state = { inviteCode: "", name: "", @@ -12,7 +12,7 @@ class JoinGame extends Component { }; } - handleSubmit = (e) => { + handleSubmit = e => { e.preventDefault(); let { name, inviteCode } = this.state; @@ -23,22 +23,31 @@ class JoinGame extends Component { }; fetch(`${API_ROOT}/v1/games/${inviteCode}/players`, option) .then(response => response.json()) - .then(result => { this.props.handleUserInit(result) }); + .then(result => { + this.props.handleUserInit(result, inviteCode); + }); this.setState({ redirect: true }); - } + }; - handleChange = (e) => { - const { name, value } = e.target; - this.setState({ [name]: value }) - } + handleChange = e => { + const { name, value } = e.target; + this.setState({ [name]: value }); + }; render() { - if (this.state.redirect) { return }; + if (this.state.redirect) { + return ; + } return (
+ this.props.history.goBack()} + />

Friends started a game?

@@ -74,4 +83,4 @@ class JoinGame extends Component { } } -export default JoinGame; \ No newline at end of file +export default withRouter(JoinGame); \ No newline at end of file diff --git a/src/components/Main/Board/Card/index.test.js b/src/components/Main/Board/Card/index.test.js index d294142..96e153c 100644 --- a/src/components/Main/Board/Card/index.test.js +++ b/src/components/Main/Board/Card/index.test.js @@ -18,7 +18,7 @@ describe('Card', () => { expect(wrapper).toMatchSnapshot(); }); it('sendGuess should be called on click of the card', () => { - wrapper = shallow(); + wrapper = shallow(); jest.spyOn(wrapper.instance(), 'handleClick'); let mockEvent = { target: { value: '', name: 'title' }, currentTarget: {value: 3} }; diff --git a/src/components/NewGame/index.js b/src/components/NewGame/index.js index 668654b..6ec263c 100644 --- a/src/components/NewGame/index.js +++ b/src/components/NewGame/index.js @@ -1,11 +1,11 @@ import React, { Component } from 'react'; import {API_ROOT, HEADERS} from '../../variables' -import { Redirect } from 'react-router-dom'; +import { Redirect, withRouter } from "react-router-dom"; import PropTypes from 'prop-types'; class NewGame extends Component { - constructor() { - super() + constructor(props) { + super(props) this.state= { name: '', redirect: false @@ -39,34 +39,35 @@ class NewGame extends Component { return (
- + this.props.history.goBack()} + /> +

Creating a new game?

- - Choose your agent name + + onChange={this.handleChange} + type="text" + placeholder="Enter Name" + />
-
- ) + ); } } @@ -74,4 +75,4 @@ NewGame.propTypes = { handleUserInit: PropTypes.func } -export default NewGame; \ No newline at end of file +export default withRouter(NewGame); \ No newline at end of file diff --git a/src/components/ReplayScreen/index.js b/src/components/ReplayScreen/index.js index 5288856..dbcf370 100644 --- a/src/components/ReplayScreen/index.js +++ b/src/components/ReplayScreen/index.js @@ -1,23 +1,64 @@ -import React from 'react'; +import React, {Component} from 'react'; +import { API_ROOT, HEADERS } from '../../variables'; import { Link } from 'react-router-dom'; -const ReplayScreen = props => { - return ( -
-
-

{props.winner.toUpperCase()} team wins!

- -
- - Start Over - - - Play Again! - +class ReplayScreen extends Component { + constructor(props) { + super(props); + this.state = { + }; + } + + handlePlayAgain = () => { + let { name, inviteCode } = this.props.userData; + this.props.closeReplay(); + + let option = { + headers: HEADERS, + method: "POST", + body: JSON.stringify({ name }) + }; + fetch(`${API_ROOT}/v1/games/${inviteCode}/players`, option) + .then(response => response.json()) + .then(result => { + this.props.handleUserInit(result, inviteCode); + }); + }; + + render() { + return ( +
+
+

+ {this.props.winner.toUpperCase()} team wins! +

+ {`${this.props.winner}`} +
+ + Start Over + + + Play Again! +
+
-
- ); + ); + } }; export default ReplayScreen; \ No newline at end of file diff --git a/src/components/RuleList/index.js b/src/components/RuleList/index.js index 6fead12..30862f3 100644 --- a/src/components/RuleList/index.js +++ b/src/components/RuleList/index.js @@ -1,39 +1,83 @@ import React from 'react'; -const RuleList = () => { +const RuleList = (props) => { return (
+ props.history.goBack()} + />

Rule List:

- CodeNames is a game of guessing which code names (words) in a set are related to a hint-word given by another player.[2]
- Players split into two teams: red and blue. One player of each team is selected as the team's spymaster; the others are field operatives. + CodeNames is a game of guessing which code names (words) in a + set are related to a hint-word given by another player.[2]{" "} +
+ Players split into two teams: red and blue. One player of each + team is selected as the team's spymaster; the others are field + operatives.

- Twenty-five code name cards, each bearing a word, are laid out in a 5×5 rectangular grid, in random order. A number of these words represent red agents, a number represent blue agents, one represents an assassin, and the others represent innocent bystanders. + Twenty-five code name cards, each bearing a word, are laid out + in a 5×5 rectangular grid, in random order. A number of these + words represent red agents, a number represent blue agents, one + represents an assassin, and the others represent innocent + bystanders.

- The teams' spymasters are given a randomly-dealt map card showing a 5×5 grid of 25 squares of various colors, each corresponding to one of the code name cards on the table. Teams take turns. On each turn, the appropriate spymaster gives a verbal hint about the words on the respective cards. Each hint may only consist of one single word and a number. The spymaster gives a hint that is related to as many of the words on his/her own agents' cards as possible, but not to any others – lest they accidentally lead their team to choose a card representing an innocent bystander, an opposing agent, or the assassin. + The teams' spymasters are given a randomly-dealt map card + showing a 5×5 grid of 25 squares of various colors, each + corresponding to one of the code name cards on the table. Teams + take turns. On each turn, the appropriate spymaster gives a + verbal hint about the words on the respective cards. Each hint + may only consist of one single word and a number. The spymaster + gives a hint that is related to as many of the words on his/her + own agents' cards as possible, but not to any others – lest they + accidentally lead their team to choose a card representing an + innocent bystander, an opposing agent, or the assassin.

- The hint's word can be chosen freely, as long as it is not (and does not contain) any of the words on the code name cards still showing at that time. Code name cards are covered as guesses are made. + The hint's word can be chosen freely, as long as it is not (and + does not contain) any of the words on the code name cards still + showing at that time. Code name cards are covered as guesses are + made.

- The hint's number tells the field operatives how many words in the grid are related to the word of the clue. It also determines the maximum number of guesses the field operatives may make on that turn, which is the hint's number plus one. Field operatives must make at least one guess per turn, risking a wrong guess and its consequences. They may also end their turn voluntarily at any point thereafter. + The hint's number tells the field operatives how many words in + the grid are related to the word of the clue. It also determines + the maximum number of guesses the field operatives may make on + that turn, which is the hint's number plus one. Field operatives + must make at least one guess per turn, risking a wrong guess and + its consequences. They may also end their turn voluntarily at + any point thereafter.

- After a spymaster gives the hint with its word and number, their field operatives make guesses about which code name cards bear words related to the hint and point them out, one at a time. When a code name card is pointed out, the spymaster covers that card with an appropriate identity card – a blue agent card, a red agent card, an innocent bystander card, or the assassin card – as indicated on the spymasters' map of the grid. If the assassin is pointed out, the game ends immediately, with the team who identified him losing. If an agent of the other team is pointed out, the turn ends immediately, and that other team is also one agent closer to winning. If an innocent bystander is pointed out, the turn simply ends. + After a spymaster gives the hint with its word and number, their + field operatives make guesses about which code name cards bear + words related to the hint and point them out, one at a time. + When a code name card is pointed out, the spymaster covers that + card with an appropriate identity card – a blue agent card, a + red agent card, an innocent bystander card, or the assassin card + – as indicated on the spymasters' map of the grid. If the + assassin is pointed out, the game ends immediately, with the + team who identified him losing. If an agent of the other team is + pointed out, the turn ends immediately, and that other team is + also one agent closer to winning. If an innocent bystander is + pointed out, the turn simply ends.

- The game ends when all of one team's agents are identified (winning the game for that team),[3] or when one team has identified the assassin (losing the game). + The game ends when all of one team's agents are identified + (winning the game for that team),[3] or when one team has + identified the assassin (losing the game).

- ) + ); } export default RuleList; \ No newline at end of file diff --git a/src/scss/index.scss b/src/scss/index.scss index ca350d5..189060c 100644 --- a/src/scss/index.scss +++ b/src/scss/index.scss @@ -14,6 +14,7 @@ html { @import '../components/Header/Header'; @import '../components/StartScreen/StartScreen'; @import '../components/RuleList/RuleList'; +@import '../components/AboutDevs/AboutDevs'; // @import '../components/NewGame/NewGame'; @import '../components/JoinGame/JoinGame'; @import '../components/Lobby/Lobby'; @@ -26,6 +27,20 @@ html { @import '../components/ConfDialog/ConfDialog'; @import '../components/ReplayScreen/ReplayScreen'; +#back { + color: $button-default; + text-shadow: 2px 2px 1px $intel-assassin-accent; + position: absolute; + top: 2vh; + left: 1vw; + font-size: 22px; + + &:hover { + color: $player-active; + cursor: pointer; + } +} + body { margin: 0; height: 100vh;