snake.positions.length === 0;
@@ -29,14 +30,14 @@ const snakeOrdering = (snake1, snake2) => {
if (snake1.name > snake2.name) {
return 1;
- } else if (snake2.name < snake2.name) {
+ } else if (snake1.name < snake2.name) {
return -1;
}
return 0;
};
-const Sidebar = function Sidebar(props) {
+function Sidebar(props) {
if (props.state && props.state.mapEvents) {
const currentMap = props.state.mapEvents[props.state.currentFrame];
let snakes = [];
@@ -48,35 +49,35 @@ const Sidebar = function Sidebar(props) {
snakes.sort(snakeOrdering);
- const snakeColor = (snake) => {
+ const snakeColor = snake => {
if (isSnakeDead(snake)) {
return '#dead';
}
return props.state.colors[snake.id];
};
+
const snakeHead = snake => Images.getSnakeHead(snakeColor(snake), true).src;
return (
- {
- snakes.map(snake => (
-
-
-
-
- {snake.points} {snake.name}
-
- ))}
+ {snakes.map(snake => (
+
+
+
+
+ {snake.points} {snake.name}
+
+ ))}
);
}
- return (
);
-};
+ return
;
+}
Sidebar.propTypes = propTypes;
-export default new StoreWatch(Sidebar, getActiveGame);
+export default StoreWatch(Sidebar, getActiveGame);
diff --git a/src/game/components/watch/StoreWatch.jsx b/src/game/components/watch/StoreWatch.jsx
new file mode 100644
index 0000000..a44aeac
--- /dev/null
+++ b/src/game/components/watch/StoreWatch.jsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import GameStore from '../../../baseStore/BaseStore';
+
+export default (InnerComponent, stateCallback) =>
+ class extends React.Component {
+ state = stateCallback(this.props);
+
+ handleChange = () => {
+ this.setState(stateCallback(this.props));
+ };
+
+ componentDidMount() {
+ GameStore.addChangeListener(this.handleChange);
+ }
+
+ componentWillUnmount() {
+ GameStore.removeChangeListener(this.handleChange);
+ }
+
+ render() {
+ return
;
+ }
+ };
diff --git a/app/game/search/GameSearch.jsx b/src/game/search/GameSearch.jsx
similarity index 59%
rename from app/game/search/GameSearch.jsx
rename to src/game/search/GameSearch.jsx
index 81778b6..2e60645 100644
--- a/app/game/search/GameSearch.jsx
+++ b/src/game/search/GameSearch.jsx
@@ -1,7 +1,8 @@
import React from 'react';
+import PropTypes from 'prop-types';
import { Link } from 'react-router';
-import GameAction from '../action/game-actions';
+import * as GameActions from '../action/game-actions';
import GameStore from '../../baseStore/BaseStore';
import StoreWatch from '../components/watch/StoreWatch';
@@ -11,32 +12,26 @@ function getSearchResults() {
return { searchResults };
}
-const propTypes = {
- text: React.PropTypes.string,
- searchResults: React.PropTypes.object.isRequired,
-};
-
class GameSearch extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- searchName: '',
- };
+ static propTypes = {
+ text: PropTypes.string,
+ searchResults: PropTypes.object.isRequired,
+ };
- this.handleChange = this.handleChange.bind(this);
- this.handleSubmit = this.handleSubmit.bind(this);
- }
+ state = {
+ searchName: '',
+ };
- handleSubmit(e) {
+ handleSubmit = e => {
e.preventDefault();
- GameAction.searchForOldGames(this.state.searchName);
- }
+ GameActions.searchForOldGames(this.state.searchName);
+ };
- handleChange(e) {
+ handleChange = e => {
this.setState({
searchName: e.target.value,
});
- }
+ };
noResultsFound() {
const results = this.props.searchResults;
@@ -51,39 +46,40 @@ class GameSearch extends React.Component {
let results;
if (this.noResultsFound()) {
results = (
-
No result found
);
+
+ No result found
+
+ );
} else {
results = (
-
{
- this.props.searchResults.matchingGames.map((game, index) => (
+
+ {' '}
+ {this.props.searchResults.matchingGames.map((game, index) => (
Date: {game.gameDate}
- {
- game.players.map((player, i) => (
-
- { player }
+
+ {' '}
+ {game.players.map((player, i) => (
+
+ {player}
))}
))}
- );
+
+ );
}
return (
Search for old games
-
- You can find old games here by searching for the snake name.
-
+ You can find old games here by searching for the snake name.
Results
- { results }
+ {results}
@@ -105,5 +101,4 @@ class GameSearch extends React.Component {
}
}
-GameSearch.propTypes = propTypes;
-export default new StoreWatch(GameSearch, getSearchResults);
+export default StoreWatch(GameSearch, getSearchResults);
diff --git a/app/App.jsx b/src/index.js
similarity index 59%
rename from app/App.jsx
rename to src/index.js
index b12f66e..3238ea7 100644
--- a/app/App.jsx
+++ b/src/index.js
@@ -2,7 +2,4 @@ import React from 'react';
import ReactDOM from 'react-dom';
import Routes from './config/Routes';
-ReactDOM.render(
- ,
- document.getElementById('app')
-);
+ReactDOM.render( , document.getElementById('app'));
diff --git a/app/pages/AboutPage.jsx b/src/pages/AboutPage.jsx
similarity index 66%
rename from app/pages/AboutPage.jsx
rename to src/pages/AboutPage.jsx
index 2f05692..50fcd00 100644
--- a/app/pages/AboutPage.jsx
+++ b/src/pages/AboutPage.jsx
@@ -8,20 +8,17 @@ function AboutPage() {
About
- We at Cygni love programming. We also love a friendly competetion over
- a couple of beers. What better way to combine these two things than a
- battle in programming!
+ We at Cygni love programming. We also love a friendly competetion over a couple of beers. What better way to
+ combine these two things than a battle in programming!
- Feel free to hack your own Snake Bot and train it in the Training room.
- From time to time we hold tournaments where you will be able to face
- other player's Snake Bots.
+ Feel free to hack your own Snake Bot and train it in the Training room. From time to time we hold
+ tournaments where you will be able to face other player's Snake Bots.
Game rules
- The rules are configurable per game, upon every game start the clients
- will be notified of the current game settings.
- Here are the default rules:
+ The rules are configurable per game, upon every game start the clients will be notified of the current game
+ settings. Here are the default rules:
Snake grows every third game tick
@@ -32,9 +29,7 @@ function AboutPage() {
5 points per caused death (another snake crashes and dies into your snake)
5 black holes
A nibbled tail is protected for 3 game ticks
- The last surviving Snake always wins.
- The ranking for dead snakes is based on accumulated points
-
+ The last surviving Snake always wins. The ranking for dead snakes is based on accumulated points
diff --git a/src/pages/GettingStartedPage.jsx b/src/pages/GettingStartedPage.jsx
new file mode 100644
index 0000000..f3286c8
--- /dev/null
+++ b/src/pages/GettingStartedPage.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import '../design/styles/stylesheet.scss';
+
+function GettingStartedPage() {
+ return (
+
+
+ Getting started
+
+
+ Your mission is to write the best Snake Bot and survive within the game world. We have prepared several
+ language bindings for you to make it really easy to get started. All the boring stuff concerning
+ server-client communication, message parsing and event handling is already implemented.
+
+
General principles
+
+ The game progresses through Game Ticks. For each Game Tick participating Snake Bots have to choose an action
+ (and they have to do it fast, response is expected within 250ms). Actions are defined by a direction to move
+ the Snake head in. A Snake head may move UP, DOWN, RIGHT or LEFT.
+
+
+ On every Game Tick each Snake Bot receives the current Map. The map contains the positions of all the
+ objects in the map.
+
+
Language bindings
+
+ Below are listed the currently implemented (and up to date) language bindings. Each project has a Readme
+ file that explains how to get going.
+
+
+
+
+
+ );
+}
+export default GettingStartedPage;
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx
new file mode 100644
index 0000000..1dd3c58
--- /dev/null
+++ b/src/pages/HomePage.jsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { Link } from 'react-router';
+import '../design/styles/stylesheet.scss';
+
+function HomePage() {
+ return (
+
+
+ Welcome!
+
+
+ Remember the old game of Snake? One of the first common implementations was available on the phone Nokia
+ 3310.
+
+
+ {' '}
+ Snake Record - Nokia 3310
+
+
+
+ This game is a bit different. To play you need to program your own Snake Bot and you will be competing
+ against other bots! The concept is simple, your snake can move UP, DOWN, RIGHT or LEFT and the winner is the
+ last snake alive. Extra points are awarded when eating stars or nibbling on other snake's tails. Look
+ out for the black holes though!
+
+
+ Getting started is really easy. We have implementations in several popular
+ programming languages. Clone an example Snake bot and get going!
+
+
Checkout the screencasts below:
+
+ VIDEO
+
+
+
+
+ );
+}
+export default HomePage;
diff --git a/src/pages/StatusPage.jsx b/src/pages/StatusPage.jsx
new file mode 100644
index 0000000..e226e7b
--- /dev/null
+++ b/src/pages/StatusPage.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import '../design/styles/stylesheet.scss';
+
+function AboutPage() {
+ return (
+
+ Status
+
+ Nulla nec lectus vel erat. Sed sit amet magna ac ipsum sagittis consectetur at ac magna. Aliquam erat volutpat.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aliquam eros non elit efficitur, ut euismod sapien
+ eleifend. Curabitur sodales enim lacinia orci congue convallis. Nulla nec lectus vel erat venenatis finibus a at
+ nisi. In ac leo mattis, dapibus velit at, gravida diam. Suspendisse ultrices maximus facilisis. Sed sit amet
+ magna ac ipsum sagittis consectetur at ac magna. Aliquam erat volutpat. Nunc eget augue quis lectus
+
+
+ );
+}
+
+export default AboutPage;
diff --git a/src/security/components/LoginPage.jsx b/src/security/components/LoginPage.jsx
new file mode 100644
index 0000000..4fb7b9f
--- /dev/null
+++ b/src/security/components/LoginPage.jsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withRouter } from 'react-router';
+import AuthService from '../services/AuthService';
+
+class LoginPage extends React.Component {
+ static propTypes = {
+ router: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+ };
+
+ state = {
+ isLoading: false,
+ user: '',
+ password: '',
+ };
+
+ login = async e => {
+ e.preventDefault();
+ try {
+ this.setState({ isLoading: true });
+ await AuthService.login(this.state.user, this.state.password);
+
+ const location = this.props.location.state;
+ if (location && location.nextPathname) {
+ this.props.router.push(location.nextPathname);
+ } else {
+ this.props.router.push('/');
+ }
+ } catch (error) {
+ this.setState({
+ error: 'There was an error logging in, did you type your username and password correctly?',
+ });
+ } finally {
+ this.setState({ isLoading: false });
+ }
+ };
+
+ handleUserChange = e => {
+ this.setState({
+ user: e.target.value,
+ });
+ };
+
+ handlePasswordChange = e => {
+ this.setState({
+ password: e.target.value,
+ });
+ };
+
+ render() {
+ return (
+
+
+ Log in
+
+
+
+ {this.state.error ? this.state.error : ''}
+
+
+
+
+ );
+ }
+}
+
+export default withRouter(LoginPage);
diff --git a/src/security/services/AuthService.js b/src/security/services/AuthService.js
new file mode 100644
index 0000000..dbcd152
--- /dev/null
+++ b/src/security/services/AuthService.js
@@ -0,0 +1,24 @@
+import rest from 'rest';
+import errorCode from 'rest/interceptor/errorCode';
+import * as TournamentActions from '../../tournament/action/tournament-actions';
+import { SERVER_URL } from '../../constants/Constants';
+
+class AuthService {
+ static async login(username, password) {
+ const client = rest.wrap(errorCode);
+ try {
+ const token = await client({ path: `${SERVER_URL}/login?login=${username}&password=${password}` });
+ TournamentActions.loginUser(token.entity, username);
+ return token;
+ } catch (error) {
+ console.error('Unable to authenticate user, got response', error);
+ throw error;
+ }
+ }
+
+ static logout() {
+ TournamentActions.logoutUser();
+ }
+}
+
+export default AuthService;
diff --git a/src/tournament/action/tournament-actions.js b/src/tournament/action/tournament-actions.js
new file mode 100644
index 0000000..8e336e9
--- /dev/null
+++ b/src/tournament/action/tournament-actions.js
@@ -0,0 +1,103 @@
+import Constants from '../../constants/Constants';
+import { dispatch } from '../../dispatchers/AppDispatcher';
+
+export function createTournament(name) {
+ dispatch({
+ actionType: Constants.CREATE_TOURNAMENT,
+ name,
+ });
+}
+
+export function updateSettings(key, value) {
+ dispatch({
+ actionType: Constants.UPDATE_SETTINGS,
+ key,
+ value,
+ });
+}
+
+export function createTournamentTable() {
+ dispatch({
+ actionType: Constants.CREATE_TOURNAMENT_TABLE,
+ });
+}
+
+export function killTournament() {
+ dispatch({
+ actionType: Constants.KILL_TOURNAMENT,
+ });
+}
+
+export function tournamentCreated(jsonData) {
+ dispatch({
+ actionType: Constants.TOURNAMENT_CREATED,
+ jsonData,
+ });
+}
+
+export function startTournament() {
+ dispatch({
+ actionType: Constants.START_TOURNAMENT,
+ });
+}
+
+export function tournamentInfoReceived(jsonData) {
+ dispatch({
+ actionType: Constants.TOURNAMENT_INFO_RECEIVED,
+ jsonData,
+ });
+}
+
+export function tournamentGamePlanReceived(jsonData) {
+ dispatch({
+ actionType: Constants.GAME_PLAN_RECEIVED,
+ jsonData,
+ });
+}
+
+export function updatePlayers(players) {
+ dispatch({
+ actionType: Constants.UPDATE_PLAYERS,
+ players,
+ });
+}
+
+export function setActiveTournamentGame(gameId) {
+ dispatch({
+ actionType: Constants.SET_ACTIVE_TOURNAMENT_GAME,
+ gameId,
+ });
+}
+
+export function tournamentEndedEvent(event) {
+ dispatch({
+ actionType: Constants.TOURNAMENT_ENDED_EVENT,
+ event,
+ });
+}
+
+export function loginUser(token, user) {
+ dispatch({
+ actionType: Constants.LOGIN_USER,
+ token,
+ user,
+ });
+}
+
+export function logoutUser() {
+ dispatch({
+ actionType: Constants.LOGOUT_USER,
+ });
+}
+
+export function invalidToken() {
+ dispatch({
+ actionType: Constants.INVALID_TOKEN,
+ });
+}
+
+export function fetchActiveTournament() {
+ dispatch({
+ actionType: Constants.FETCH_ACTIVE_TOURNAMENT,
+ });
+}
diff --git a/app/tournament/components/bracket/Bracket.jsx b/src/tournament/components/bracket/Bracket.jsx
similarity index 73%
rename from app/tournament/components/bracket/Bracket.jsx
rename to src/tournament/components/bracket/Bracket.jsx
index 8795780..b35afdf 100644
--- a/app/tournament/components/bracket/Bracket.jsx
+++ b/src/tournament/components/bracket/Bracket.jsx
@@ -1,7 +1,8 @@
import React from 'react';
+import PropTypes from 'prop-types';
import TournamentStore from '../../../baseStore/BaseStore';
import StoreWatch from '../../watch/StoreWatch';
-import TournamentAction from '../../action/tournament-actions';
+import * as TournamentActions from '../../action/tournament-actions';
import Star from '../../../design/images/star/star.svg';
function getGamePlan() {
@@ -13,14 +14,14 @@ function getGamePlan() {
};
}
-const propTypes = {
- gamePlan: React.PropTypes.object,
- winner: React.PropTypes.object,
-};
-
class Bracket extends React.Component {
+ static propTypes = {
+ gamePlan: PropTypes.object,
+ winner: PropTypes.object,
+ };
+
static chooseGame(game) {
- return () => TournamentAction.setActiveTournamentGame(game.gameId);
+ return () => TournamentActions.setActiveTournamentGame(game.gameId);
}
static roundClassName(round) {
@@ -38,7 +39,7 @@ class Bracket extends React.Component {
}
static renderPlayers(game) {
- return [...Array(game.expectedNoofPlayers).keys()].map((i) => {
+ return [...Array(game.expectedNoofPlayers).keys()].map(i => {
const player = game.players[i];
if (!player) {
@@ -61,9 +62,7 @@ class Bracket extends React.Component {
);
}
- return (
- {player.name}
- );
+ return {player.name} ;
});
}
@@ -74,16 +73,13 @@ class Bracket extends React.Component {
return (
- Go to game
+
+ Go to game
-
);
+
+ );
}
-
static renderWinner(winner) {
if (!winner) {
return